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/.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 36f04ebefb63..fd2be686c8d3 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -2,96 +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 [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 + - 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 + + ``` +
- ```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? + ```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 b019c11123bc..176b89065001 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -2,49 +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 [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? + - 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 4790122ef11a..b94e9ed4ba7b 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -2,48 +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 [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? + - 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 d5431b320f18..15fd1aad7a78 100644 --- a/.github/ISSUE_TEMPLATE/new-rule.yml +++ b/.github/ISSUE_TEMPLATE/new-rule.yml @@ -2,55 +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 [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? + - 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 bc20f2deea2e..3d36d3f6c152 100644 --- a/.github/ISSUE_TEMPLATE/new-syntax.yml +++ b/.github/ISSUE_TEMPLATE/new-syntax.yml @@ -1,63 +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 [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? + - 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 e91d16a488fb..e85447fab9f2 100644 --- a/.github/ISSUE_TEMPLATE/rule-change.yml +++ b/.github/ISSUE_TEMPLATE/rule-change.yml @@ -2,63 +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 [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? + - 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 5fd747b29061..59c87c2d76ee 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,7 +6,7 @@ #### Prerequisites checklist -- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md). +- [ ] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md). #### What is the purpose of this pull request? (put an "X" next to an item) 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 index ea024d8f7846..1da4d3fa6555 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -5,26 +5,36 @@ # - all-globs-to-all-files: ['docs/**', '!lib/rules/**'] rule: -- any: - - changed-files: - - any-glob-to-any-file: ['lib/rules/**'] + - 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/**'] + - 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'] + - 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/**'] + - any: + - changed-files: + - any-glob-to-any-file: ["lib/cli-engine/formatters/**"] "github actions": -- any: - - changed-files: - - any-glob-to-any-file: ['.github/workflows/**'] + - any: + - changed-files: + - any-glob-to-any-file: [".github/workflows/**"] diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8163f03867c8..068c34a5b545 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,42 +1,37 @@ { - "$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"], - // 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: "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 index d94331771221..a3047eadb444 100644 --- a/.github/workflows/annotate_pr.yaml +++ b/.github/workflows/annotate_pr.yaml @@ -1,25 +1,25 @@ name: Annotate PR with trunk issues on: - workflow_run: - workflows: [Pull Request] - types: [completed] + workflow_run: + workflows: [Pull Request] + types: [completed] permissions: read-all jobs: - trunk_check: - name: Trunk Check Annotate - runs-on: ubuntu-latest + trunk_check: + name: Trunk Check Annotate + runs-on: ubuntu-latest - permissions: - checks: write + permissions: + checks: write - steps: - - name: Checkout - uses: actions/checkout@v4 + steps: + - name: Checkout + uses: actions/checkout@v4 - - name: Trunk Check - uses: trunk-io/trunk-action@v1 - with: - post-annotations: true # only for fork PRs + - 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 85a5bac501fc..b8abbe293ef8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,152 +1,141 @@ 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: 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: [23.x, 22.x, 21.x, 20.x, 18.x, "18.18.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 - - name: Test EMFILE Handling - run: npm run test:emfile - - test_on_browser: - name: Browser Test - runs-on: ubuntu-latest - 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 wdio - - name: Fuzz Test - run: node Makefile fuzz - - uses: actions/upload-artifact@v4 - if: failure() - with: - name: logs - path: | - wdio-logs/*.log - - 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 + 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: [23.x, 22.x, 21.x, 20.x, 18.x, "18.18.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 + - 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 d68582fa600a..b746a6fb07de 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,63 +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' ] + 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 + # 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 - steps: - - name: Checkout repository - uses: actions/checkout@v4 + steps: + - name: Checkout repository + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + 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 + # 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 - # 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 + # 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 - # â„šī¸ 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 + # â„šī¸ 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 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 + # âœī¸ 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 + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml index de07aa1819ba..6728bfcf1a6e 100644 --- a/.github/workflows/docs-ci.yml +++ b/.github/workflows/docs-ci.yml @@ -1,43 +1,43 @@ name: CI on: - push: - branches: [main] - paths: - - 'docs/**' + push: + branches: [main] + paths: + - "docs/**" - pull_request: - branches: [main] - paths: - - 'docs/**' + pull_request: + branches: [main] + paths: + - "docs/**" permissions: - contents: read + 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 + 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 index d58080259b8d..cdcd28f96746 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -1,12 +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 + 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 index 47ef47b6dd4e..0bd3be8877ae 100644 --- a/.github/workflows/rebuild-docs-sites.yml +++ b/.github/workflows/rebuild-docs-sites.yml @@ -1,17 +1,17 @@ name: Rebuild Docs Sites on: - push: - branches: [main] - paths: - - 'docs/src/_data/versions.json' + 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}}" + 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 3e8b0216db3f..80cd1aa11c0e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,29 +6,28 @@ name: Mark stale issues and pull requests on: - schedule: - - cron: '31 22 * * *' + schedule: + - cron: "31 22 * * *" permissions: read-all jobs: - stale: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - - 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 + 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 index 863b0e5bb678..4c8d27793210 100644 --- a/.github/workflows/types-integration.yml +++ b/.github/workflows/types-integration.yml @@ -1,213 +1,213 @@ name: CI on: - push: - branches: [main] - pull_request: - branches: [main] + push: + branches: [main] + pull_request: + branches: [main] permissions: - contents: read + 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 - npm install ../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 - npm install ../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 (neostandard) - working-directory: antfu - run: | - npm install - 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 - 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 (neostandard) - 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 + 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 + npm install ../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 + npm install ../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 (neostandard) + working-directory: antfu + run: | + npm install + 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 + 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 (neostandard) + 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 f1071502b1aa..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: 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: Save updated files + run: | + chmod +x ./tools/commit-readme.sh + ./tools/commit-readme.sh diff --git a/.gitignore b/.gitignore index 6b048fd49b49..a4b2462a064f 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 diff --git a/.markdownlint.yml b/.markdownlint.yml index c9fa0d884321..0a4841f824a7 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,18 +1,10 @@ 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 diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index b731ea2c1b32..48535b6c06ab 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -3,5 +3,5 @@ entry: eslint description: "An AST-based pattern checker for JavaScript." language: node - types: ['javascript'] + types: ["javascript"] require_serial: false 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/configs/svgo.config.js b/.trunk/configs/svgo.config.js index f4467232754b..81cde54069ec 100644 --- a/.trunk/configs/svgo.config.js +++ b/.trunk/configs/svgo.config.js @@ -1,16 +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 - } - } - } - ] + 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 index d443c2951d3b..cdd2e82e1d43 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -5,7 +5,7 @@ cli: version: 1.22.3 repo: - trunk_remote_hint: github.com/eslint/eslint + trunk_remote_hint: github.com/eslint/eslint # Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) plugins: @@ -26,9 +26,16 @@ tools: # 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] # Add YAML to default files. + files: [typescript, javascript, yaml, json] # Add YAML and JSON to default files. hold_the_line: false commands: - name: lint @@ -54,13 +61,24 @@ lint: - yamllint - trufflehog # Requires the network to run. ignore: - - linters: [markdownlint] + - linters: [markdownlint, prettier] paths: - CHANGELOG.md - linters: [prettier] paths: - - "**" # Ignore all files - - "!conf/**/*.json" # Except for json files in conf/ + - 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 + - package.json + - packages/*/package.json + - packages/js/src/configs/eslint-all.js + - tests/fixtures actions: disabled: - trunk-announce diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d070fd0c3cb..1e7b85b67c4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,43 @@ +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) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f04df5df3b77..b602905da780 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,10 +10,10 @@ This project adheres to the [OpenJS Foundation Code of Conduct](https://eslint.o 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 [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. diff --git a/Makefile.js b/Makefile.js index ce54c024bfff..46f7755bb634 100644 --- a/Makefile.js +++ b/Makefile.js @@ -11,18 +11,18 @@ //------------------------------------------------------------------------------ const checker = require("npm-license"), - 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"); + 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` */ @@ -31,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, mkdir, pwd, test } = require("shelljs"); +const { + cat, + cd, + echo, + exec, + exit, + find, + mkdir, + pwd, + test, +} = require("shelljs"); //------------------------------------------------------------------------------ // Settings @@ -46,8 +56,15 @@ const { cat, cd, echo, exec, exit, find, mkdir, pwd, test } = require("shelljs") 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, /BlueOak/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"; @@ -57,36 +74,34 @@ const MAIN_GIT_BRANCH = "main"; //------------------------------------------------------------------------------ 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"), - 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; + 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 @@ -98,7 +113,7 @@ const NODE = "node ", // intentional extra space * @returns {string} The result of the executed command. */ function execSilent(cmd) { - return exec(cmd, { silent: true }).stdout; + return exec(cmd, { silent: true }).stdout; } /** @@ -106,7 +121,7 @@ function execSilent(cmd) { * @returns {string} Name of the currently checked out Git branch. */ function getCurrentGitBranch() { - return execSilent("git branch --show-current").trim(); + return execSilent("git branch --show-current").trim(); } /** @@ -117,30 +132,40 @@ function getCurrentGitBranch() { * @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); } /** @@ -149,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); } /** @@ -167,63 +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.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); + 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); } /** @@ -233,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); } /** @@ -249,11 +278,11 @@ 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); } /** @@ -262,7 +291,7 @@ function publishSite() { * @returns {boolean} `true` if it is a prerelease, `false` otherwise. */ function isPreRelease(version) { - return /[a-z]/u.test(version); + return /[a-z]/u.test(version); } /** @@ -272,55 +301,62 @@ function isPreRelease(version) { * @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 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`); } /** @@ -328,14 +364,16 @@ function updateVersions(oldVersion, newVersion) { * @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); - } + 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); + } } /** @@ -348,35 +386,41 @@ function updateRuleTypeHeaders() { * @returns {void} */ function generateRelease({ prereleaseId, packageTag }) { - echo(`Current Git branch: ${getCurrentGitBranch()}`); + echo(`Current Git branch: ${getCurrentGitBranch()}`); - const oldVersion = require("./package.json").version; + const oldVersion = require("./package.json").version; - ReleaseOps.generateRelease(prereleaseId, packageTag); - const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + 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("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); + 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`); + docsPackage.version = releaseInfo.version; + fs.writeFileSync( + docsPackagePath, + `${JSON.stringify(docsPackage, null, 4)}\n`, + ); - if (getCurrentGitBranch() === MAIN_GIT_BRANCH) { - updateVersions(oldVersion, releaseInfo.version); - } + if (getCurrentGitBranch() === MAIN_GIT_BRANCH) { + updateVersions(oldVersion, releaseInfo.version); + } - echo("Updating rule type header comments"); - updateRuleTypeHeaders(); + 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}`); + 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}`); } /** @@ -385,35 +429,45 @@ function generateRelease({ prereleaseId, packageTag }) { * @returns {void} */ function publishRelease() { - ReleaseOps.publishRelease(); - const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + 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" + 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`); + 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}`); + // 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`); + exec(`git checkout ${MAIN_GIT_BRANCH} --force`); - fs.writeFileSync(CHANGELOG_FILE, `${releaseInfo.markdownChangelog}${cat(CHANGELOG_FILE)}`); + fs.writeFileSync( + CHANGELOG_FILE, + `${releaseInfo.markdownChangelog}${cat(CHANGELOG_FILE)}`, + ); - const versions = JSON.parse(cat(VERSIONS_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`); + 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"); - } + exec(`git add ${CHANGELOG_FILE} ${VERSIONS_FILE}`); + exec( + `git commit -m "chore: updates for v${releaseInfo.version} release"`, + ); + exec("git push origin HEAD"); + } } /** @@ -422,7 +476,7 @@ function publishRelease() { * @returns {Array} The separated lines. */ function splitCommandResultToLines(result) { - return result.trim().split("\n"); + return result.trim().split("\n"); } /** @@ -431,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.at(-1).trim(); + commits = splitCommandResultToLines(commits); + return commits.at(-1).trim(); } /** @@ -443,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]; } /** @@ -463,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]; } /** @@ -474,13 +530,13 @@ function getCommitDeletingFile(filePath) { * @returns {string} The version number. */ function getFirstVersionOfDeletion(filePath) { - const deletionCommit = getCommitDeletingFile(filePath), - tags = execSilent(`git tag --contains ${deletionCommit}`); + 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]; + return splitCommandResultToLines(tags) + .map(version => semver.valid(version.trim())) + .filter(version => version) + .sort(semver.compare)[0]; } /** @@ -488,56 +544,63 @@ function getFirstVersionOfDeletion(filePath) { * @returns {Object} Output from each formatter */ function getFormatterResults() { - 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: {} }); + 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: {} }, + ); } /** @@ -546,348 +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.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.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 --statements 99 --branches 98 --functions 99 --lines 99`); - 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.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"); - - 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('../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 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.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('../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 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("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.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); + } + }, + ); }; /** @@ -897,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, + ); + } } /** @@ -912,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": { @@ -926,7 +1051,7 @@ module.exports = [{ } }];`; - content.to(PERF_ESLINTRC); + content.to(PERF_ESLINTRC); } /** @@ -946,33 +1071,35 @@ module.exports = [{ * @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); + }, + ); } /** @@ -984,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(); + }); } /** @@ -1018,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 = ([packageTag]) => generateRelease({ packageTag }); -target.generatePrerelease = ([prereleaseId]) => generateRelease({ prereleaseId, packageTag: "next" }); +target.generatePrerelease = ([prereleaseId]) => + generateRelease({ prereleaseId, packageTag: "next" }); target.publishRelease = publishRelease; diff --git a/README.md b/README.md index 826c23f9996d..13b5646f9048 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ 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/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. +- 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 @@ -75,21 +75,21 @@ You can configure rules in your `eslint.config.js` files as in this example: import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.js", "**/*.cjs", "**/*.mjs"], - rules: { - "prefer-const": "warn", - "no-constant-binary-expression": "error" - } - } + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + rules: { + "prefer-const": "warn", + "no-constant-binary-expression": "error", + }, + }, ]); ``` 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: -* `"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) +- `"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) 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)). @@ -109,16 +109,16 @@ ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/con Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [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) +- [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) ## 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? @@ -174,32 +174,32 @@ 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. @@ -313,6 +313,7 @@ Percy Ma + ## Sponsors The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate) @@ -321,11 +322,12 @@ to get your logo on our READMEs and [website](https://eslint.org/sponsors).

Platinum Sponsors

Automattic Airbnb

Gold Sponsors

Qlty Software trunk.io Shopify

Silver Sponsors

-

Vite JetBrains Liftoff StackBlitz

Bronze Sponsors

+

Vite Liftoff StackBlitz

Bronze Sponsors

Cybozu Anagram Solver Icons8 Discord GitBook Neko 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

+ [tidelift]: https://tidelift.com/funding/github/npm/eslint diff --git a/bin/eslint.js b/bin/eslint.js index 5ba2ff50cdb2..fb333bcadc01 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -16,7 +16,7 @@ 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:*"); } //------------------------------------------------------------------------------ @@ -45,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); + }); } /** @@ -67,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("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); + // 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); } /** @@ -116,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); + } } //------------------------------------------------------------------------------ @@ -138,42 +136,46 @@ ${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@latest'."); - - const spawn = require("cross-spawn"); - - spawn.sync("npm", ["init", "@eslint/config@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); + 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; + } + + // 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/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 index 4e38c1d2cc37..0b23f6bc12bb 100644 --- a/conf/ecma-version.js +++ b/conf/ecma-version.js @@ -12,5 +12,5 @@ const LATEST_ECMA_VERSION = 2025; module.exports = { - LATEST_ECMA_VERSION + LATEST_ECMA_VERSION, }; diff --git a/conf/globals.js b/conf/globals.js index 81df6bb20018..e89df78624c0 100644 --- a/conf/globals.js +++ b/conf/globals.js @@ -10,151 +10,150 @@ //----------------------------------------------------------------------------- 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, - 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 + ...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 + ...es2024, }; - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { - commonjs, - es3, - es5, - es2015, - es2016, - es2017, - es2018, - es2019, - es2020, - es2021, - es2022, - es2023, - es2024, - es2025 + commonjs, + es3, + es5, + es2015, + es2016, + es2017, + es2018, + es2019, + es2020, + es2021, + es2022, + es2023, + es2024, + es2025, }; 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 ddd7e094d38e..03a757808f20 100644 --- a/conf/rule-type-list.json +++ b/conf/rule-type-list.json @@ -1,94 +1,90 @@ { - "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" } } - ] - }, - { - "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": [] } - ] + "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" } } + ] + }, + { + "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 cd50c27ee3c3..30c589ac4157 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -10,166 +10,173 @@ const Image = require("@11ty/eleventy-img"); 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"); const prismESLintHook = require("./tools/prism-eslint-hook"); - -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 ` +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) { - 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 }) { - - prismESLintHook.addContentMustBeMarked(codeBlockToken.content, languageOptions); - - const isRuleRemoved = !Object.hasOwn(env.rules_meta, env.title); - - if (isRuleRemoved) { - 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 ` + } + + /** + * 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} @@ -290,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) => `
${text}
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 3498c2261c18..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. - languageOptions: { 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!"); 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/integration-tutorial-code/example-eslint-integration.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.js index 7c7ecb3ed007..8840fe7fc624 100644 --- a/docs/_examples/integration-tutorial-code/example-eslint-integration.js +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.js @@ -7,57 +7,59 @@ const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function function createESLintInstance(overrideConfig) { - return new ESLint({ - overrideConfigFile: true, - overrideConfig, - fix: true - }); + 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 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); + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } +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/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 index a21fb4047ac2..541ade36818c 100644 --- a/docs/netlify.toml +++ b/docs/netlify.toml @@ -1,2 +1,2 @@ [build] - command = "cd .. && npm install && cd ./docs && npm run build" +command = "cd .. && npm install && cd ./docs && npm run build" diff --git a/docs/package.json b/docs/package.json index 8304a08f73a4..0d98a1d40e05 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "9.22.0", + "version": "9.23.0", "description": "", "main": "index.js", "keywords": [], @@ -31,6 +31,7 @@ "@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", @@ -48,9 +49,10 @@ "postcss-cli": "^10.0.0", "postcss-html": "^1.5.0", "prismjs": "^1.29.0", - "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" 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 5b081b7c830b..e7918441959b 100644 --- a/docs/src/_data/config.json +++ b/docs/src/_data/config.json @@ -1,3 +1,3 @@ { - "lang": "en" + "lang": "en" } diff --git a/docs/src/_data/conversions.json b/docs/src/_data/conversions.json index 865c27a40476..dd9023606682 100644 --- a/docs/src/_data/conversions.json +++ b/docs/src/_data/conversions.json @@ -1,42 +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" - } + "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/eslintVersions.js b/docs/src/_data/eslintVersions.js index 40ba029011d2..a9d3b3735591 100644 --- a/docs/src/_data/eslintVersions.js +++ b/docs/src/_data/eslintVersions.js @@ -15,64 +15,63 @@ const eleventyFetch = require("@11ty/eleventy-fetch"); // Exports //----------------------------------------------------------------------------- -module.exports = async function() { +module.exports = async function () { + const thisBranch = process.env.BRANCH; + const thisVersion = require("../../package.json").version; - 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"; - // 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 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; - const { items } = data; + let foundItemForThisBranch = false; + let isPrereleasePhase = false; - let foundItemForThisBranch = false; - let isPrereleasePhase = false; + for (const item of items) { + const isItemForThisBranch = item.branch === thisBranch; - for (const item of items) { - const isItemForThisBranch = item.branch === thisBranch; + foundItemForThisBranch ||= isItemForThisBranch; - foundItemForThisBranch ||= isItemForThisBranch; + const isNumberVersion = /^\d/u.test(item.version); // `false` for HEAD - const isNumberVersion = /^\d/u.test(item.version); // `false` for HEAD + if (isNumberVersion) { + // Make sure the version is correct + if (isItemForThisBranch) { + item.version = thisVersion; + } - if (isNumberVersion) { + item.display = `v${item.version}`; + } else { + item.display = item.version; + } - // Make sure the version is correct - if (isItemForThisBranch) { - item.version = thisVersion; - } + if (isItemForThisBranch) { + item.selected = true; + } - item.display = `v${item.version}`; - } else { - item.display = item.version; - } + if (item.branch === "next") { + isPrereleasePhase = true; + } + } - if (isItemForThisBranch) { - item.selected = true; - } + // Add an empty item if this is not a production branch + if (!foundItemForThisBranch) { + items.unshift({ + version: "", + branch: "", + display: "", + path: "", + selected: true, + }); + } - if (item.branch === "next") { - isPrereleasePhase = true; - } - } + data.isPrereleasePhase = isPrereleasePhase; - // 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; + return data; }; diff --git a/docs/src/_data/flags.js b/docs/src/_data/flags.js index ec697b7088e4..6e11aaf9887e 100644 --- a/docs/src/_data/flags.js +++ b/docs/src/_data/flags.js @@ -15,32 +15,35 @@ * @returns {boolean} `true` if the flag is used for test purposes only. */ function isTestOnlyFlag(name) { - return name.startsWith("test_only"); + return name.startsWith("test_only"); } //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- -module.exports = function() { +module.exports = function () { + const { + activeFlags, + inactiveFlags, + getInactivityReasonMessage, + } = require("../../../lib/shared/flags"); - 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) - } - ]) - ) - }; + 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 db29e79227f4..2afba4036891 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -1,786 +1,786 @@ { - "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 - } -} \ 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 + } +} diff --git a/docs/src/_data/helpers.js b/docs/src/_data/helpers.js index b4307e841ea7..0fd09876dd3a 100644 --- a/docs/src/_data/helpers.js +++ b/docs/src/_data/helpers.js @@ -1,36 +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(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(arr, pageUrl) { - const 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/links.json b/docs/src/_data/links.json index 7c3eb0208f48..b01eeb2747e6 100644 --- a/docs/src/_data/links.json +++ b/docs/src/_data/links.json @@ -1,21 +1,21 @@ { - "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" + "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/rules_categories.js b/docs/src/_data/rules_categories.js index ebd4883d0b65..88d04a79aa92 100644 --- a/docs/src/_data/rules_categories.js +++ b/docs/src/_data/rules_categories.js @@ -1,28 +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 1b517e170a14..6106ef998160 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -310,6 +310,11 @@ "fixable": "code" }, "class-methods-use-this": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "defaultOptions": [ { @@ -516,6 +521,11 @@ } }, "default-param-last": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Enforce default parameters to be last", @@ -3467,6 +3477,11 @@ } }, "no-useless-constructor": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Disallow unnecessary constructors", diff --git a/docs/src/_data/site.js b/docs/src/_data/site.js index 95cd46aac549..7f9802822716 100644 --- a/docs/src/_data/site.js +++ b/docs/src/_data/site.js @@ -17,12 +17,11 @@ const yaml = require("js-yaml"); // Exports //----------------------------------------------------------------------------- -module.exports = function(eleventy) { +module.exports = function (eleventy) { + const siteName = eleventy.site_name; + const siteDataFile = path.resolve(__dirname, `sites/${siteName}.yml`); - 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 8e330ee39738..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,15 +21,15 @@ hostname: eslint.org #------------------------------------------------------------------------------ google_analytics: - code: "G-7DGPHY308T" + code: "G-7DGPHY308T" #------------------------------------------------------------------------------ # Ads #------------------------------------------------------------------------------ carbon_ads: - serve: "" - placement: "" + serve: "" + placement: "" ethical_ads: true #------------------------------------------------------------------------------ @@ -36,90 +37,90 @@ ethical_ads: true #------------------------------------------------------------------------------ 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: Code Explorer - link: codeExplorer - target: _blank + - 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 - 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 + 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/ - start_with_latest: https://github.com/eslint/eslint/edit/latest/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 8f5587a221a7..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,104 +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: Code Explorer - link: codeExplorer - target: _blank + - 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: į¤žäē¤åĒ’äŊ“ - 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 į­–į•Ĩ + 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/ - start_with_latest: https://github.com/eslint/eslint/edit/latest/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 a0d98bd077af..58d47dc3ca7c 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -6,7 +6,7 @@ "path": "/docs/head/" }, { - "version": "9.22.0", + "version": "9.23.0", "branch": "latest", "path": "/docs/latest/" }, diff --git a/docs/src/_includes/layouts/doc.html b/docs/src/_includes/layouts/doc.html index b3f096e8fd15..2cb0e6d4751f 100644 --- a/docs/src/_includes/layouts/doc.html +++ b/docs/src/_includes/layouts/doc.html @@ -148,4 +148,34 @@

{{ title }}

+
+ + diff --git a/docs/src/_plugins/md-syntax-highlighter.js b/docs/src/_plugins/md-syntax-highlighter.js index c9e2322cbcb3..24d08b4f0afb 100644 --- a/docs/src/_plugins/md-syntax-highlighter.js +++ b/docs/src/_plugins/md-syntax-highlighter.js @@ -38,21 +38,21 @@ prismESLintHook.installPrismESLintMarkerHook(); * @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}
`; }; /** @@ -61,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 49328d8285fe..11cafb0ed4f1 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 index 68e731f864bb..b5faf7928190 100644 Binary files a/docs/src/assets/images/configure/config-inspector.png 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 088809eb1b75..ac7a3492cbcb 100644 --- a/docs/src/assets/js/search.js +++ b/docs/src/assets/js/search.js @@ -14,15 +14,17 @@ 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 poweredByLink = document.querySelector('.search_powered-by-wrapper'); +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; @@ -37,9 +39,11 @@ let caretPosition = 0; * @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); } /** @@ -49,11 +53,11 @@ function fetchSearchResults(query) { * @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; - } + resultsElement.innerHTML = ""; + if (removeEventListener && document.clickEventAdded) { + document.removeEventListener("click", handleDocumentClick); + document.clickEventAdded = false; + } } /** @@ -62,9 +66,9 @@ function clearSearchResults(removeEventListener = false) { * @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'); + resultsLiveRegion.innerHTML = "No results found."; + resultsElement.innerHTML = "No results found."; + resultsElement.setAttribute("data-results", "false"); } /** @@ -72,8 +76,8 @@ function showNoResults() { * @returns {void} - This function doesn't return anything. */ function clearNoResults() { - resultsLiveRegion.innerHTML = ""; - resultsElement.innerHTML = ""; + resultsLiveRegion.innerHTML = ""; + resultsElement.innerHTML = ""; } /** @@ -82,56 +86,53 @@ function clearNoResults() { * @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 { - showNoResults(); - } - + 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); + } } /** @@ -141,11 +142,11 @@ 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); + }; } /** @@ -156,24 +157,24 @@ function debounce(callback, delay) { * @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) }); +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); - } -} - +const handleDocumentClick = e => { + if (e.target !== resultsElement && e.target !== searchInput) { + clearSearchResults(true); + } +}; //----------------------------------------------------------------------------- // Event Handlers @@ -181,102 +182,113 @@ const handleDocumentClick = (e) => { // listen for input changes if (searchInput) - searchInput.addEventListener('keyup', function (e) { - const query = searchInput.value; + searchInput.addEventListener("keyup", function (e) { + const query = searchInput.value; - if (query === searchQuery) return; + if (query === searchQuery) return; - if (query.length) searchClearBtn.removeAttribute('hidden'); - else searchClearBtn.setAttribute('hidden', ''); + 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 (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); - } - }); - + 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(); - }); + poweredByLink.addEventListener("focus", function () { + clearSearchResults(); + }); } if (resultsElement) { - resultsElement.addEventListener('keydown', (e) => { - if (e.key !== "ArrowUp" && e.key !== "ArrowDown" && e.key !== "Tab" && e.key !== 'Shift') { - searchInput.focus(); - } - }); + 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); - } - } - } +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 92d5e0cd40af..d63c9d4b4b6b 100644 --- a/docs/src/assets/js/themes.js +++ b/docs/src/assets/js/themes.js @@ -1,69 +1,78 @@ (function () { - var enableToggle = function (btn) { - btn.setAttribute("aria-pressed", "true"); - }; + var enableToggle = function (btn) { + btn.setAttribute("aria-pressed", "true"); + }; - var disableToggle = function (btns) { - btns.forEach(btn => btn.setAttribute("aria-pressed", "false")); - }; + 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 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 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 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]; + 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]); - } - }); - }); + 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]); - } + 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'); - } - }); - }; + // 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); + document.addEventListener("DOMContentLoaded", initializeThemeSwitcher); })(); diff --git a/docs/src/assets/scss/ads.scss b/docs/src/assets/scss/ads.scss index 4b1b4e84e1b5..cc4ed68030c5 100644 --- a/docs/src/assets/scss/ads.scss +++ b/docs/src/assets/scss/ads.scss @@ -1,11 +1,11 @@ .hero-ad { - @media all and (max-width: 800px) { - display: none; - } + @media all and (max-width: 800px) { + display: none; + } } .docs-ad { - height: 290px; + height: 290px; } /* @@ -14,113 +14,113 @@ */ #carbonads * { - margin: initial; - padding: initial; + 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; - } + 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); + 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; + font-weight: 500; + color: inherit; + text-decoration: none; } #carbonads a:hover { - text-decoration: none; - color: var(--link-color); + text-decoration: none; + color: var(--link-color); } .jumbotron #carbonads a { - color: #eee; + color: #eee; } .jumbotron #carbonads a:hover { - color: #ccc; + color: #ccc; } #carbonads span { - display: block; - position: relative; - overflow: hidden; + display: block; + position: relative; + overflow: hidden; } #carbonads .carbon-wrap { - display: flex; - flex-direction: column; - max-width: 130px; + display: flex; + flex-direction: column; + max-width: 130px; } #carbonads .carbon-img img { - display: block; + display: block; } #carbonads .carbon-text { - margin-top: 10px; - line-height: 1rem; - font-size: .7em; - font-weight: 500; - text-align: left; + 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: .1ch; - text-transform: uppercase; + 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; - } + #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; + } } /* @@ -129,26 +129,26 @@ [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; + 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; + 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; + color: var(--body-text-color) !important; } .jumbotron [data-ea-publisher].loaded .ea-content a, .jumbotron [data-ea-type].loaded .ea-content a { - color: #eee; + color: #eee; } .jumbotron [data-ea-publisher].loaded .ea-content a:hover, .jumbotron [data-ea-type].loaded .ea-content a:hover { - color: #ccc; + color: #ccc; } diff --git a/docs/src/assets/scss/components/alert.scss b/docs/src/assets/scss/components/alert.scss index b71801a7beaf..1f61eeec3d68 100644 --- a/docs/src/assets/scss/components/alert.scss +++ b/docs/src/assets/scss/components/alert.scss @@ -1,89 +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(--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); - } + 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(--alert-warning-heading-color); - } - - .alert--important & { - color: var(--alert-important-heading-color); - } - - .alert--tip & { - color: var(--alert-tip-heading-color); - } + 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 index 3c514d2aee9b..351e6653dbe8 100644 --- a/docs/src/assets/scss/components/logo.scss +++ b/docs/src/assets/scss/components/logo.scss @@ -1,12 +1,12 @@ .logo-component { - fill: var(--logo-color); + fill: var(--logo-color); } .logo-title { - fill: var(--headings-color); + fill: var(--headings-color); } #logo-center { - opacity: var(--logo-center-opacity); - fill: var(--logo-center-color); + 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 c510dfeb5663..6754851337fc 100644 --- a/docs/src/assets/scss/components/rules.scss +++ b/docs/src/assets/scss/components/rules.scss @@ -1,203 +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; - 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%; - } + 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; - } + 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__content { - flex: 1 1 35ch; - overflow-x: auto; + flex: 1 1 35ch; + overflow-x: auto; } .rule__name_wrapper { - display: flex; - align-items: center; - gap: 0.5rem; + display: flex; + align-items: center; + gap: 0.5rem; - .frozen { - font-size: .875rem; - } + .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(--rule-status-background-color); - 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; + 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 20871924b337..9f942657e70f 100644 --- a/docs/src/assets/scss/components/search.scss +++ b/docs/src/assets/scss/components/search.scss @@ -1,184 +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: 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%; - } + 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-right: 2.5rem; - padding-inline-end: 2.5rem; + 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: .75rem; - 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; + 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); - } - } + 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: 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; - } + 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 86ba6a27f6ff..24048d5ce7b9 100644 --- a/docs/src/assets/scss/components/social-icons.scss +++ b/docs/src/assets/scss/components/social-icons.scss @@ -1,26 +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; - } - } + @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 79c2db5f3f11..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 .675rem; - 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 5d07a6de03a8..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,231 +9,231 @@ h3:target, h4:target, h5:target, h6:target { - background-color: var(--lighter-background-color); - border-radius: var(--border-radius); + 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, @@ -243,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, @@ -359,12 +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; - padding: 0.25rem 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, @@ -372,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 478b7ef1de12..7b4b4b43c5c9 100644 --- a/docs/src/assets/scss/styles.scss +++ b/docs/src/assets/scss/styles.scss @@ -1,37 +1,37 @@ -@import "tokens/themes"; -@import "tokens/spacing"; -@import "tokens/typography"; -@import "tokens/ui"; -@import "tokens/opacity"; +@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"; -@import "components/logo"; +@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 "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 1e4c4348e9a3..868a96fcc722 100644 --- a/docs/src/assets/scss/syntax-highlighter.scss +++ b/docs/src/assets/scss/syntax-highlighter.scss @@ -1,66 +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(--code-text-color); + 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); - } + &.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: var(--code-comments-color); + color: var(--code-comments-color); } .token.namespace { - opacity: 0.7; + opacity: 0.7; } .token.selector, @@ -69,92 +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; - - /* + /* 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; + 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; + /* 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 { - /* +.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; + padding-right: 0; } .token.eslint-marked-on-zero-width { - position: relative; + position: relative; - /* Delete the wavy line. */ - background: none; + /* 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; + 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 index c1f4ad9f488d..2a6fb240ed6f 100644 --- a/docs/src/assets/scss/tokens/opacity.scss +++ b/docs/src/assets/scss/tokens/opacity.scss @@ -1,18 +1,18 @@ :root { - --opacity-100: 1; - --opacity-60: 0.6; + --opacity-100: 1; + --opacity-60: 0.6; } @media (prefers-color-scheme: "dark") { - :root { - --logo-center-opacity: var(--opacity-60); - } + :root { + --logo-center-opacity: var(--opacity-60); + } } html[data-theme="light"] { - --logo-center-opacity: var(--opacity-100); + --logo-center-opacity: var(--opacity-100); } html[data-theme="dark"] { - --logo-center-opacity: var(--opacity-60); + --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 a48d2d742b56..6fb58f6bb43a 100644 --- a/docs/src/assets/scss/tokens/themes.scss +++ b/docs/src/assets/scss/tokens/themes.scss @@ -1,240 +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); - --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); + /* 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); - --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); - } + :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); - --code-comments-color: var(--color-neutral-500); - --headings-color: var(--color-neutral-900); + --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); + --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); + --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; + --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); + --code-text-color: var(--color-neutral-900); - --logo-color: var(--color-primary-800); - --logo-center-color: var(--color-primary-600); + --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-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-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); + --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); + --rule-status-background-color: var(--color-rose-50); } html[data-theme="dark"] { - color-scheme: dark; + 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; + --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); + --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); + --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); + --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); + --code-text-color: var(--color-neutral-100); - --logo-color: #fff; - --logo-center-color: #fff; + --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-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-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); + --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); + --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 152bb195b53a..923e3ae14f13 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 3422792738b8..d3efa4b49fe6 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. @@ -42,22 +42,22 @@ Here is the basic format of the source file for a rule: /** @type {import('../shared/types').Rule} */ 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,9 +105,9 @@ 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 @@ -115,8 +115,8 @@ When rules are feature complete, they are marked as frozen (indicated with ❄ 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. -* **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. +- **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. +- **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 ef1190c89a14..3baf835cbe86 100644 --- a/docs/src/contribute/development-environment.md +++ b/docs/src/contribute/development-environment.md @@ -45,7 +45,7 @@ You must be connected to the Internet for this step to work. You'll see a lot of ## 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: @@ -91,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 diff --git a/docs/src/contribute/governance.md b/docs/src/contribute/governance.md index 694e5eb4f50c..de22605f05b4 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,25 +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 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. +- 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. @@ -60,25 +59,25 @@ Committers are community members who have shown that they are committed to the c Committers: -* 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. +- 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. @@ -92,16 +91,16 @@ Reviewers are community members who have contributed a significant amount of tim 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 invoice for the hours they spend working on ESLint at a rate of $80 USD per hour. +- 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. @@ -111,10 +110,10 @@ The ESLint project is jointly governed by a Technical Steering Committee (TSC) w 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,10 +129,10 @@ 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. @@ -185,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/pull-requests.md b/docs/src/contribute/pull-requests.md index 5962c483c1e8..169a2a299035 100644 --- a/docs/src/contribute/pull-requests.md +++ b/docs/src/contribute/pull-requests.md @@ -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 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..dd579f1764b0 100644 --- a/docs/src/contribute/work-on-issue.md +++ b/docs/src/contribute/work-on-issue.md @@ -29,15 +29,15 @@ We use labels to indicate the status of issues. The most complete documentation 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: -* "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) +- "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 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. If you find you can't finish the work, then simply 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 1ce5ea3c0a91..fc08ea382d7f 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(); ``` @@ -35,14 +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. -* `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` @@ -52,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 @@ -63,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` @@ -174,7 +171,7 @@ For Example 1: ```js while (a) { - a = foo(); + a = foo(); } bar(); ``` @@ -182,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. @@ -190,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(); ``` @@ -213,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`. @@ -221,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. @@ -229,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. @@ -237,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 @@ -254,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); + }, + }; + }, }; ``` @@ -312,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 }); + } + }, + }; + }, }; ``` @@ -393,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; + }); + } + }, + }; + }, }; ``` @@ -524,9 +512,9 @@ console.log("Hello world!"); ```js if (a) { - foo(); + foo(); } else { - bar(); + bar(); } ``` @@ -538,11 +526,11 @@ if (a) { ```js if (a) { - foo(); + foo(); } else if (b) { - bar(); + bar(); } else if (c) { - hoge(); + hoge(); } ``` @@ -554,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; } ``` @@ -577,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; } ``` @@ -604,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) @@ -629,10 +617,10 @@ It creates the paths from `try` block to `catch` block at: ```js try { - foo(); - bar(); + foo(); + bar(); } finally { - fuga(); + fuga(); } last(); ``` @@ -649,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(); ``` @@ -667,11 +655,11 @@ last(); ```js while (a) { - foo(); - if (b) { - continue; - } - bar(); + foo(); + if (b) { + continue; + } + bar(); } ``` @@ -683,8 +671,8 @@ while (a) { ```js do { - foo(); - bar(); + foo(); + bar(); } while (a); ``` @@ -696,11 +684,11 @@ do { ```js for (let i = 0; i < 10; ++i) { - foo(); - if (b) { - break; - } - bar(); + foo(); + if (b) { + break; + } + bar(); } ``` @@ -712,7 +700,7 @@ for (let i = 0; i < 10; ++i) { ```js for (;;) { - foo(); + foo(); } bar(); ``` @@ -725,7 +713,7 @@ bar(); ```js for (let key in obj) { - foo(key); + foo(key); } ``` @@ -737,10 +725,10 @@ for (let key in obj) { ```js function foo(a) { - if (a) { - return; - } - bar(); + if (a) { + return; + } + bar(); } foo(false); @@ -748,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 827117ac4164..8acc68daa10c 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,34 +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. -* **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. +- **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. 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. +- **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: @@ -163,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"; + } }; ``` @@ -267,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). @@ -282,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 ""; }; ``` @@ -324,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 fda2fa4276d5..9a2134b3c61e 100644 --- a/docs/src/extend/custom-parsers.md +++ b/docs/src/extend/custom-parsers.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: extend eslint title: Custom Parsers order: 5 - --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} @@ -27,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 }; ``` @@ -45,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/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. +- `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 @@ -59,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", + }, }; ``` @@ -76,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. @@ -87,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. @@ -104,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 @@ -132,12 +131,14 @@ Then add the custom parser to your ESLint configuration file with the `languageO const myparser = require("eslint-parser-myparser"); -module.exports = [{ - languageOptions: { - parser: myparser - }, - // ... rest of configuration -}]; +module.exports = [ + { + languageOptions: { + parser: myparser, + }, + // ... rest of configuration + }, +]; ``` When using legacy configuration, specify the `parser` property as a string: @@ -146,8 +147,8 @@ When using legacy configuration, specify the `parser` property as a string: // .eslintrc.js module.exports = { - parser: "eslint-parser-myparser", - // ... rest of configuration + parser: "eslint-parser-myparser", + // ... rest of configuration }; ``` @@ -163,17 +164,17 @@ 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 }; ``` @@ -182,11 +183,13 @@ Include the custom parser in an ESLint configuration file: ```js // eslint.config.js -module.exports = [{ - languageOptions: { - parser: require("./path/to/awesome-custom-parser") - } -}]; +module.exports = [ + { + languageOptions: { + parser: require("./path/to/awesome-custom-parser"), + }, + }, +]; ``` Or if using legacy configuration: diff --git a/docs/src/extend/custom-processors-deprecated.md b/docs/src/extend/custom-processors-deprecated.md index 572e30e9f0ed..1de004e55bd8 100644 --- a/docs/src/extend/custom-processors-deprecated.md +++ b/docs/src/extend/custom-processors-deprecated.md @@ -14,36 +14,37 @@ In order to create a custom processor, the object exported from your module has ```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) - } - } + 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) + }, + }, }; ``` @@ -59,49 +60,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: @@ -133,10 +132,10 @@ For example: ```yml plugins: - - a-plugin + - a-plugin overrides: - - files: "*.md" - processor: a-plugin/markdown + - files: "*.md" + processor: a-plugin/markdown ``` See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. @@ -174,14 +173,16 @@ module.exports = { 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 */ }; +const myCustomProcessor = { + /* processor methods */ +}; module.exports = { - // The same custom processor is applied to both - // `.md` and `.mdx` files. - processors: { - ".md": myCustomProcessor, - ".mdx": myCustomProcessor - } -} + // 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 715873a575e9..3c65dcfe9ea1 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -5,7 +5,6 @@ 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/markdown](https://www.npmjs.com/package/@eslint/markdown) includes a custom processor for this). @@ -20,41 +19,41 @@ In order to create a custom processor, the object exported from your module has ```js 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) - } - } + 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 @@ -76,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: @@ -152,17 +149,19 @@ Example: ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - processor: "example/processor-name" - }, - // ... other configs -]; +export default defineConfig([ + { + files: ["**/*.txt"], // apply processor to text files + plugins: { + example, + }, + processor: "example/processor-name", + }, + // ... other configs +]); ``` In this example, the processor name is `"example/processor-name"`, and that's the value that will be used for serializing configurations. @@ -175,14 +174,16 @@ Example: ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - processor: example.processors["processor-name"] - }, - // ... other configs -]; +export default defineConfig([ + { + files: ["**/*.txt"], + processor: example.processors["processor-name"], + }, + // ... other configs +]); ``` 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. @@ -197,16 +198,18 @@ In order to use a processor from a plugin in a configuration file, import the pl ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - processor: "example/processor-name" - } -]; +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 cd5eb540e37c..f91ed15dc9b6 100644 --- a/docs/src/extend/custom-rule-tutorial.md +++ b/docs/src/extend/custom-rule-tutorial.md @@ -16,8 +16,8 @@ You can create custom rules to validate if your code meets a certain expectation 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? @@ -29,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. @@ -73,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) + }; + }, }; ``` @@ -94,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) + }; + }, }; ``` @@ -221,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. - languageOptions: { 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!"); @@ -291,8 +297,8 @@ 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), `eslint.config.js`: @@ -310,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. @@ -339,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! } ``` @@ -472,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 8e94b6dd206b..33770091b86b 100644 --- a/docs/src/extend/custom-rules-deprecated.md +++ b/docs/src/extend/custom-rules-deprecated.md @@ -1,6 +1,5 @@ --- title: Working with Rules (Deprecated) - --- As of ESLint v9.0.0, the function-style rule format that was current in ESLint <= 2.13.1 is no longer supported. diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 1bae4eebd1c8..4a04b4afb0cf 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -5,7 +5,6 @@ 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. @@ -16,19 +15,19 @@ Here's the basic format of a custom rule: // 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 + }; + }, }; ``` @@ -42,40 +41,40 @@ The source file for a rule exports an object with the following properties. Both `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`) 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. - * `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 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. -* `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 | false`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). Mandatory when the rule has options. -* `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. +- `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 | 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) +- `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`, **Deprecated** Use `meta.deprecated.replacedBy` instead.) 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. @@ -132,29 +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#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)). +- `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: -* `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)). +- `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. @@ -162,18 +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: -* `messageId`: (`string`) The ID of the message (see [messageIds](#messageids)) (recommended over `message`). -* `message`: (`string`) The problem message (alternative to `messageId`). -* `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`) 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. Note that at least one of `node` or `loc` is required. @@ -181,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", }); ``` @@ -212,9 +211,9 @@ The node contains all the information necessary to figure out the line and colum `messageId`s are the recommended approach to reporting messages in `context.report()` calls because of the following benefits: -* 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 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: @@ -266,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", + }, + ], + }, + ], }); ``` @@ -286,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, ";"); + }, }); ``` @@ -300,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. @@ -326,21 +325,22 @@ 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. - - ```js - ({ foo : 1 }) - // should get fixed to either + - 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. - ({ 'foo': 1 }) + ```js + ({ foo: 1 })( + // should get fixed to either - // or + { 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. @@ -348,15 +348,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, ""); + }, }); ``` @@ -492,7 +492,7 @@ Some rules require options in order to function correctly. These options appear ```json { - "quotes": ["error", "double"] + "quotes": ["error", "double"] } ``` @@ -500,18 +500,18 @@ The `quotes` rule in this example has one option, `"double"` (the `error` is the ```js module.exports = { - meta: { - schema: [ - { - enum: ["single", "double", "backtick"] - } - ] - }, - create: function(context) { - var isDouble = (context.options[0] === "double"); - - // ... - } + meta: { + schema: [ + { + enum: ["single", "double", "backtick"], + }, + ], + }, + create: function (context) { + var isDouble = context.options[0] === "double"; + + // ... + }, }; ``` @@ -527,11 +527,11 @@ The `SourceCode` object is the main object for getting more information about th ```js module.exports = { - create: function(context) { - var sourceCode = context.sourceCode; + create: function (context) { + var sourceCode = context.sourceCode; - // ... - } + // ... + }, }; ``` @@ -539,70 +539,70 @@ 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. -* `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`. +- `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. +- `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. @@ -611,7 +611,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(); @@ -657,18 +656,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`: @@ -682,20 +681,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, + }, + ], + }, }; ``` @@ -711,25 +710,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, + }, + ], + }, + }, }; ``` @@ -749,36 +748,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. @@ -789,18 +788,18 @@ 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). @@ -816,35 +815,41 @@ 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 { /* ... */ }; - } -} + 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 +- 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`. @@ -854,7 +859,7 @@ 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 @@ -869,21 +874,21 @@ You can view scope information for any JavaScript code using [Code Explorer](htt 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;;) {}`).
@@ -899,15 +904,15 @@ 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 @@ -917,21 +922,20 @@ To help with this, you can use the `sourceCode.markVariableAsUsed()` method. Thi ```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"); + }, + }; + // ... + }, }; ``` @@ -945,10 +949,10 @@ ESLint analyzes code paths while traversing AST. You can access code path object Please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: -* `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 diff --git a/docs/src/extend/index.md b/docs/src/extend/index.md index 815a42b457bd..7109d4715af4 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 index 2b993d575b4f..fc2abe6bd446 100644 --- a/docs/src/extend/languages.md +++ b/docs/src/extend/languages.md @@ -5,7 +5,6 @@ eleventyNavigation: 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. @@ -26,12 +25,14 @@ While there is no specific structure an AST or CST must follow, it's easier to i 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 + + - 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 @@ -40,27 +41,27 @@ ESLint holds information about source code in a `SourceCode` object. This object 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`. +- `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. +- `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. +- `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. @@ -74,20 +75,20 @@ The `Language` object contains all of the information about the programming lang 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()`. +- `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. +- `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. See [`JSONLanguage`](https://github.com/eslint/json/blob/main/src/languages/json-language.js) as an example of a basic `Language` class. @@ -99,18 +100,17 @@ Languages are published in plugins similar to processors and rules. Define the ` 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 - } + // preferred location of name and version + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + languages: { + my: myLanguage, + }, + rules: { + // add rules here + }, }; // for ESM @@ -124,16 +124,18 @@ In order to use a language from a plugin in a configuration file, import the plu ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - language: "example/my" - } -]; +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 742c0999d82a..1113d15a4723 100644 --- a/docs/src/extend/plugin-migration-flat-config.md +++ b/docs/src/extend/plugin-migration-flat-config.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: create plugins title: Migration to Flat Config 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 @@ -69,16 +68,15 @@ No other changes are necessary for the `processors` key in your plugin as long a ```js const plugin = { - configs: {}, - rules: {}, - processors: { - - // no longer supported - ".md": { - preprocess() {}, - postprocess() {} - } - } + configs: {}, + rules: {}, + processors: { + // no longer supported + ".md": { + preprocess() {}, + postprocess() {}, + }, + }, }; // for ESM @@ -92,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 @@ -114,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. @@ -157,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; @@ -185,20 +184,22 @@ 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 recommended config - example.configs.recommended, - - // and provide your own overrides - { - rules: { - "example/rule1": "warn" - } - } -]; +export default defineConfig([ + // use recommended config and provide your own overrides + { + files: ["**/*.js"], + plugins: { + example, + }, + extends: ["example/recommended"], + rules: { + "example/rule1": "warn", + }, + }, +]); ``` If your config extends other configs, you can export an array: @@ -207,35 +208,22 @@ If your config extends other configs, you can export an array: const baseConfig = require("./base-config"); module.exports = { - configs: { - extendedConfig: [ - baseConfig, - { - rules: { - "example/rule1": "error", - "example/rule2": "error" - } - } - ], - }, + 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. -If your exported config is an object, then your users can insert it directly into the config array; if your exported config is an array, then your users should use the spread operator (`...`) to insert the array's items into the config array. - -Here's an example with both an object config and an array config: - -```js -import example from "eslint-plugin-example"; - -export default [ - example.configs.recommended, // Object, so don't spread - ...example.configs.extendedConfig, // Array, so needs spreading -]; -``` - For more information, see the [full documentation](https://eslint.org/docs/latest/extend/plugins#configs-in-plugins). ## Migrating Environments for Flat Config @@ -266,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; @@ -295,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. @@ -325,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 65d5426d776b..54d9c3597074 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: extend eslint title: Create Plugins order: 2 - --- 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. @@ -14,19 +13,19 @@ ESLint plugins extend ESLint with additional functionality. In most cases, you'l A plugin is a JavaScript object that exposes certain properties to ESLint: -* `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. +- `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. 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 const plugin = { - meta: {}, - configs: {}, - rules: {}, - processors: {} + meta: {}, + configs: {}, + rules: {}, + processors: {}, }; // for ESM @@ -44,15 +43,14 @@ For easier debugging and more effective caching of plugins, it's recommended to ```js const plugin = { - - // preferred location of name and version - meta: { - name: "eslint-plugin-example", - version: "1.2.3" - }, - rules: { - // add rules here - } + // preferred location of name and version + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + rules: { + // add rules here + }, }; // for ESM @@ -67,18 +65,19 @@ The `meta.name` property should match the npm package name for your plugin and t ```js import fs from "fs"; -const pkg = JSON.parse(fs.readFileSync(new URL("./package.json", import.meta.url), "utf8")); +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 - }, - rules: { - // add rules here - } + // preferred location of name and version + meta: { + name: pkg.name, + version: pkg.version, + }, + rules: { + // add rules here + }, }; export default plugin; @@ -87,21 +86,20 @@ 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`. -::: +- **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`. + ::: As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: ```js const plugin = { - - // alternate location of name and version - name: "eslint-plugin-example", - version: "1.2.3", - rules: { - // add rules here - } + // alternate location of name and version + name: "eslint-plugin-example", + version: "1.2.3", + rules: { + // add rules here + }, }; // for ESM @@ -121,17 +119,17 @@ Plugins can expose custom rules for use in ESLint. To do so, the plugin must exp ```js const plugin = { - meta: { - name: "eslint-plugin-example", - version: "1.2.3" - }, - rules: { - "dollar-sign": { - create(context) { - // rule implementation ... - } - } - } + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + rules: { + "dollar-sign": { + create(context) { + // rule implementation ... + }, + }, + }, }; // for ESM @@ -145,18 +143,19 @@ In order to use a rule from a plugin in a configuration file, import the plugin ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - rules: { - "example/dollar-sign": "error" - } - } -]; +export default defineConfig([ + { + plugins: { + example, + }, + rules: { + "example/dollar-sign": "error", + }, + }, +]); ``` ::: warning @@ -169,16 +168,20 @@ Plugins can expose [processors](custom-processors) for use in configuration file ```js const plugin = { - meta: { - name: "eslint-plugin-example", - version: "1.2.3" - }, - processors: { - "processor-name": { - preprocess(text, filename) {/* ... */}, - postprocess(messages, filename) { /* ... */ }, - } - } + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + processors: { + "processor-name": { + preprocess(text, filename) { + /* ... */ + }, + postprocess(messages, filename) { + /* ... */ + }, + }, + }, }; // for ESM @@ -192,16 +195,18 @@ In order to use a processor from a plugin in a configuration file, import the pl ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - processor: "example/processor-name" - } -]; +export default defineConfig([ + { + files: ["**/*.txt"], + plugins: { + example, + }, + processor: "example/processor-name", + }, +]); ``` ### Configs in Plugins @@ -212,40 +217,42 @@ You can include individual rules from a plugin in a config that's also included ```js const plugin = { - meta: { - name: "eslint-plugin-example", - version: "1.2.3" - }, - configs: {}, - rules: { - "dollar-sign": { - create(context) { - // rule implementation ... - } - } - } + 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 - } - } - } - }] + recommended: [ + { + plugins: { + example: plugin, + }, + rules: { + "example/dollar-sign": "error", + }, + languageOptions: { + globals: { + myGlobal: "readonly", + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + ], }); // for ESM @@ -257,21 +264,100 @@ module.exports = plugin; 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. -In order to use a config from a plugin in a configuration file, import the plugin and access the config directly through the plugin object. Assuming the config is an array, use the spread operator to add it into the array returned from the configuration file, like this: +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 // eslint.config.js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - ...example.configs.recommended -]; +export default defineConfig([ + { + files: ["**/*.js"], // any patterns you want to apply the config to + plugins: { + example, + }, + extends: ["example/recommended"], + }, +]); ``` ::: important Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. ::: +#### Backwards Compatibility for Legacy Configs + +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. + +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: + +```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; +``` + +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. @@ -280,9 +366,9 @@ ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility t 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 @@ -292,9 +378,9 @@ In order to make your plugin available publicly, you have to publish it on npm. ```json { - "peerDependencies": { - "eslint": ">=9.0.0" - } + "peerDependencies": { + "eslint": ">=9.0.0" + } } ``` diff --git a/docs/src/extend/rule-deprecation.md b/docs/src/extend/rule-deprecation.md index 4a64fdbb4b5f..ae49166335ff 100644 --- a/docs/src/extend/rule-deprecation.md +++ b/docs/src/extend/rule-deprecation.md @@ -1,7 +1,7 @@ --- 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: true` and `replacedBy`. In the new format `deprecated` is an object of type `DeprecatedInfo` and `replacedBy` should be defined inside `deprecated` instead of the top-level. @@ -11,42 +11,42 @@ In the new format `deprecated` is an object of type `DeprecatedInfo` and `replac 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. +- `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. +- `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.. +- `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 65a9afda486b..7d09d5184a03 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/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,60 +78,60 @@ 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. +- **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. ### Deprecated members @@ -140,57 +139,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 @@ -200,28 +199,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. +- **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 @@ -229,15 +228,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 @@ -247,60 +246,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 @@ -308,26 +307,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 @@ -337,42 +336,42 @@ 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` | +| 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` | +| `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` | +| `"Variable"` | `VariableDeclarator` | #### parent -* **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` | +- **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` | ### Deprecated members @@ -381,10 +380,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..a1079010b83b 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 index 9e73202bba69..ac602aafb858 100644 --- a/docs/src/extend/shareable-configs-deprecated.md +++ b/docs/src/extend/shareable-configs-deprecated.md @@ -16,23 +16,21 @@ Shareable configs are simply npm packages that export a configuration object. To 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`. +- 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, + }, - globals: { - MyGlobal: true - }, - - rules: { - semi: [2, "always"] - } - + rules: { + semi: [2, "always"], + }, }; ``` @@ -46,9 +44,9 @@ You should declare your dependency on ESLint in the `package.json` using the [pe ```json { - "peerDependencies": { - "eslint": ">= 3" - } + "peerDependencies": { + "eslint": ">= 3" + } } ``` @@ -74,7 +72,7 @@ To use a shareable config, include the config name in the `extends` field of a c ```json { - "extends": "eslint-config-myconfig" + "extends": "eslint-config-myconfig" } ``` @@ -82,7 +80,7 @@ You can also omit the `eslint-config-` and it is automatically assumed by ESLint ```json { - "extends": "myconfig" + "extends": "myconfig" } ``` @@ -96,7 +94,7 @@ You can use the module name: ```json { - "extends": "@scope/eslint-config" + "extends": "@scope/eslint-config" } ``` @@ -104,7 +102,7 @@ You can also omit the `eslint-config` and it is automatically assumed by ESLint: ```json { - "extends": "@scope" + "extends": "@scope" } ``` @@ -112,7 +110,7 @@ The module name can also be customized. For example, if you have a package named ```json { - "extends": "@scope/eslint-config-myconfig" + "extends": "@scope/eslint-config-myconfig" } ``` @@ -120,7 +118,7 @@ You could also omit `eslint-config` to specify the configuration as: ```json { - "extends": "@scope/myconfig" + "extends": "@scope/myconfig" } ``` @@ -137,9 +135,9 @@ As an example, you can create a file called `my-special-config.js` in the root o ```js // my-special-config.js module.exports = { - rules: { - quotes: [2, "double"] - } + rules: { + quotes: [2, "double"], + }, }; ``` @@ -147,7 +145,7 @@ Then, assuming you're using the package name `eslint-config-myconfig`, you can a ```json { - "extends": "myconfig/my-special-config" + "extends": "myconfig/my-special-config" } ``` @@ -155,7 +153,7 @@ When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possibl ```json { - "extends": "@scope/eslint-config/my-special-config" + "extends": "@scope/eslint-config/my-special-config" } ``` @@ -185,33 +183,33 @@ myconfig In the `index.js` file, you can do something like this: ```js -module.exports = require('./lib/ci.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 - } + rules: { + "no-console": 1, + }, }; ``` Inside `/lib/ci.js` you have: ```js -module.exports = require('./ci/backend'); +module.exports = require("./ci/backend"); ``` Inside `/lib/ci/common.js`: ```js module.exports = { - rules: { - 'no-alert': 2 - }, - extends: 'myconfig/lib/defaults' + rules: { + "no-alert": 2, + }, + extends: "myconfig/lib/defaults", }; ``` @@ -221,10 +219,10 @@ Now inside `/lib/ci/backend.js`: ```js module.exports = { - rules: { - 'no-console': 1 - }, - extends: 'myconfig/lib/ci/common' + rules: { + "no-console": 1, + }, + extends: "myconfig/lib/ci/common", }; ``` @@ -232,4 +230,4 @@ In the last file, once again see that to properly resolve your config, you need ## 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/shareable-configs.md b/docs/src/extend/shareable-configs.md index 8076ce2be4aa..9ba3444adfc0 100644 --- a/docs/src/extend/shareable-configs.md +++ b/docs/src/extend/shareable-configs.md @@ -5,7 +5,6 @@ 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. @@ -22,26 +21,25 @@ Shareable configs are simply npm packages that export a configuration object or 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`. -* 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`. +- 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 export default [ - { - languageOptions: { - globals: { - MyGlobal: true - } - }, - - rules: { - semi: [2, "always"] - } - - } + { + languageOptions: { + globals: { + MyGlobal: true, + }, + }, + + rules: { + semi: [2, "always"], + }, + }, ]; ``` @@ -59,9 +57,9 @@ You should declare your dependency on ESLint in the `package.json` using the [pe ```json { - "peerDependencies": { - "eslint": ">= 9" - } + "peerDependencies": { + "eslint": ">= 9" + } } ``` @@ -69,15 +67,19 @@ If your shareable config depends on a plugin or a custom parser, you should spec ## Using a Shareable Config -To use a shareable config, import the package inside of an `eslint.config.js` file and add it into the exported array, like this: +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: ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import myconfig from "eslint-config-myconfig"; -export default [ - ...myconfig -]; +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig], + }, +]); ``` ::: warning @@ -90,18 +92,20 @@ You can override settings from the shareable config by adding them directly into ```js // eslint.config.js +import { defineConfig } from "eslint/config"; import myconfig from "eslint-config-myconfig"; -export default [ - ...myconfig, - - // anything from here will override myconfig - { - rules: { - "no-unused-vars": "warn" - } - } -]; +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig], + + // anything from here will override myconfig + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` ## Sharing Multiple Configs @@ -113,9 +117,9 @@ As an example, you can create a file called `my-special-config.js` in the root o ```js // my-special-config.js export default { - rules: { - quotes: [2, "double"] - } + rules: { + quotes: [2, "double"], + }, }; ``` @@ -123,20 +127,21 @@ Then, assuming you're using the package name `eslint-config-myconfig`, you can a ```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"; -export default [ - ...myconfig, - mySpecialConfig, - - // anything from here will override myconfig and mySpecialConfig - { - rules: { - "no-unused-vars": "warn" - } - } -]; +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig, mySpecialConfig], + + // anything from here will override myconfig + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` ::: important @@ -145,4 +150,4 @@ We strongly recommend always including a default export for your package to avoi ## 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 index 1f5541d606f3..b46027a6be4d 100644 --- a/docs/src/extend/stats.md +++ b/docs/src/extend/stats.md @@ -9,7 +9,7 @@ eleventyNavigation: {%- 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. +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 @@ -26,18 +26,18 @@ As such, it is not available via stdout but made easily ingestible via a formatt 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. +- `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 @@ -47,7 +47,7 @@ Let's consider the following example: /*eslint no-regex-spaces: "error", wrap-regex: "error"*/ function a() { - return / foo/.test("bar"); + return / foo/.test("bar"); } ``` @@ -62,45 +62,45 @@ This yields the following `stats` entry as part of the formatted lint results ob ```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 + "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 } ``` @@ -123,20 +123,20 @@ You can achieve the same thing using the Node.js API by passing`stats: true` as const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance. - const eslint = new ESLint({ stats: true, fix: true }); + // 1. Create an instance. + const eslint = new ESLint({ stats: true, fix: true }); - // 2. Lint files. - const results = await eslint.lintFiles(["file-to-fix.js"]); + // 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); + // 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); + // 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 2684d340974a..9ec28e1513a3 100644 --- a/docs/src/extend/ways-to-extend.md +++ b/docs/src/extend/ways-to-extend.md @@ -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). diff --git a/docs/src/integrate/index.md b/docs/src/integrate/index.md index 19164a585308..045c4e65d1c7 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 b4115bda1279..fa1957f839c3 100644 --- a/docs/src/integrate/integration-tutorial.md +++ b/docs/src/integrate/integration-tutorial.md @@ -18,28 +18,28 @@ projects. 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 @@ -52,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 (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) -* npm -* A text editor +- Node.js (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) +- npm +- A text editor ## Step 1: Setup @@ -101,11 +101,11 @@ const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function function createESLintInstance(overrideConfig) { - return new ESLint({ - overrideConfigFile: true, - overrideConfig, - fix: true, - }); + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } ``` @@ -127,12 +127,12 @@ files. // 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; } ``` @@ -152,19 +152,19 @@ 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; } ``` @@ -178,22 +178,22 @@ function will be the main entry point for your integration: // 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 = { - 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); + // 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 @@ -207,58 +207,58 @@ const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function function createESLintInstance(overrideConfig) { - return new ESLint({ - overrideConfigFile: true, - overrideConfig, - fix: true, - }); + 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 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); + // 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 diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 79b7e4111122..72760bcc050a 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -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,24 +47,24 @@ 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); }); ``` @@ -81,29 +81,29 @@ const testCode = ` `; (async function main() { - // 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); + // 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); }); ``` @@ -121,58 +121,58 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob ##### File Enumeration -* `options.cwd` (`string`)
- Default is `process.cwd()`. The working directory. This must be an absolute path. -* `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.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 `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. +- `options.cwd` (`string`)
+ Default is `process.cwd()`. The working directory. This must be an absolute path. +- `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.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 `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`)
- 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 | ConfigData[] | 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 | ConfigData[] | 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.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)). +- `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 | ConfigData[] | 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 | ConfigData[] | 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.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`)
- 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`)
- Default is `null`. The types of the rules that the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods use for autofix. +- `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`)
+ 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`)
- 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`)
- Default is `.eslintcache`. The [`eslint.lintFiles()`][eslint-lintfiles] method writes caches into this file. -* `options.cacheStrategy` (`string`)
- Default is `"metadata"`. Strategy for the cache to use for detecting changed files. Can be either `"metadata"` or `"content"`. +- `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`)
+ Default is `.eslintcache`. The [`eslint.lintFiles()`][eslint-lintfiles] method writes caches into this file. +- `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. +- `options.flags` (`string[]`)
+ Default is `[]`. The feature flags to enable for this instance. ### ◆ eslint.lintFiles(patterns) @@ -184,13 +184,13 @@ This method lints the files that match the glob patterns and then returns the re #### Parameters -* `patterns` (`string | string[]`)
- The lint target files. This can contain any of file paths, directory paths, and glob patterns. +- `patterns` (`string | string[]`)
+ The lint target files. This can contain any of file paths, directory paths, and glob patterns. #### Return Value -* (`Promise`)
- The promise that will be fulfilled with an array of [LintResult] objects. +- (`Promise`)
+ The promise that will be fulfilled with an array of [LintResult] objects. ### ◆ eslint.lintText(code, options) @@ -208,17 +208,17 @@ If the `options.filePath` value is configured to be ignored, this method returns The second parameter `options` is omittable. -* `code` (`string`)
- The source code text to check. -* `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, 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. +- `code` (`string`)
+ The source code text to check. +- `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, 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`)
- 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. +- (`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) @@ -231,13 +231,13 @@ This method returns an object containing meta information for each rule that tri #### Parameters -* `results` (`LintResult[]`)
- An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`. +- `results` (`LintResult[]`)
+ An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`. #### Return Value -* (`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). +- (`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,13 +249,13 @@ This method calculates the configuration for a given file, which can be useful f #### Parameters -* `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. +- `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`)
- The promise that will be fulfilled with a configuration object. +- (`Promise`)
+ The promise that will be fulfilled with a configuration object. ### ◆ eslint.isPathIgnored(filePath) @@ -267,13 +267,13 @@ This method checks if a given file is ignored by your configuration. #### Parameters -* `filePath` (`string`)
- The path to the file you want to check. +- `filePath` (`string`)
+ The path to the file you want to check. #### Return Value -* (`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`. +- (`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) @@ -285,20 +285,20 @@ This method loads a formatter. Formatters convert lint results to a human- or ma #### Parameters -* `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 `./`. +- `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 `./`. #### Return Value -* (`Promise`)
- The promise that will be fulfilled with a [LoadedFormatter] object. +- (`Promise`)
+ The promise that will be fulfilled with a [LoadedFormatter] object. ### ◆ eslint.hasFlag(flagName) @@ -306,19 +306,19 @@ This method is used to determine if a given feature flag is set, as in this exam ```js if (eslint.hasFlag("x_feature")) { - // handle flag + // handle flag } ``` #### Parameters -* `flagName` (`string`)
- The flag to check. +- `flagName` (`string`)
+ The flag to check. #### Return Value -* (`boolean`)
- True if the flag is enabled. +- (`boolean`)
+ True if the flag is enabled. ### ◆ ESLint.version @@ -352,13 +352,13 @@ This is a static method. #### Parameters -* `results` (`LintResult[]`)
- The [LintResult] objects to write. +- `results` (`LintResult[]`)
+ The [LintResult] objects to write. #### Return Value -* (`Promise`)
- The promise that will be fulfilled after all files are written. +- (`Promise`)
+ The promise that will be fulfilled after all files are written. ### ◆ ESLint.getErrorResults(results) @@ -372,108 +372,108 @@ This is a static method. #### Parameters -* `results` (`LintResult[]`)
- The [LintResult] objects to filter. +- `results` (`LintResult[]`)
+ The [LintResult] objects to filter. #### Return Value -* (`LintResult[]`)
- The filtered [LintResult] objects. +- (`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`)
- 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[]`)
- The array of [LintMessage] objects. -* `suppressedMessages` (`SuppressedLintMessage[]`)
- The array of [SuppressedLintMessage] objects. -* `fixableErrorCount` (`number`)
- The number of errors that can be fixed automatically by the `fix` constructor option. -* `fixableWarningCount` (`number`)
- The number of warnings that can be fixed automatically by the `fix` constructor option. -* `errorCount` (`number`)
- The number of errors. This includes fixable errors and fatal errors. -* `fatalErrorCount` (`number`)
- The number of fatal errors. -* `warningCount` (`number`)
- The number of warnings. This includes fixable warnings. -* `output` (`string | undefined`)
- The modified source code text. This property is undefined if any fixable messages didn't exist. -* `source` (`string | undefined`)
- The original source code text. This property is undefined if any messages didn't exist or the `output` property exists. -* `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). +- `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[]`)
+ The array of [LintMessage] objects. +- `suppressedMessages` (`SuppressedLintMessage[]`)
+ The array of [SuppressedLintMessage] objects. +- `fixableErrorCount` (`number`)
+ The number of errors that can be fixed automatically by the `fix` constructor option. +- `fixableWarningCount` (`number`)
+ The number of warnings that can be fixed automatically by the `fix` constructor option. +- `errorCount` (`number`)
+ The number of errors. This includes fixable errors and fatal errors. +- `fatalErrorCount` (`number`)
+ The number of fatal errors. +- `warningCount` (`number`)
+ The number of warnings. This includes fixable warnings. +- `output` (`string | undefined`)
+ The modified source code text. This property is undefined if any fixable messages didn't exist. +- `source` (`string | undefined`)
+ The original source code text. This property is undefined if any messages didn't exist or the `output` property exists. +- `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`)
- 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`)
- The severity of this message. `1` means warning and `2` means error. -* `fatal` (`boolean | undefined`)
- `true` if this is a fatal error unrelated to a rule, like a parsing error. -* `message` (`string`)
- The error message. -* `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`)
- The 1-based column number of the begin point of this message. -* `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`)
- 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`)
- The [EditInfo] object of autofix. This property is undefined if this message is not fixable. -* `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. +- `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`)
+ The severity of this message. `1` means warning and `2` means error. +- `fatal` (`boolean | undefined`)
+ `true` if this is a fatal error unrelated to a rule, like a parsing error. +- `message` (`string`)
+ The error message. +- `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`)
+ The 1-based column number of the begin point of this message. +- `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`)
+ 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`)
+ The [EditInfo] object of autofix. This property is undefined if this message is not fixable. +- `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`)
- Same as `ruleId` in [LintMessage] type. -* `severity` (`1 | 2`)
- Same as `severity` in [LintMessage] type. -* `fatal` (`boolean | undefined`)
- Same as `fatal` in [LintMessage] type. -* `message` (`string`)
- Same as `message` in [LintMessage] type. -* `messageId` (`string | undefined`)
- Same as `messageId` in [LintMessage] type. -* `line` (`number | undefined`)
- Same as `line` in [LintMessage] type. -* `column` (`number | undefined`)
- Same as `column` in [LintMessage] type. -* `endLine` (`number | undefined`)
- Same as `endLine` in [LintMessage] type. -* `endColumn` (`number | undefined`)
- Same as `endColumn` in [LintMessage] type. -* `fix` (`EditInfo | undefined`)
- Same as `fix` in [LintMessage] type. -* `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
- Same as `suggestions` in [LintMessage] type. -* `suppressions` (`{ kind: string; justification: string}[]`)
- The list of suppressions. Each suppression is the pair of a kind and a justification. +- `ruleId` (`string` | `null`)
+ Same as `ruleId` in [LintMessage] type. +- `severity` (`1 | 2`)
+ Same as `severity` in [LintMessage] type. +- `fatal` (`boolean | undefined`)
+ Same as `fatal` in [LintMessage] type. +- `message` (`string`)
+ Same as `message` in [LintMessage] type. +- `messageId` (`string | undefined`)
+ Same as `messageId` in [LintMessage] type. +- `line` (`number | undefined`)
+ Same as `line` in [LintMessage] type. +- `column` (`number | undefined`)
+ Same as `column` in [LintMessage] type. +- `endLine` (`number | undefined`)
+ Same as `endLine` in [LintMessage] type. +- `endColumn` (`number | undefined`)
+ Same as `endColumn` in [LintMessage] type. +- `fix` (`EditInfo | undefined`)
+ Same as `fix` in [LintMessage] type. +- `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
+ Same as `suggestions` in [LintMessage] type. +- `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]`)
- The pair of 0-based indices in source code text to remove. -* `text` (`string`)
- The text to add. +- `range` (`[number, number]`)
+ The pair of 0-based indices in source code text to remove. +- `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. @@ -481,8 +481,8 @@ 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 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. +- `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. --- @@ -518,7 +518,7 @@ If you're ever unsure which config system the returned constructor uses, check t const DefaultESLint = await loadESLint(); if (DefaultESLint.configType === "flat") { - // do something specific to flat config + // do something specific to flat config } ``` @@ -557,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); @@ -579,13 +579,13 @@ The `Linter` object does the actual evaluation of the JavaScript code. It doesn' 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(); ``` @@ -596,18 +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] 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. +- `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`. @@ -617,60 +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. -* `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 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 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. @@ -678,11 +686,15 @@ 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" }] @@ -694,15 +706,19 @@ You can also get an instance of the `SourceCode` object used inside of `linter` 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()`. @@ -716,9 +732,9 @@ const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verifyAndFix("var foo", { - rules: { - semi: 2 - } + rules: { + semi: 2, + }, }); ``` @@ -734,9 +750,9 @@ 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). +- `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#version/Linter.version @@ -788,28 +804,28 @@ 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/ }], + }, + ], }); ``` @@ -825,35 +841,37 @@ If you don't specify any options to the `RuleTester` constructor, then it uses t 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. -* `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. +- `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 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. +- `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` or omitted, asserts that none of the reported problems suggest autofixes. + +- `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: @@ -890,21 +908,25 @@ Please note that `data` in a test case does not assert `data` passed to `context ### 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: +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 - }] - }] -}) + 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. @@ -917,44 +939,56 @@ ESLint makes its best attempt at applying all fixes, but there is no guarantee t 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. 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. +- `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 @@ -976,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 @@ -993,13 +1027,13 @@ 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/ 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 2d45736cbc1b..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. @@ -42,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); }); ``` @@ -73,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 9fe864b288fc..e299a5fd2a15 100644 --- a/docs/src/library/rule-list.md +++ b/docs/src/library/rule-list.md @@ -13,7 +13,9 @@ The rule list is a macro defined in `components/rule-list.macro.html`. The macro {% from 'components/rule-list.macro.html' import replacementRuleList %} -{{ replacementRuleList({ specifiers: [{ rule: { name: 'global-require', url: '...' }, plugin: { name: '@eslint-comunnity/eslint-plugin-n', url: '...' } }] }) }} +{{ replacementRuleList({ specifiers: [{ rule: { name: 'global-require', url: +'...' }, plugin: { name: '@eslint-comunnity/eslint-plugin-n', url: '...' } }] }) +}} ``` {% endraw %} diff --git a/docs/src/library/rule.md b/docs/src/library/rule.md index 096668462f74..e40f421aa69e 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,24 +19,17 @@ 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({ +{{ rule({ name: "array-bracket-newline", deprecated: true, description: 'Enforces line breaks after opening and before closing array brackets.', @@ -47,7 +40,7 @@ A rule has a: } }) }} - {{ rule({ +{{ rule({ name: "no-arrow-condition", removed: true, description: 'Disallows arrow functions where test conditions are expected.', 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 ba614dfc6473..30cd5b1e2911 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,19 +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. -* **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. -* **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. +- **Completed**: 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. @@ -59,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 @@ -95,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. @@ -121,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. @@ -169,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 38edcdc89b92..238a9e54d883 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 @@ -40,8 +39,8 @@ All release-related communications occur in a thread in the `#team` channel on D 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. @@ -54,41 +53,41 @@ After the patch release has been published (or no patch release is necessary), c 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` | +| :--------------: | :----------------------: | :---------------------------------------: | +| `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` | +| :--------------: | :----------------------: | :------------------------------------: | +| `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` | +| :--------------: | :-----------------------------: | :----------------------: | :---------------------------------------: | +| `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` | +| :--------------: | :-----------------------------: | :----------------------: | :------------------------------------: | +| `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 @@ -104,5 +103,5 @@ The only real exception is if ESLint is completely unusable by most of the curre 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. +- `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 6dd1781e291b..e947bfe0affa 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 001a1550bd4f..50378907c5f1 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. @@ -30,8 +29,8 @@ Once the bot checks have been satisfied, you check the following: 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." @@ -65,8 +64,8 @@ If the pull request does not have a related issue, then it should be moved throu 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". +- 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: @@ -105,8 +104,8 @@ Team members may merge a pull request immediately if it: 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. 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 index feb23fc1d090..18d6724e7f23 100644 --- a/docs/src/pages/flags.md +++ b/docs/src/pages/flags.md @@ -19,18 +19,18 @@ ESLint ships experimental and future breaking changes behind feature flags to le 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. +- `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. +- 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 @@ -90,11 +90,11 @@ When using the API, you can pass a `flags` array to both the `ESLint` and `Linte const { ESLint, Linter } = require("eslint"); const eslint = new ESLint({ - flags: ["flag_one", "flag_two"] + flags: ["flag_one", "flag_two"], }); const linter = new Linter({ - flags: ["flag_one", "flag_two"] + flags: ["flag_one", "flag_two"], }); ``` @@ -104,7 +104,7 @@ To enable flags in the VS Code ESLint Extension for the editor, specify the flag ```json { - "eslint.options": { "flags": ["flag_one", "flag_two"] } + "eslint.options": { "flags": ["flag_one", "flag_two"] } } ``` @@ -112,7 +112,7 @@ To enable flags in the VS Code ESLint Extension for a lint task, specify the `es ```json { - "eslint.lintTask.options": "--flag flag_one --flag flag_two ." + "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 0f1508d4a206..ac5c545c74f6 100644 --- a/docs/src/pages/rules.md +++ b/docs/src/pages/rules.md @@ -52,6 +52,7 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r } }) }} {%- endfor -%} + {%- endfor -%} {%- if rules.deprecated -%} @@ -61,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, @@ -77,6 +78,7 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r hasSuggestions: isHasSuggestions } }) }} + {%- endfor -%} {%- endif -%} @@ -87,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/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/func-name-matching.md b/docs/src/rules/func-name-matching.md index f104bd0e6484..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 { @@ -56,23 +56,23 @@ Examples of **correct** code for this rule: /*eslint func-name-matching: "error"*/ // 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,23 +101,24 @@ module['exports'] = function foo(name) {}; ```js /*eslint func-name-matching: ["error", "never"] */ -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 { @@ -156,7 +157,7 @@ Examples of **correct** code for the `{ considerPropertyDescriptor: true }` opti ```js /*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ // equivalent to /*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ -var obj = {}; +const obj = {}; Object.create(obj, {foo:{value: function foo() {}}}); Object.defineProperty(obj, 'bar', {value: function bar() {}}); Object.defineProperties(obj, {baz:{value: function baz() {} }}); @@ -172,7 +173,7 @@ Examples of **incorrect** code for the `{ considerPropertyDescriptor: true }` op ```js /*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ // equivalent to /*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ -var obj = {}; +const obj = {}; Object.create(obj, {foo:{value: function bar() {}}}); Object.defineProperty(obj, 'bar', {value: function baz() {}}); Object.defineProperties(obj, {baz:{value: function foo() {} }}); 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-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-keys.md b/docs/src/rules/no-dupe-keys.md index b8edaf81ff0e..10fdaebb998a 100644 --- a/docs/src/rules/no-dupe-keys.md +++ b/docs/src/rules/no-dupe-keys.md @@ -55,6 +55,11 @@ 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-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 7617e48196ad..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" }); @@ -46,25 +46,25 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-extra-bind: "error"*/ -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(); } @@ -80,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-global-assign.md b/docs/src/rules/no-global-assign.md index d0d057c55ed9..82603f9649b7 100644 --- a/docs/src/rules/no-global-assign.md +++ b/docs/src/rules/no-global-assign.md @@ -57,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 ``` 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-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-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 9e1577b66766..d2547ed951c5 100644 --- a/docs/src/rules/no-param-reassign.md +++ b/docs/src/rules/no-param-reassign.md @@ -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) {} } ``` diff --git a/docs/src/rules/no-shadow-restricted-names.md b/docs/src/rules/no-shadow-restricted-names.md index 8960d336a074..5e0513e85971 100644 --- a/docs/src/rules/no-shadow-restricted-names.md +++ b/docs/src/rules/no-shadow-restricted-names.md @@ -13,7 +13,7 @@ further_reading: 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: ```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,7 +31,7 @@ function NaN(){} !function(Infinity){}; -var undefined = 5; +const undefined = 5; try {} catch(eval){} ``` @@ -59,12 +59,12 @@ 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; ``` ::: diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md index a2c7d0a44079..c4032c75e69f 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; } ``` @@ -30,13 +30,13 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-shadow: "error"*/ -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) { @@ -45,7 +45,7 @@ function d(a) { d(a); if (true) { - let a = 5; + const a = 5; } ``` @@ -74,7 +74,7 @@ Examples of **incorrect** code for the `{ "builtinGlobals": true }` option: /*eslint no-shadow: ["error", { "builtinGlobals": true }]*/ function foo() { - var Object = 0; + const Object = 0; } ``` @@ -98,7 +98,7 @@ Examples of **incorrect** code for the default `{ "hoist": "functions" }` option /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ if (true) { - let b = 6; + const b = 6; } function b() {} @@ -106,7 +106,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,15 +116,15 @@ Examples of **correct** code for the default `{ "hoist": "functions" }` option: /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ 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 @@ -136,11 +136,11 @@ Examples of **incorrect** code for the `{ "hoist": "all" }` option: /*eslint no-shadow: ["error", { "hoist": "all" }]*/ if (true) { - let a = 3; - let b = 6; + const a = 3; + const b = 6; } -let a = 5; +const a = 5; function b() {} ``` @@ -156,17 +156,17 @@ Examples of **correct** code for the `{ "hoist": "never" }` option: /*eslint no-shadow: ["error", { "hoist": "never" }]*/ 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. ### allow @@ -207,7 +207,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; ``` ::: @@ -221,9 +221,9 @@ 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)() ``` ::: diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md index a837e5a9e92a..e8651558586d 100644 --- a/docs/src/rules/no-throw-literal.md +++ b/docs/src/rules/no-throw-literal.md @@ -28,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}` ``` @@ -50,7 +50,7 @@ throw new Error(); throw new Error("error"); -var e = new Error("error"); +const e = new Error("error"); throw e; try { @@ -73,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) { @@ -83,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-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-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-useless-assignment.md b/docs/src/rules/no-useless-assignment.md index 0faf447a5df4..3cabc76cb444 100644 --- a/docs/src/rules/no-useless-assignment.md +++ b/docs/src/rules/no-useless-assignment.md @@ -151,6 +151,41 @@ function fn() { ::: +## 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-constructor.md b/docs/src/rules/no-useless-constructor.md index f54a3cbdb771..3009a33e56db 100644 --- a/docs/src/rules/no-useless-constructor.md +++ b/docs/src/rules/no-useless-constructor.md @@ -77,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/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index ac7ce1fbf4af..502010424329 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -182,3 +182,27 @@ 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/strict.md b/docs/src/rules/strict.md index bf3bd6206c2b..ccfda9f31027 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -210,7 +210,7 @@ function foo() { } }()); -var foo = (function() { +const foo2 = (function() { "use strict"; return function foo(a = 1) { 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 626039b8013f..08b8f876f73f 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: use eslint title: Command Line Interface Reference order: 4 - --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} @@ -159,7 +158,7 @@ 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 @@ -172,8 +171,8 @@ Miscellaneous: 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 @@ -188,7 +187,7 @@ This example uses the configuration file at `~/my.eslint.config.js`, which is us **Flat Config Mode Only.** This option runs `npx @eslint/config-inspector@latest` to start the 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. +- **Argument Type**: No argument. ##### `--inspect-config` example @@ -201,8 +200,8 @@ This example uses the configuration file at `~/my.eslint.config.js`, which is us **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-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. @@ -222,9 +221,9 @@ Details about the global variables defined by each environment are available in This option allows you to specify additional file extensions to lint. -* **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). +- **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). This option is primarely 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. @@ -250,10 +249,10 @@ This option is primarely intended for use in combination with the `--no-config-l #### `--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 @@ -271,9 +270,9 @@ This option defines global variables so that they are not flagged as undefined 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 @@ -287,8 +286,8 @@ This option allows you to specify a parser to be used by ESLint. 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 @@ -310,16 +309,16 @@ This option allows you to specify parser options to be used by ESLint. The avail **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 @@ -334,8 +333,8 @@ For example: 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. @@ -355,8 +354,8 @@ Before using the plugin, you have to install it using npm. 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 `/`. @@ -394,8 +393,8 @@ To ignore rules in `.eslintrc` configuration files and only run rules specified **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. @@ -417,7 +416,7 @@ Note that, as with core rules and plugin rules, you still need to enable the rul 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: @@ -437,7 +436,7 @@ If you want to fix code from `stdin` or otherwise want to get the fixes without 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. @@ -455,12 +454,12 @@ This flag can be useful for integrations (e.g. editor plugins) which need to aut 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. @@ -487,9 +486,9 @@ This option is helpful if you are using another program to format your code, but **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 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). @@ -509,7 +508,7 @@ This option is helpful if you are using another program to format your code, but 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 @@ -522,8 +521,8 @@ Disables excluding of files from `.eslintignore` files, `--ignore-path` flags, ` 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-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 +- **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 @@ -538,7 +537,7 @@ This option allows you to specify patterns of files to ignore. In eslintrc mode, 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 @@ -552,8 +551,8 @@ This option tells ESLint to read and lint source code from STDIN instead of from 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. @@ -571,7 +570,7 @@ This is useful when processing files from STDIN and you have rules which depend 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 @@ -584,8 +583,8 @@ This option allows you to disable reporting on warnings and running of rules set 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. @@ -606,8 +605,8 @@ When used alongside `--quiet`, this will cause rules marked as warn to still be 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 @@ -620,9 +619,9 @@ Write the output of linting results to a specified file. 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. @@ -678,7 +677,7 @@ or alternatively 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`. @@ -701,17 +700,17 @@ You can use these options to override the default behavior, which is to enable c 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 @@ -724,7 +723,7 @@ This allows you to set an ESLint config without files modifying it. All inline c 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. @@ -745,12 +744,12 @@ For example, suppose a rule has a bug that causes it to report a false positive, 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 (which defaults to `"warn"`). +- **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 @@ -763,12 +762,12 @@ Same as [`--report-unused-disable-directives`](#--report-unused-disable-directiv 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"`). +- **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. @@ -786,7 +785,7 @@ npx eslint --report-unused-inline-configs error 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. @@ -809,9 +808,9 @@ 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. @@ -826,11 +825,11 @@ If the directory for the cache does not exist make sure you add a trailing `/` o 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. @@ -847,7 +846,7 @@ The `content` strategy can be useful in cases where the modification time of you 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. -* **Argument Type**: No argument. +- **Argument Type**: No argument. The resulting configuration file is created in the current directory. @@ -862,7 +861,7 @@ The resulting configuration file is created in the current directory. 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. @@ -877,7 +876,7 @@ The ESLint team may ask for this information to help solve bugs. When you use th 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 @@ -890,7 +889,7 @@ This option prevents errors when a quoted glob pattern or `--ext` is unmatched. 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 @@ -903,7 +902,7 @@ This option causes ESLint to exit with exit code 2 if one or more fatal parsing **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 @@ -916,7 +915,7 @@ This option causes ESLint to exit with exit code 2 if one or more fatal parsing 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. +- **Argument Type**: No argument. ##### `--pass-on-no-patterns` example @@ -929,7 +928,7 @@ This option allows ESLint to exit with code 0 when no file or directory patterns 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. @@ -944,7 +943,7 @@ This information is useful when you're seeing a problem and having a hard time p 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 @@ -957,7 +956,7 @@ This option outputs the help menu, displaying all of the available options. All 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 @@ -970,8 +969,8 @@ This option outputs the current ESLint version onto the console. All other optio 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 @@ -982,9 +981,9 @@ This option outputs the configuration to be used for the file passed. When prese #### `--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)). +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. +- **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. @@ -999,8 +998,8 @@ This option is intended for use with custom formatters that display statistics. This option enables one or more feature flags for ESLint. -* **Argument Type**: String. A feature identifier. -* **Multiple Arguments**: Yes +- **Argument Type**: String. A feature identifier. +- **Multiple Arguments**: Yes ##### `--flag` example @@ -1013,6 +1012,6 @@ This option enables one or more feature flags for ESLint. 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` 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 index 2c9af9d04258..cf2c33ee956a 100644 --- a/docs/src/use/configure/combine-configs.md +++ b/docs/src/use/configure/combine-configs.md @@ -19,12 +19,12 @@ import js from "@eslint/js"; import { defineConfig } from "eslint/config"; export default defineConfig([ - js.configs.recommended, - { - rules: { - "no-unused-vars": "warn" - } - } + js.configs.recommended, + { + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` @@ -40,13 +40,13 @@ import js from "@eslint/js"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/src/safe/*.js"], - plugins: { - js - }, - extends: ["js/recommended"] - } + { + files: ["**/src/safe/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + }, ]); ``` @@ -62,16 +62,15 @@ 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" - } - } + // insert array directly + exampleConfigs, + + // your modifications + { + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` @@ -83,18 +82,17 @@ 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" - } - } + // insert individual elements instead of an array + exampleConfigs[0], + exampleConfigs[1], + exampleConfigs[2], + + // your modifications + { + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` @@ -108,13 +106,13 @@ 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" - } - } + { + files: ["**/src/safe/*.js"], + extends: [exampleConfigs], + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` diff --git a/docs/src/use/configure/configuration-files-deprecated.md b/docs/src/use/configure/configuration-files-deprecated.md index b1aefb948a69..c163bd8c9897 100644 --- a/docs/src/use/configure/configuration-files-deprecated.md +++ b/docs/src/use/configure/configuration-files-deprecated.md @@ -12,11 +12,11 @@ You can put your ESLint project configuration in a configuration file. You can i 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. +- **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: @@ -45,26 +45,21 @@ Here's an example JSON configuration file that uses the `typescript-eslint` pars ```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/*"] + "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/*"] } ``` @@ -106,9 +101,9 @@ In JSON: ```json { - "settings": { - "sharedData": "Hello" - } + "settings": { + "sharedData": "Hello" + } } ``` @@ -116,7 +111,7 @@ And in YAML: ```yaml --- - settings: +settings: sharedData: "Hello" ``` @@ -162,10 +157,10 @@ And in YAML: ```yaml --- - root: true +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. +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 @@ -199,14 +194,14 @@ Please note that the [home directory of the current user on your preferred opera 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. +- 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. +- 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. @@ -214,19 +209,19 @@ The `eslint-config-` prefix can be omitted from the configuration name. For exam 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` +- 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 @@ -241,10 +236,10 @@ Example of a configuration file in YAML format: ```yaml extends: standard rules: - comma-dangle: - - error - - always - no-empty: warn + comma-dangle: + - error + - always + no-empty: warn ``` ### Using `eslint:recommended` @@ -257,22 +252,22 @@ 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", - } -} + 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 @@ -283,25 +278,20 @@ The `plugins` [property value](./plugins#configure-plugins) can omit the `eslint 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`) +- `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" - } + "plugins": ["react"], + "extends": ["eslint:recommended", "plugin:react/recommended"], + "rules": { + "react/no-set-state": "off" + } } ``` @@ -313,14 +303,14 @@ 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" - } + "extends": [ + "./node_modules/coding-standard/eslintDefaults.js", + "./node_modules/coding-standard/.eslintrc-es6", + "./node_modules/coding-standard/.eslintrc-jsx" + ], + "rules": { + "eqeqeq": "warn" + } } ``` @@ -338,22 +328,22 @@ 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", - } -} + 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 @@ -370,31 +360,31 @@ In your `.eslintrc.json`: ```json { - "rules": { - "quotes": ["error", "double"] - }, - - "overrides": [ - { - "files": ["bin/*.js", "lib/*.js"], - "excludedFiles": "*.test.js", - "rules": { - "quotes": ["error", "single"] - } - } - ] + "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. +- 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 diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index 62da1c288bd2..1989f510ec98 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: configure title: Configuration Files order: 1 - --- {%- from 'components/npx_tabs.macro.html' import npx_tabs %} @@ -21,12 +20,12 @@ You can put your ESLint project configuration in a configuration file. You can i The ESLint configuration file may be named any of the following: -* `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)) +- `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)) It should be placed in the root directory of your project and export an array of [configuration objects](#configuration-objects). Here's an example: @@ -35,12 +34,12 @@ It should be placed in the root directory of your project and export an array of import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: "error", - "prefer-const": "error" - } - } + { + rules: { + semi: "error", + "prefer-const": "error", + }, + }, ]); ``` @@ -53,12 +52,12 @@ If your project does not specify `"type":"module"` in its `package.json` file, t const { defineConfig } = require("eslint/config"); module.exports = defineConfig([ - { - rules: { - semi: "error", - "prefer-const": "error" - } - } + { + rules: { + semi: "error", + "prefer-const": "error", + }, + }, ]); ``` @@ -66,23 +65,24 @@ module.exports = defineConfig([ 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 to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions)) -* `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. -* `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. +- `name` - A name for the configuration object. This is used in error messages and config inspector to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions)) +- `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` @@ -98,11 +98,11 @@ Because config objects that don't specify `files` or `ignores` apply to all file import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: "error" - } - } + { + rules: { + semi: "error", + }, + }, ]); ``` @@ -117,12 +117,12 @@ You can limit which files a configuration object applies to by specifying a comb import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["src/**/*.js"], - rules: { - semi: "error" - } - } + { + files: ["src/**/*.js"], + rules: { + semi: "error", + }, + }, ]); ``` @@ -132,13 +132,13 @@ Here, only the JavaScript files in the `src` directory have the `semi` rule appl import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["src/**/*.js"], - ignores: ["**/*.config.js"], - rules: { - semi: "error" - } - } + { + files: ["src/**/*.js"], + ignores: ["**/*.config.js"], + rules: { + semi: "error", + }, + }, ]); ``` @@ -148,13 +148,13 @@ This configuration object matches all JavaScript files in the `src` directory ex import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["src/**/*.js"], - ignores: ["**/*.config.js", "!**/eslint.config.js"], - rules: { - semi: "error" - } - } + { + files: ["src/**/*.js"], + ignores: ["**/*.config.js", "!**/eslint.config.js"], + rules: { + semi: "error", + }, + }, ]); ``` @@ -168,12 +168,12 @@ If `ignores` is used without `files` and there are other keys (such as `rules`), import { defineConfig } from "eslint/config"; export default defineConfig([ - { - ignores: ["**/*.config.js"], - rules: { - semi: "error" - } - } + { + ignores: ["**/*.config.js"], + rules: { + semi: "error", + }, + }, ]); ``` @@ -196,14 +196,10 @@ For example, to lint TypeScript files with `.ts`, `.cts` and `.mts` extensions, import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: [ - "**/*.ts", - "**/*.cts", - "**.*.mts" - ] - }, - // ...other config + { + files: ["**/*.ts", "**/*.cts", "**.*.mts"], + }, + // ...other config ]); ``` @@ -216,10 +212,10 @@ Files without an extension can be matched with the pattern `!(*.*)`. For example import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/!(*.*)"] - }, - // ...other config + { + files: ["**/!(*.*)"], + }, + // ...other config ]); ``` @@ -232,18 +228,18 @@ Filenames starting with a dot, such as `.gitignore`, are considered to have only 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. +- 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: -* 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. +- 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`. +- 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 // eslint.config.js @@ -308,23 +304,23 @@ When more than one configuration object matches a given filename, the configurat 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" - } - } - } + { + files: ["**/*.js"], + languageOptions: { + globals: { + MY_CUSTOM_GLOBAL: "readonly", + }, + }, + }, + { + files: ["tests/**/*.js"], + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, ]); ``` @@ -343,12 +339,12 @@ Inline configuration is implemented using an `/*eslint*/` comment, such as `/*es import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.js"], - linterOptions: { - noInlineConfig: true - } - } + { + files: ["**/*.js"], + linterOptions: { + noInlineConfig: true, + }, + }, ]); ``` @@ -361,12 +357,12 @@ Disable and enable directives such as `/*eslint-disable*/`, `/*eslint-enable*/` import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.js"], - linterOptions: { - reportUnusedDisableDirectives: "error" - } - } + { + files: ["**/*.js"], + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, ]); ``` @@ -387,12 +383,12 @@ You can enable reporting of these unused inline config comments by setting the ` import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.js"], - linterOptions: { - reportUnusedInlineConfigs: "error" - } - } + { + files: ["**/*.js"], + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }, ]); ``` @@ -407,11 +403,11 @@ You can configure any number of rules in a configuration object by add a `rules` import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: "error" - } - } + { + rules: { + semi: "error", + }, + }, ]); ``` @@ -422,11 +418,11 @@ This configuration object specifies that the [`semi`](../../rules/semi) rule sho import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: ["error", "never"] - } - } + { + rules: { + semi: ["error", "never"], + }, + }, ]); ``` @@ -443,31 +439,31 @@ ESLint supports adding shared settings into configuration files. When you add a 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" - } - } + { + 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", + }, + }, ]); ``` @@ -475,9 +471,9 @@ export default defineConfig([ 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: -* a string that specifies the name of a configuration in a plugin -* a configuration object -* a configuration array +- a string that specifies the name of a configuration in a plugin +- a configuration object +- a configuration array #### Using Configurations from Plugins @@ -489,12 +485,13 @@ import examplePlugin from "eslint-plugin-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - example: examplePlugin - }, - extends: ["example/recommended"] - } + { + files: ["**/*.js"], + plugins: { + example: examplePlugin, + }, + extends: ["example/recommended"], + }, ]); ``` @@ -508,23 +505,28 @@ import pluginExample from "eslint-plugin-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - example: pluginExample - }, - extends: [pluginExample.configs.recommended] - } + { + 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. +::: + #### Using Predefined Configurations ESLint has two predefined configurations for JavaScript: -* `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. +- `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. To include these predefined configurations, install the `@eslint/js` package and then make any modifications to other properties in subsequent configuration objects: @@ -534,15 +536,16 @@ import js from "@eslint/js"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - js - }, - extends: ["js/recommended"], - rules: { - "no-unused-vars": "warn" - } - } + { + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` @@ -560,12 +563,13 @@ import exampleConfig from "eslint-config-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - extends: [exampleConfig], - rules: { - "no-unused-vars": "warn" - } - } + { + files: ["**/*.js"], + extends: [exampleConfig], + rules: { + "no-unused-vars": "warn", + }, + }, ]); ``` @@ -583,20 +587,20 @@ For example, if you are creating a configuration object for a plugin named `esli ```js export default { - configs: { - recommended: { - name: "example/recommended", - rules: { - "no-unused-vars": "warn" - } - }, - strict: { - name: "example/strict", - rules: { - "no-unused-vars": "error" - } - } - } + configs: { + recommended: { + name: "example/recommended", + rules: { + "no-unused-vars": "warn", + }, + }, + strict: { + name: "example/strict", + rules: { + "no-unused-vars": "error", + }, + }, + }, }; ``` @@ -604,24 +608,24 @@ When exposing arrays of configuration objects, the `name` may have extra scoping ```js export default { - configs: { - strict: [ - { - name: "example/strict/language-setup", - languageOptions: { - ecmaVersion: 2024 - } - }, - { - name: "example/strict/sub-config", - file: ["src/**/*.js"], - rules: { - "no-unused-vars": "error" - } - } - ] - } -} + configs: { + strict: [ + { + name: "example/strict/language-setup", + languageOptions: { + ecmaVersion: 2024, + }, + }, + { + name: "example/strict/sub-config", + file: ["src/**/*.js"], + rules: { + "no-unused-vars": "error", + }, + }, + ], + }, +}; ``` ## Configuration File Resolution diff --git a/docs/src/use/configure/debug.md b/docs/src/use/configure/debug.md index f9a8f2bd24e8..1d5e149d3b0a 100644 --- a/docs/src/use/configure/debug.md +++ b/docs/src/use/configure/debug.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: configure title: Debug Your Configuration order: 8 - --- {%- from 'components/npx_tabs.macro.html' import npx_tabs %} @@ -47,20 +46,18 @@ 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 - } + "linterOptions": { + "reportUnusedDisableDirectives": 1 + }, + "language": "@/js", + "languageOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "plugins": ["@"], + "rules": { + "prefer-const": 2 + } } ``` diff --git a/docs/src/use/configure/ignore-deprecated.md b/docs/src/use/configure/ignore-deprecated.md index 09c1ca83f238..7f3cb3b8a3e9 100644 --- a/docs/src/use/configure/ignore-deprecated.md +++ b/docs/src/use/configure/ignore-deprecated.md @@ -9,8 +9,8 @@ This documentation is for ignoring files using the deprecated eslintrc configura 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). +- Add `ignorePatterns` to a configuration file. +- Create a dedicated file that contains the ignore patterns (`.eslintignore` by default). ## `ignorePatterns` in Config Files @@ -18,16 +18,16 @@ You can tell ESLint to ignore specific files and directories using `ignorePatter ```json { - "ignorePatterns": ["temp.js", "**/vendor/*.js"], - "rules": { - //... - } + "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. +- 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`. @@ -45,10 +45,10 @@ When ESLint is run, it looks in the current working directory to find an `.eslin 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). +- 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. @@ -64,36 +64,36 @@ Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification fo 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. +- `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. +- 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. + 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. +- 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. + 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. +- 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: + 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 - ``` + ```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: + The following `--ignore-pattern` is also equivalent: - ```shell - eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/ - ``` + ```shell + eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/ + ``` ## Using an Alternate File @@ -117,15 +117,15 @@ If an `.eslintignore` file is not found and an alternate file is not specified, ```json { - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "env": { - "browser": true, - "node": true - } - }, - "eslintIgnore": ["hello.js", "world.js"] + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "env": { + "browser": true, + "node": true + } + }, + "eslintIgnore": ["hello.js", "world.js"] } ``` diff --git a/docs/src/use/configure/ignore.md b/docs/src/use/configure/ignore.md index a0b824fabdeb..7d98eb6097c9 100644 --- a/docs/src/use/configure/ignore.md +++ b/docs/src/use/configure/ignore.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: configure title: Ignore Files order: 7 - --- {%- from 'components/npx_tabs.macro.html' import npx_tabs %} @@ -19,8 +18,8 @@ This page explains how to use the `globalIgnores()` function to completely ignor ::: You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns in the following ways: -* Inside of your `eslint.config.js` file. -* On the command line using `--ignore-pattern`. +- Inside of your `eslint.config.js` file. +- On the command line using `--ignore-pattern`. ## Ignoring Files @@ -30,9 +29,7 @@ In your `eslint.config.js` file, if an `ignores` key is used without any other k // eslint.config.js import { defineConfig, globalIgnores } from "eslint/config"; -export default defineConfig([ - globalIgnores([".config/*"]) -]); +export default defineConfig([globalIgnores([".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/"]`. @@ -52,9 +49,7 @@ Ignoring directories works the same way as ignoring files, by placing a pattern // eslint.config.js import { defineConfig, globalIgnores } from "eslint/config"; -export default defineConfig([ - globalIgnores([".config/"]) -]); +export default defineConfig([globalIgnores([".config/"])]); ``` 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: @@ -63,9 +58,7 @@ Unlike `.gitignore`, an ignore pattern like `.config` will only ignore the `.con // eslint.config.js import { defineConfig, globalIgnores } from "eslint/config"; -export default defineConfig([ - globalIgnores(["**/.config/"]) -]); +export default defineConfig([globalIgnores(["**/.config/"])]); ``` ## Unignoring Files and Directories @@ -77,27 +70,27 @@ You can also unignore files and directories that are ignored by previous pattern import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ - globalIgnores([ - "!node_modules/", // unignore `node_modules/` directory - "node_modules/*", // ignore its content - "!node_modules/mylibrary/" // unignore `node_modules/mylibrary` directory - ]) + globalIgnores([ + "!node_modules/", // unignore `node_modules/` directory + "node_modules/*", // ignore its content + "!node_modules/mylibrary/", // unignore `node_modules/mylibrary` directory + ]), ]); ``` 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, `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: +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: ```js // eslint.config.js import { defineConfig, globalIgnores } from "eslint/config"; 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` - ]) + globalIgnores([ + "build/**/*", // ignore all contents in and under `build/` directory but not the `build/` directory itself + "!build/test.js", // unignore `!build/test.js` + ]), ]); ``` @@ -110,11 +103,11 @@ For example, this config ignores all files in and under `build` directory except import { defineConfig, globalIgnores } from "eslint/config"; 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 - ]) + 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 + ]), ]); ``` @@ -147,7 +140,7 @@ By default, `globalIgnores()` will assign a name to the config that represents y import { defineConfig, globalIgnores } from "eslint/config"; export default defineConfig([ - globalIgnores(["build/**/*"], "Ignore build directory") + globalIgnores(["build/**/*"], "Ignore build directory"), ]); ``` @@ -161,9 +154,7 @@ When you pass directories to the ESLint CLI, files and directories are silently // eslint.config.js import { defineConfig, globalIgnores } from "eslint/config"; -export default defineConfig([ - globalIgnores(["foo.js"]) -]); +export default defineConfig([globalIgnores(["foo.js"])]); ``` And then you run: @@ -200,10 +191,10 @@ const __dirname = path.dirname(__filename); const gitignorePath = path.resolve(__dirname, ".gitignore"); export default defineConfig([ - includeIgnoreFile(gitignorePath), - { - // your overrides - } + includeIgnoreFile(gitignorePath), + { + // your overrides + }, ]); ``` diff --git a/docs/src/use/configure/index.md b/docs/src/use/configure/index.md index 6173b520f59c..038755338fc4 100644 --- a/docs/src/use/configure/index.md +++ b/docs/src/use/configure/index.md @@ -5,7 +5,6 @@ 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: @@ -15,9 +14,9 @@ ESLint is designed to be flexible and configurable for your use case. You can tu Here are some of the options that you can configure in ESLint: -* [**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. +- [**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. @@ -25,33 +24,33 @@ All of these options give you fine-grained control over how ESLint treats your c [**Configuration Files**](configuration-files) -* [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) +- [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 JavaScript Options](./language-options#specifying-javascript-options) -* [Specifying Globals](./language-options#specifying-globals) +- [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) -* [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) +- [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 index ba05cc8cba2a..e533ae5ff529 100644 --- a/docs/src/use/configure/language-options-deprecated.md +++ b/docs/src/use/configure/language-options-deprecated.md @@ -12,40 +12,40 @@ The JavaScript ecosystem has a variety of runtimes, versions, extensions, and fr 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. +- `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. @@ -67,10 +67,10 @@ To specify environments in a configuration file, use the `env` key. Specify whic ```json { - "env": { - "browser": true, - "node": true - } + "env": { + "browser": true, + "node": true + } } ``` @@ -78,14 +78,14 @@ Or in a `package.json` file ```json { - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "env": { - "browser": true, - "node": true - } - } + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "env": { + "browser": true, + "node": true + } + } } ``` @@ -93,7 +93,7 @@ And in YAML: ```yaml --- - env: +env: browser: true node: true ``` @@ -104,10 +104,10 @@ If you want to use an environment from a plugin, be sure to specify the plugin n ```json { - "plugins": ["example"], - "env": { - "example/custom": true - } + "plugins": ["example"], + "env": { + "example/custom": true + } } ``` @@ -115,14 +115,14 @@ Or in a `package.json` file ```json { - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "plugins": ["example"], - "env": { - "example/custom": true - } - } + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "plugins": ["example"], + "env": { + "example/custom": true + } + } } ``` @@ -150,10 +150,10 @@ To configure global variables inside of a configuration file, set the `globals` ```json { - "globals": { - "var1": "writable", - "var2": "readonly" - } + "globals": { + "var1": "writable", + "var2": "readonly" + } } ``` @@ -161,7 +161,7 @@ And in YAML: ```yaml --- - globals: +globals: var1: writable var2: readonly ``` @@ -172,12 +172,12 @@ Globals can be disabled by setting their value to `"off"`. For example, in an en ```json { - "env": { - "es6": true - }, - "globals": { - "Promise": "off" - } + "env": { + "es6": true + }, + "globals": { + "Promise": "off" + } } ``` @@ -193,28 +193,28 @@ By the same token, supporting ES6 syntax is not the same as supporting new ES6 g 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, or 16 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), or 2025 (same as 16) 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/) +- `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, or 16 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), or 2025 (same as 16) 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" - } + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "semi": "error" + } } ``` diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index f5ea0cd21a93..5a02399b5aae 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -17,11 +17,11 @@ The JavaScript ecosystem has a variety of runtimes, versions, extensions, and fr 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: -* `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. +- `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. Here's an example [configuration file](./configuration-files#configuration-file) you might use when linting ECMAScript 5 code: @@ -30,12 +30,12 @@ Here's an example [configuration file](./configuration-files#configuration-file) import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - } + { + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, ]); ``` @@ -43,11 +43,11 @@ export default defineConfig([ 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: -* `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/). +- `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 [configuration file](./configuration-files#configuration-file) that enables JSX parsing in the default parser: @@ -56,15 +56,15 @@ Here's an example [configuration file](./configuration-files#configuration-file) import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - } + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, ]); ``` @@ -99,14 +99,14 @@ To configure global variables inside of a [configuration file](./configuration-f import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - globals: { - var1: "writable", - var2: "readonly" - } - } - } + { + languageOptions: { + globals: { + var1: "writable", + var2: "readonly", + }, + }, + }, ]); ``` @@ -119,13 +119,13 @@ Globals can be disabled by setting their value to `"off"`. For example, in an en import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - globals: { - Promise: "off" - } - } - } + { + languageOptions: { + globals: { + Promise: "off", + }, + }, + }, ]); ``` @@ -143,13 +143,13 @@ import globals from "globals"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - globals: { - ...globals.browser - } - } - } + { + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, ]); ``` @@ -161,13 +161,13 @@ import globals from "globals"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - languageOptions: { - globals: { - ...globals.browser, - ...globals.jest - } - } - } + { + 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 0b83266ddd7c..025f03a09d87 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -16,8 +16,8 @@ To learn more about the flat config format, refer to [this blog post](https://es For reference information on these configuration formats, refer to the following documentation: -* [eslintrc configuration files](configuration-files-deprecated) -* [flat configuration files](configuration-files) +- [eslintrc configuration files](configuration-files-deprecated) +- [flat configuration files](configuration-files) ## Migrate Your Config File @@ -44,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 @@ -65,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 }; ``` @@ -83,16 +83,16 @@ 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", + }, + }, ]; ``` @@ -112,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 }; ``` @@ -126,13 +126,13 @@ 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 + }, ]; ``` @@ -147,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() {}, + }, + }, }; ``` @@ -165,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", }; ``` @@ -174,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", + }, + ]; } ``` @@ -188,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, + }, ]; ``` @@ -227,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"], + }, }; ``` @@ -240,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", + }, + }, + ], }; ``` @@ -266,15 +268,15 @@ 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 + }, ]; ``` @@ -286,21 +288,21 @@ A flat 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 ]; ``` @@ -316,19 +318,19 @@ For example, here's an eslintrc file with language options: // .eslintrc.js module.exports = { - env: { - browser: true, - node: 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: @@ -339,18 +341,18 @@ Here's the same configuration in flat config: import globals from "globals"; export default [ - { - languageOptions: { - ecmaVersion: 2022, - sourceType: "module", - globals: { - ...globals.browser, - ...globals.node, - myCustomGlobal: "readonly" - } - } - // ...other config - } + { + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { + ...globals.browser, + ...globals.node, + myCustomGlobal: "readonly", + }, + }, + // ...other config + }, ]; ``` @@ -373,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", () => { + // ... + }); }); ``` @@ -389,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", () => { + // ... + }); }); ``` @@ -403,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, + }, + }, + }, ]; ``` @@ -421,8 +421,8 @@ 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. @@ -442,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: @@ -457,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: @@ -476,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 + }, ]; ``` @@ -497,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"], + }, ]; ``` @@ -527,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"), ]; ``` @@ -561,8 +564,8 @@ Here are the same patterns represented as `ignorePatterns` in a `.eslintrc.js` f ```javascript // .eslintrc.js module.exports = { - // ...other config - ignorePatterns: ["temp.js", "config/*"], + // ...other config + ignorePatterns: ["temp.js", "config/*"], }; ``` @@ -570,11 +573,11 @@ The equivalent ignore patterns in flat config look like this: ```javascript export default [ - // ...other config - { - // Note: there should be no other properties in this object - ignores: ["**/temp.js", "config/*"] - } + // ...other config + { + // Note: there should be no other properties in this object + ignores: ["**/temp.js", "config/*"], + }, ]; ``` @@ -596,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: @@ -608,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", + }, + }, ]; ``` @@ -622,9 +625,9 @@ 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`. @@ -637,22 +640,21 @@ The `--rulesdir` flag was used to load additional rules from a specified directo 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"] - } - - } + { + // define the plugin + plugins: { + local: { + rules: { + "my-rule": myRule, + }, + }, + }, + + // configure the rule + rules: { + "local/my-rule": ["error"], + }, + }, ]; ``` @@ -663,11 +665,11 @@ The `--ext` flag was used to specify additional file extensions ESLint should se ```js // eslint.config.js export default [ - { - files: ["**/*.ts", "**/*.tsx"] + { + files: ["**/*.ts", "**/*.tsx"], - // any additional configuration for these file types here - } + // any additional configuration for these file types here + }, ]; ``` @@ -689,9 +691,9 @@ With flat config, it's no longer possible to use a `package.json` file to config 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 @@ -707,8 +709,8 @@ In versions of `vscode-eslint` prior to v3.0.10, the new configuration system is ```json { - // required in vscode-eslint < v3.0.10 only - "eslint.experimental.useFlatConfig": true + // required in vscode-eslint < v3.0.10 only + "eslint.experimental.useFlatConfig": true } ``` @@ -716,6 +718,6 @@ In a future version of the ESLint plugin, you will no longer need to enable this ## 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 index 74c917b03b48..46d11b14d4d0 100644 --- a/docs/src/use/configure/parser-deprecated.md +++ b/docs/src/use/configure/parser-deprecated.md @@ -21,17 +21,17 @@ To indicate the npm module to use as your parser, specify it using the `parser` ```json { - "parser": "esprima", - "rules": { - "semi": "error" - } + "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. +- [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 50f4a676e190..a8aca0daed11 100644 --- a/docs/src/use/configure/parser.md +++ b/docs/src/use/configure/parser.md @@ -23,12 +23,12 @@ import babelParser from "@babel/eslint-parser"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.js", "**/*.mjs"], - languageOptions: { - parser: babelParser - } - } + { + files: ["**/*.js", "**/*.mjs"], + languageOptions: { + parser: babelParser, + }, + }, ]); ``` @@ -36,9 +36,9 @@ This configuration ensures that the Babel parser, rather than the default Espree The following third-party parsers are known to be 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. +- [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. ::: 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. @@ -54,19 +54,19 @@ 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"] - } - } - } - } + { + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: false, + presets: ["@babel/preset-env"], + }, + }, + }, + }, ]); ``` diff --git a/docs/src/use/configure/plugins-deprecated.md b/docs/src/use/configure/plugins-deprecated.md index 339a257c585e..1ba6bb8d80e7 100644 --- a/docs/src/use/configure/plugins-deprecated.md +++ b/docs/src/use/configure/plugins-deprecated.md @@ -1,16 +1,17 @@ --- 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. +- 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 @@ -20,10 +21,7 @@ To configure plugins inside of a configuration file, use the `plugins` key, whic ```json { - "plugins": [ - "plugin1", - "eslint-plugin-plugin2" - ] + "plugins": ["plugin1", "eslint-plugin-plugin2"] } ``` @@ -31,7 +29,7 @@ And in YAML: ```yaml --- - plugins: +plugins: - plugin1 - eslint-plugin-plugin2 ``` @@ -76,9 +74,9 @@ A scoped package: 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` +- `eslint-plugin-foo` → `foo/a-rule` +- `@foo/eslint-plugin` → `@foo/a-config` +- `@foo/eslint-plugin-bar` → `@foo/bar/a-environment` For example: @@ -116,8 +114,8 @@ To specify processors in a configuration file, use the `processor` key with the ```json { - "plugins": ["a-plugin"], - "processor": "a-plugin/a-processor" + "plugins": ["a-plugin"], + "processor": "a-plugin/a-processor" } ``` @@ -125,13 +123,13 @@ To specify processors for specific kinds of files, use the combination of the `o ```json { - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - } - ] + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + } + ] } ``` @@ -139,19 +137,19 @@ Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles ```json { - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - }, - { - "files": ["**/*.md/*.js"], - "rules": { - "strict": "off" - } - } - ] + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + }, + { + "files": ["**/*.md/*.js"], + "rules": { + "strict": "off" + } + } + ] } ``` diff --git a/docs/src/use/configure/plugins.md b/docs/src/use/configure/plugins.md index 0c58176fb295..a91d3882fb24 100644 --- a/docs/src/use/configure/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: configure title: Configure Plugins order: 4 - --- ::: tip @@ -14,9 +13,9 @@ This page explains how to configure plugins using the flat config format. For th 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. 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. +- 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 @@ -30,14 +29,14 @@ import example from "eslint-plugin-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - example - }, - rules: { - "example/rule1": "warn" - } - } + { + plugins: { + example, + }, + rules: { + "example/rule1": "warn", + }, + }, ]); ``` @@ -55,14 +54,14 @@ import local from "./my-local-plugin.js"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - local - }, - rules: { - "local/rule1": "warn" - } - } + { + plugins: { + local, + }, + rules: { + "local/rule1": "warn", + }, + }, ]); ``` @@ -78,18 +77,18 @@ 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" - } - } + { + plugins: { + local: { + rules: { + "my-rule": myRule, + }, + }, + }, + rules: { + "local/my-rule": "warn", + }, + }, ]); ``` @@ -107,16 +106,16 @@ 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" - } - } + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc, + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + }, ]); ``` @@ -129,16 +128,16 @@ 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" - } - } + { + files: ["**/*.js"], + plugins: { + jsdoc, + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + }, ]); ``` @@ -149,16 +148,16 @@ 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" - } - } + { + files: ["**/*.js"], + plugins: { + jsd: jsdoc, + }, + rules: { + "jsd/require-description": "error", + "jsd/check-values": "error", + }, + }, ]); ``` @@ -176,13 +175,13 @@ import markdown from "@eslint/markdown"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.md"], - plugins: { - markdown - }, - processor: "markdown/markdown" - } + { + files: ["**/*.md"], + plugins: { + markdown, + }, + processor: "markdown/markdown", + }, ]); ``` @@ -194,29 +193,29 @@ 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" - } - } + // 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", + }, + }, ]); ``` @@ -228,31 +227,31 @@ 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"] - } + // 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"], + }, ]); ``` @@ -266,13 +265,13 @@ import json from "@eslint/json"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - files: ["**/*.json"], - plugins: { - json - }, - language: "json/jsonc" - } + { + files: ["**/*.json"], + plugins: { + json, + }, + language: "json/jsonc", + }, ]); ``` @@ -282,5 +281,5 @@ When you specify a `language` in a config object, `languageOptions` becomes spec ## 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) +- [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 index 0c5be8a4b896..126138ca32f6 100644 --- a/docs/src/use/configure/rules-deprecated.md +++ b/docs/src/use/configure/rules-deprecated.md @@ -14,9 +14,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. @@ -73,11 +73,11 @@ To configure rules inside of a configuration file, use the `rules` key along wit ```json { - "rules": { - "eqeqeq": "off", - "curly": "error", - "quotes": ["error", "double"] - } + "rules": { + "eqeqeq": "off", + "curly": "error", + "quotes": ["error", "double"] + } } ``` @@ -86,11 +86,11 @@ And in YAML: ```yaml --- rules: - eqeqeq: off - curly: error - quotes: - - error - - double + eqeqeq: off + curly: error + quotes: + - error + - double ``` ## Rules from Plugins @@ -101,15 +101,13 @@ In a configuration file, for example: ```json { - "plugins": [ - "plugin1" - ], - "rules": { - "eqeqeq": "off", - "curly": "error", - "quotes": ["error", "double"], - "plugin1/rule1": "error" - } + "plugins": ["plugin1"], + "rules": { + "eqeqeq": "off", + "curly": "error", + "quotes": ["error", "double"], + "plugin1/rule1": "error" + } } ``` @@ -118,14 +116,14 @@ And in YAML: ```yaml --- plugins: - - plugin1 + - plugin1 rules: - eqeqeq: 0 - curly: error - quotes: - - error - - "double" - plugin1/rule1: error + 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`. @@ -142,23 +140,23 @@ You can also use this format with configuration comments, such as: ### 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 */ ``` @@ -168,8 +166,8 @@ 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 */ ``` @@ -181,7 +179,7 @@ To disable rule warnings in an entire file, put a `/* eslint-disable */` block c ```js /* eslint-disable */ -alert('foo'); +alert("foo"); ``` You can also disable or enable specific rules for an entire file: @@ -189,7 +187,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): @@ -197,56 +195,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`: @@ -264,13 +262,13 @@ 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 diff --git a/docs/src/use/configure/rules.md b/docs/src/use/configure/rules.md index cb2d17a8da7f..b64ebffdda1a 100644 --- a/docs/src/use/configure/rules.md +++ b/docs/src/use/configure/rules.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: configure title: Configure Rules order: 3 - --- ::: tip @@ -20,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. @@ -82,11 +81,11 @@ To report unused `eslint` inline config comments (those that don't change anythi import { defineConfig } from "eslint/config"; export default defineConfig([ - { - linterOptions: { - reportUnusedInlineConfigs: "error" - } - } + { + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }, ]); ``` @@ -103,13 +102,13 @@ To configure rules inside of a [configuration file](./configuration-files#config import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - eqeqeq: "off", - "no-unused-vars": "error", - "prefer-const": ["error", { "ignoreReadBeforeAssign": true }] - } - } + { + rules: { + eqeqeq: "off", + "no-unused-vars": "error", + "prefer-const": ["error", { ignoreReadBeforeAssign: true }], + }, + }, ]); ``` @@ -119,16 +118,16 @@ When more than one configuration object specifies the same rule, the rule config import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: ["error", "never"] - } - }, - { - rules: { - semi: ["warn", "always"] - } - } + { + rules: { + semi: ["error", "never"], + }, + }, + { + rules: { + semi: ["warn", "always"], + }, + }, ]); ``` @@ -138,16 +137,16 @@ Using this configuration, the final rule configuration for `semi` is `["warn", " import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - semi: ["error", "never"] - } - }, - { - rules: { - semi: "warn" - } - } + { + rules: { + semi: ["error", "never"], + }, + }, + { + rules: { + semi: "warn", + }, + }, ]); ``` @@ -169,14 +168,14 @@ import example from "eslint-plugin-example"; import { defineConfig } from "eslint/config"; export default defineConfig([ - { - plugins: { - example - }, - rules: { - "example/rule1": "warn" - } - } + { + plugins: { + example, + }, + rules: { + "example/rule1": "warn", + }, + }, ]); ``` @@ -196,23 +195,23 @@ In order to use plugin rules in configuration comments, your configuration file ### 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 */ ``` @@ -222,8 +221,8 @@ 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 */ ``` @@ -237,7 +236,7 @@ To disable rule warnings in an entire file, put a `/* eslint-disable */` block c ```js /* eslint-disable */ -alert('foo'); +alert("foo"); ``` You can also disable or enable specific rules for an entire file: @@ -245,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): @@ -253,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`: @@ -322,13 +321,13 @@ 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 @@ -340,17 +339,17 @@ To disable rules inside of a [configuration file](./configuration-files#configur import { defineConfig } from "eslint/config"; export default defineConfig([ - { - rules: { - "no-unused-expressions": "error" - } - }, - { - files: ["*-test.js","*.spec.js"], - rules: { - "no-unused-expressions": "off" - } - } + { + rules: { + "no-unused-expressions": "error", + }, + }, + { + files: ["*-test.js", "*.spec.js"], + rules: { + "no-unused-expressions": "off", + }, + }, ]); ``` @@ -363,14 +362,14 @@ To disable all inline config comments, use the `noInlineConfig` setting in your import { defineConfig } from "eslint/config"; export default defineConfig([ - { - linterOptions: { - noInlineConfig: true - }, - rules: { - "no-unused-expressions": "error" - } - } + { + linterOptions: { + noInlineConfig: true, + }, + rules: { + "no-unused-expressions": "error", + }, + }, ]); ``` @@ -385,11 +384,11 @@ To report unused `eslint-disable` comments (those that disable rules which would import { defineConfig } from "eslint/config"; export default defineConfig([ - { - linterOptions: { - reportUnusedDisableDirectives: "error" - } - } + { + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, ]); ``` diff --git a/docs/src/use/core-concepts/glossary.md b/docs/src/use/core-concepts/glossary.md index c4b5e4443045..7b68c8ce7621 100644 --- a/docs/src/use/core-concepts/glossary.md +++ b/docs/src/use/core-concepts/glossary.md @@ -34,11 +34,11 @@ For example, this `eslint.config.js` file enables the `prefer-const` [rule](#rul ```js export default [ - { - rules: { - "prefer-const": "error", - }, - }, + { + rules: { + "prefer-const": "error", + }, + }, ]; ``` @@ -70,9 +70,9 @@ The library used by ESLint to parse [selector](#selector) syntax for querying [n 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_ +- `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. @@ -84,21 +84,21 @@ For example, the ESTree representation of the code `1 + 2;` would be an object r ```json { - "type": "ExpressionStatement", - "expression": { - "type": "BinaryExpression", - "left": { - "type": "Literal", - "value": 1, - "raw": "1" - }, - "operator": "+", - "right": { - "type": "Literal", - "value": 2, - "raw": "2" - } - } + "type": "ExpressionStatement", + "expression": { + "type": "BinaryExpression", + "left": { + "type": "Literal", + "value": 1, + "raw": "1" + }, + "operator": "+", + "right": { + "type": "Literal", + "value": 2, + "raw": "2" + } + } } ``` @@ -243,17 +243,17 @@ The following [config file](#config-file-configuration-file) overrides `no-unuse ```js export default [ - { - rules: { - "no-unused-expressions": "error" - } - }, - { - files: ["*.test.js"], - rules: { - "no-unused-expressions": "off" - } - } + { + rules: { + "no-unused-expressions": "error", + }, + }, + { + files: ["*.test.js"], + rules: { + "no-unused-expressions": "off", + }, + }, ]; ``` @@ -329,9 +329,9 @@ 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 +- `"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). diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 2d378193f1f4..5735659e984a 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

- 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 07 2025 21:16:31 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Mar 21 2025 20:35:07 GMT+0000 (Coordinated Universal Time)
diff --git a/docs/src/use/formatters/html-formatter-example.json b/docs/src/use/formatters/html-formatter-example.json index 6070c859e9ef..f2599b975ede 100644 --- a/docs/src/use/formatters/html-formatter-example.json +++ b/docs/src/use/formatters/html-formatter-example.json @@ -1,3 +1,3 @@ { - "layout": false -} \ No newline at end of file + "layout": false +} diff --git a/docs/src/use/getting-started.md b/docs/src/use/getting-started.md index 6354e2b1119d..15c7095e5121 100644 --- a/docs/src/use/getting-started.md +++ b/docs/src/use/getting-started.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: use eslint title: Getting Started order: 1 - --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} @@ -56,41 +55,41 @@ When you run `npm init @eslint/config`, you'll be asked a series of questions to For example, one of the questions is "Where does your code run?" If you select "Browser" then your configuration file will contain the definitions for global variables found in web browsers. Here's an example: ```js +import { defineConfig } from "eslint/config"; import globals from "globals"; -import pluginJs from "@eslint/js"; - +import js from "@eslint/js"; -/** @type {import('eslint').Linter.Config[]} */ -export default [ - {languageOptions: { globals: globals.browser }}, - pluginJs.configs.recommended, -]; +export default defineConfig([ + { files: ["**/*.js"], languageOptions: { globals: globals.browser } }, + { files: ["**/*.js"], plugins: { js }, extends: ["js/recommended"] }, +]); ``` -The `pluginJs.configs.recommended` object contains configuration to ensure that all of the rules marked as recommended on the [rules page](../rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. +The `"js/recommended"` configuration ensures all of the rules marked as recommended on the [rules page](../rules) will be turned on. Alternatively, you can use configurations that others have created by searching for "eslint-config" on [npmjs.com](https://www.npmjs.com/search?q=eslint-config). ESLint will not lint your code unless you extend from a shared configuration or explicitly turn rules on in your configuration. You can configure rules individually by defining a new object with a `rules` key, as in this example: ```js -import pluginJs from "@eslint/js"; - -export default [ - pluginJs.configs.recommended, - - { - rules: { - "no-unused-vars": "warn", - "no-undef": "warn" - } - } -]; +import { defineConfig } from "eslint/config"; +import js from "@eslint/js"; + +export default defineConfig([ + { files: ["**/*.js"], plugins: { js }, extends: ["js/recommended"] }, + + { + rules: { + "no-unused-vars": "warn", + "no-undef": "warn", + }, + }, +]); ``` The names `"no-unused-vars"` and `"no-undef"` are the names of [rules](../rules) in ESLint. The first value is the error level of the rule and can be 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 will be 1) +- "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) The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the configuration docs). @@ -125,27 +124,31 @@ Before you begin, you must already have a `package.json` file. If you don't, mak 2. Add an `eslint.config.js` file: - ```shell - # Create JavaScript configuration file - touch eslint.config.js - ``` + ```shell + # Create JavaScript configuration file + touch eslint.config.js + ``` 3. Add configuration to the `eslint.config.js` file. Refer to the [Configure ESLint documentation](configure/) to learn how to add rules, custom configurations, plugins, and more. - ```js - import pluginJs from "@eslint/js"; - - export default [ - pluginJs.configs.recommended, - - { - rules: { - "no-unused-vars": "warn", - "no-undef": "warn" - } - } - ]; - ``` + ```js + import { defineConfig } from "eslint/config"; + import js from "@eslint/js"; + + export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + rules: { + "no-unused-vars": "warn", + "no-undef": "warn", + }, + }, + ]); + ``` 4. Lint code using the ESLint CLI: @@ -154,14 +157,14 @@ Before you begin, you must already have a `package.json` file. If you don't, mak args: ["project-dir/", "file.js"] }) }} - For more information on the available CLI options, refer to [Command Line Interface](./command-line-interface). +For more information on the available CLI options, refer to [Command Line Interface](./command-line-interface). --- ## Next Steps -* Learn about [advanced configuration](configure/) of ESLint. -* Get familiar with the [command line options](command-line-interface). -* Explore [ESLint integrations](integrations) into other tools like editors, build systems, and more. -* Can't find just the right rule? Make your own [custom rule](../extend/custom-rules). -* Make ESLint even better by [contributing](../contribute/). +- Learn about [advanced configuration](configure/) of ESLint. +- Get familiar with the [command line options](command-line-interface). +- Explore [ESLint integrations](integrations) into other tools like editors, build systems, and more. +- Can't find just the right rule? Make your own [custom rule](../extend/custom-rules). +- Make ESLint even better by [contributing](../contribute/). diff --git a/docs/src/use/index.md b/docs/src/use/index.md index a5726c696b0b..f3882368ac97 100644 --- a/docs/src/use/index.md +++ b/docs/src/use/index.md @@ -44,12 +44,12 @@ The ESLint team is committed to making upgrading as easy and painless as possibl If you were using a prior version of ESLint, you can get help with the transition by reading: -* [migrating-to-1.0.0](migrating-to-1.0.0) -* [migrating-to-2.0.0](migrating-to-2.0.0) -* [migrating-to-3.0.0](migrating-to-3.0.0) -* [migrating-to-4.0.0](migrating-to-4.0.0) -* [migrating-to-5.0.0](migrating-to-5.0.0) -* [migrating-to-6.0.0](migrating-to-6.0.0) -* [migrating-to-7.0.0](migrating-to-7.0.0) -* [migrate-to-8.0.0](migrate-to-8.0.0) -* [migrate-to-9.x](migrate-to-9.0.0) +- [migrating-to-1.0.0](migrating-to-1.0.0) +- [migrating-to-2.0.0](migrating-to-2.0.0) +- [migrating-to-3.0.0](migrating-to-3.0.0) +- [migrating-to-4.0.0](migrating-to-4.0.0) +- [migrating-to-5.0.0](migrating-to-5.0.0) +- [migrating-to-6.0.0](migrating-to-6.0.0) +- [migrating-to-7.0.0](migrating-to-7.0.0) +- [migrate-to-8.0.0](migrate-to-8.0.0) +- [migrate-to-9.x](migrate-to-9.0.0) diff --git a/docs/src/use/integrations.md b/docs/src/use/integrations.md index 30e3ac8d981e..586814cbf5d0 100644 --- a/docs/src/use/integrations.md +++ b/docs/src/use/integrations.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: use eslint title: Integrations order: 8 - --- This page contains community projects that have integrated ESLint. The projects on this page are not maintained by the ESLint team. @@ -14,41 +13,41 @@ If you would like to recommend an integration to be added to this page, [submit ## Editors -* Sublime Text 3: - * [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint) - * [Build Next](https://github.com/albertosantini/sublimetext-buildnext) -* Vim: - * [ALE](https://github.com/dense-analysis/ale) - * [Syntastic](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) -* Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. -* Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) -* Eclipse IDE: [Tern ESLint linter](https://github.com/angelozerr/tern.java/wiki/Tern-Linter-ESLint) -* TextMate 2: - * [eslint.tmbundle](https://github.com/ryanfitzer/eslint.tmbundle) - * [javascript-eslint.tmbundle](https://github.com/natesilva/javascript-eslint.tmbundle) -* IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) -* Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) -* Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) -* Brackets: Included and [Brackets ESLint](https://github.com/brackets-userland/brackets-eslint) +- Sublime Text 3: + - [SublimeLinter-eslint](https://github.com/SublimeLinter/SublimeLinter-eslint) + - [Build Next](https://github.com/albertosantini/sublimetext-buildnext) +- Vim: + - [ALE](https://github.com/dense-analysis/ale) + - [Syntastic](https://github.com/vim-syntastic/syntastic/tree/master/syntax_checkers/javascript) +- Emacs: [Flycheck](http://www.flycheck.org/) supports ESLint with the [javascript-eslint](http://www.flycheck.org/en/latest/languages.html#javascript) checker. +- Eclipse Orion: ESLint is the [default linter](https://dev.eclipse.org/mhonarc/lists/orion-dev/msg02718.html) +- Eclipse IDE: [Tern ESLint linter](https://github.com/angelozerr/tern.java/wiki/Tern-Linter-ESLint) +- TextMate 2: + - [eslint.tmbundle](https://github.com/ryanfitzer/eslint.tmbundle) + - [javascript-eslint.tmbundle](https://github.com/natesilva/javascript-eslint.tmbundle) +- IntelliJ IDEA, WebStorm, PhpStorm, PyCharm, RubyMine, and other JetBrains IDEs: [How to use ESLint](https://www.jetbrains.com/help/webstorm/eslint.html) +- Visual Studio: [Linting JavaScript in VS](https://learn.microsoft.com/en-us/visualstudio/javascript/linting-javascript?view=vs-2022) +- Visual Studio Code: [ESLint Extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) +- Brackets: Included and [Brackets ESLint](https://github.com/brackets-userland/brackets-eslint) ## Build tools -* Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) -* Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) -* Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint) +- Grunt: [grunt-eslint](https://www.npmjs.com/package/grunt-eslint) +- Webpack: [eslint-webpack-plugin](https://www.npmjs.com/package/eslint-webpack-plugin) +- Rollup: [@rollup/plugin-eslint](https://www.npmjs.com/package/@rollup/plugin-eslint) ## Command Line Tools -* [ESLint Watch](https://www.npmjs.com/package/eslint-watch) -* [Code Climate CLI](https://github.com/codeclimate/codeclimate) -* [ESLint Nibble](https://github.com/IanVS/eslint-nibble) +- [ESLint Watch](https://www.npmjs.com/package/eslint-watch) +- [Code Climate CLI](https://github.com/codeclimate/codeclimate) +- [ESLint Nibble](https://github.com/IanVS/eslint-nibble) ## Source Control -* [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) -* [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) -* [overcommit Git hook manager](https://github.com/brigade/overcommit) -* [Mega-Linter](https://megalinter.io/latest/): Linters aggregator for CI, [embedding eslint](https://megalinter.io/latest/descriptors/javascript_eslint/) +- [Git Precommit Hook](https://coderwall.com/p/zq8jlq/eslint-pre-commit-hook) +- [Git pre-commit hook that only lints staged changes](https://gist.github.com/dahjelle/8ddedf0aebd488208a9a7c829f19b9e8) +- [overcommit Git hook manager](https://github.com/brigade/overcommit) +- [Mega-Linter](https://megalinter.io/latest/): Linters aggregator for CI, [embedding eslint](https://megalinter.io/latest/descriptors/javascript_eslint/) ## Other Integration Lists diff --git a/docs/src/use/migrate-to-8.0.0.md b/docs/src/use/migrate-to-8.0.0.md index 36844396600c..32332889a8b7 100644 --- a/docs/src/use/migrate-to-8.0.0.md +++ b/docs/src/use/migrate-to-8.0.0.md @@ -10,26 +10,26 @@ The lists below are ordered roughly by the number of users each change is expect ### Breaking changes for users -* [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) -* [Removed `codeframe` and `table` formatters](#removed-formatters) -* [`comma-dangle` rule schema is stricter](#comma-dangle) -* [Unused disable directives are now fixable](#directives) -* [`eslint:recommended` has been updated](#eslint-recommended) +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [Removed `codeframe` and `table` formatters](#removed-formatters) +- [`comma-dangle` rule schema is stricter](#comma-dangle) +- [Unused disable directives are now fixable](#directives) +- [`eslint:recommended` has been updated](#eslint-recommended) ### Breaking changes for plugin developers -* [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) -* [Rules require `meta.hasSuggestions` to provide suggestions](#suggestions) -* [Rules require `meta.fixable` to provide fixes](#fixes) -* [`SourceCode#getComments()` fails in `RuleTester`](#get-comments) -* [Changes to shorthand property AST format](#ast-format) +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [Rules require `meta.hasSuggestions` to provide suggestions](#suggestions) +- [Rules require `meta.fixable` to provide fixes](#fixes) +- [`SourceCode#getComments()` fails in `RuleTester`](#get-comments) +- [Changes to shorthand property AST format](#ast-format) ### Breaking changes for integration developers -* [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) -* [The `CLIEngine` class has been removed](#remove-cliengine) -* [The `linter` object has been removed](#remove-linter) -* [The `/lib` entrypoint has been removed](#remove-lib) +- [Node.js 10, 13, and 15 are no longer supported](#drop-old-node) +- [The `CLIEngine` class has been removed](#remove-cliengine) +- [The `linter` object has been removed](#remove-linter) +- [The `/lib` entrypoint has been removed](#remove-lib) --- @@ -37,9 +37,9 @@ The lists below are ordered roughly by the number of users each change is expect Node.js 10, 13, 15 all reached end of life either in 2020 or early 2021. ESLint is officially dropping support for these versions of Node.js starting with ESLint v8.0.0. ESLint now supports the following versions of Node.js: -* Node.js 12.22 and above -* Node.js 14 and above -* Node.js 16 and above +- Node.js 12.22 and above +- Node.js 14 and above +- Node.js 16 and above **To address:** Make sure you upgrade to at least Node.js `12.22.0` when using ESLint v8.0.0. One important thing to double check is the Node.js version supported by your editor when using ESLint via editor integrations. If you are unable to upgrade, we recommend continuing to use ESLint 7 until you are able to upgrade Node.js. @@ -59,9 +59,9 @@ In ESLint v7.0.0, the `comma-dangle` rule could be configured like this without ```json { - "rules": { - "comma-dangle": ["error", "never", { "arrays": "always" }] - } + "rules": { + "comma-dangle": ["error", "never", { "arrays": "always" }] + } } ``` @@ -71,7 +71,7 @@ With this configuration, the rule would ignore the third element in the array be ```json { - "comma-dangle": ["error", "never"], + "comma-dangle": ["error", "never"] } ``` @@ -79,13 +79,16 @@ or ```json { - "comma-dangle": ["error", { - "arrays": "never", - "objects": "never", - "imports": "never", - "exports": "never", - "functions": "never" - }] + "comma-dangle": [ + "error", + { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "never" + } + ] } ``` @@ -103,10 +106,10 @@ In ESLint v7.0.0, using both `--report-unused-disable-directives` and `--fix` on Four new rules have been enabled in the `eslint:recommended` preset. -* [`no-loss-of-precision`](../rules/no-loss-of-precision) -* [`no-nonoctal-decimal-escape`](../rules/no-nonoctal-decimal-escape) -* [`no-unsafe-optional-chaining`](../rules/no-unsafe-optional-chaining) -* [`no-useless-backreference`](../rules/no-useless-backreference) +- [`no-loss-of-precision`](../rules/no-loss-of-precision) +- [`no-nonoctal-decimal-escape`](../rules/no-nonoctal-decimal-escape) +- [`no-unsafe-optional-chaining`](../rules/no-unsafe-optional-chaining) +- [`no-useless-backreference`](../rules/no-useless-backreference) **To address:** Fix errors or disable these rules. @@ -120,12 +123,12 @@ In ESLint v7.0.0, rules that [provided suggestions](../extend/custom-rules#provi ```js module.exports = { - meta: { - hasSuggestions: true - }, - create(context) { - // your rule - } + meta: { + hasSuggestions: true, + }, + create(context) { + // your rule + }, }; ``` @@ -140,8 +143,8 @@ In ESLint v7.0.0, rules that were written as a function (rather than object) wer **To address:** If your rule makes fixes and is written as a function, such as: ```js -module.exports = function(context) { - // your rule +module.exports = function (context) { + // your rule }; ``` @@ -149,12 +152,12 @@ Then rewrite your rule in this format: ```js module.exports = { - meta: { - fixable: "code" // or "whitespace" - }, - create(context) { - // your rule - } + meta: { + fixable: "code", // or "whitespace" + }, + create(context) { + // your rule + }, }; ``` @@ -183,7 +186,7 @@ ESLint v8.0.0 includes an upgrade to Espree v8.0.0 to support new syntax. This E ```js const version = 8; const x = { - version + version, }; ``` @@ -191,19 +194,19 @@ This code creates a property node that looks like this: ```json { - "type": "Property", - "method": false, - "shorthand": true, - "computed": false, - "key": { - "type": "Identifier", - "name": "version" - }, - "kind": "init", - "value": { - "type": "Identifier", - "name": "version" - } + "type": "Property", + "method": false, + "shorthand": true, + "computed": false, + "key": { + "type": "Identifier", + "name": "version" + }, + "kind": "init", + "value": { + "type": "Identifier", + "name": "version" + } } ``` @@ -212,7 +215,7 @@ Note that both the `key` and the `value` properties contain the same information ```js // true in ESLint v7.x, false in ESLint v8.0.0 if (propertyNode.key === propertyNode.value) { - // do something + // do something } ``` @@ -240,14 +243,14 @@ The `CLIEngine` class has been removed and replaced by the [`ESLint` class](../i | `isPathIgnored(filePath)` | `isPathIgnored(filePath)` | | `static outputFixes(results)` | `static outputFixes(results)` | | `static getErrorResults(results)` | `static getErrorResults(results)` | -| `static getFormatter(name)` | (removed â€ģ1) | +| `static getFormatter(name)` | (removed â€ģ1) | | `addPlugin(pluginId, definition)` | the `plugins` constructor option | -| `getRules()` | (removed â€ģ2) | -| `resolveFileGlobPatterns()` | (removed â€ģ3) | +| `getRules()` | (removed â€ģ2) | +| `resolveFileGlobPatterns()` | (removed â€ģ3) | -* â€ģ1 The `engine.getFormatter()` method currently returns the object of loaded packages as-is, which made it difficult to add new features to formatters for backward compatibility reasons. The new `eslint.loadFormatter()` method returns an adapter object that wraps the object of loaded packages, to ease the process of adding new features. Additionally, the adapter object has access to the `ESLint` instance to calculate default data (using loaded plugin rules to make `rulesMeta`, for example). As a result, the `ESLint` class only implements an instance version of the `loadFormatter()` method. -* â€ģ2 The `CLIEngine#getRules()` method had side effects and so was removed. If you were using `CLIEngine#getRules()` to retrieve meta information about rules based on linting results, use `ESLint#getRulesMetaForResults()` instead. If you were using `CLIEngine#getRules()` to retrieve all built-in rules, import `builtinRules` from `eslint/use-at-your-own-risk` for an unsupported API that allows access to internal rules. -* â€ģ3 Since ESLint v6.0.0, ESLint uses different logic from the `resolveFileGlobPatterns()` method to iterate files, making this method obsolete. +- â€ģ1 The `engine.getFormatter()` method currently returns the object of loaded packages as-is, which made it difficult to add new features to formatters for backward compatibility reasons. The new `eslint.loadFormatter()` method returns an adapter object that wraps the object of loaded packages, to ease the process of adding new features. Additionally, the adapter object has access to the `ESLint` instance to calculate default data (using loaded plugin rules to make `rulesMeta`, for example). As a result, the `ESLint` class only implements an instance version of the `loadFormatter()` method. +- â€ģ2 The `CLIEngine#getRules()` method had side effects and so was removed. If you were using `CLIEngine#getRules()` to retrieve meta information about rules based on linting results, use `ESLint#getRulesMetaForResults()` instead. If you were using `CLIEngine#getRules()` to retrieve all built-in rules, import `builtinRules` from `eslint/use-at-your-own-risk` for an unsupported API that allows access to internal rules. +- â€ģ3 Since ESLint v6.0.0, ESLint uses different logic from the `resolveFileGlobPatterns()` method to iterate files, making this method obsolete. **Related issue(s):** [RFC80](https://github.com/eslint/rfcs/tree/main/designs/2021-package-exports), [#14716](https://github.com/eslint/eslint/pull/14716), [#13654](https://github.com/eslint/eslint/issues/13654) diff --git a/docs/src/use/migrate-to-9.0.0.md b/docs/src/use/migrate-to-9.0.0.md index 88f955bc10e5..0e72be56d67c 100644 --- a/docs/src/use/migrate-to-9.0.0.md +++ b/docs/src/use/migrate-to-9.0.0.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: use eslint title: Migrate to v9.x order: 9 - --- ESLint v9.0.0 is a major release of ESLint, and as such, has several breaking changes that you need to be aware of. This guide is intended to walk you through the breaking changes. @@ -16,45 +15,45 @@ The lists below are ordered roughly by the number of users each change is expect ### Breaking changes for users -* [Node.js < v18.18, v19 are no longer supported](#drop-old-node) -* [New default config format (`eslint.config.js`)](#flat-config) -* [Removed multiple formatters](#removed-formatters) -* [Removed `require-jsdoc` and `valid-jsdoc` rules](#remove-jsdoc-rules) -* [`eslint:recommended` has been updated](#eslint-recommended) -* [`--quiet` no longer runs rules set to `"warn"`](#quiet-warn) -* [`--output-file` now writes a file to disk even with an empty output](#output-file) -* [Change in behavior when no patterns are passed to CLI](#cli-empty-patterns) -* [`/* eslint */` comments with only severity now retain options from the config file](#eslint-comment-options) -* [Multiple `/* eslint */` comments for the same rule are now disallowed](#multiple-eslint-comments) -* [Stricter `/* exported */` parsing](#exported-parsing) -* [`no-constructor-return` and `no-sequences` rule schemas are stricter](#stricter-rule-schemas) -* [New checks in `no-implicit-coercion` by default](#no-implicit-coercion) -* [Case-sensitive flags in `no-invalid-regexp`](#no-invalid-regexp) -* [`varsIgnorePattern` option of `no-unused-vars` no longer applies to catch arguments](#vars-ignore-pattern) -* [`no-restricted-imports` now accepts multiple config entries with the same `name`](#no-restricted-imports) -* [`"eslint:recommended"` and `"eslint:all"` strings no longer accepted in flat config](#string-config) -* [`no-inner-declarations` has a new default behavior with a new option](#no-inner-declarations) -* [`no-unused-vars` now defaults `caughtErrors` to `"all"`](#no-unused-vars) -* [`no-useless-computed-key` flags unnecessary computed member names in classes by default](#no-useless-computed-key) -* [`camelcase` allow option only accepts an array of strings](#camelcase) +- [Node.js < v18.18, v19 are no longer supported](#drop-old-node) +- [New default config format (`eslint.config.js`)](#flat-config) +- [Removed multiple formatters](#removed-formatters) +- [Removed `require-jsdoc` and `valid-jsdoc` rules](#remove-jsdoc-rules) +- [`eslint:recommended` has been updated](#eslint-recommended) +- [`--quiet` no longer runs rules set to `"warn"`](#quiet-warn) +- [`--output-file` now writes a file to disk even with an empty output](#output-file) +- [Change in behavior when no patterns are passed to CLI](#cli-empty-patterns) +- [`/* eslint */` comments with only severity now retain options from the config file](#eslint-comment-options) +- [Multiple `/* eslint */` comments for the same rule are now disallowed](#multiple-eslint-comments) +- [Stricter `/* exported */` parsing](#exported-parsing) +- [`no-constructor-return` and `no-sequences` rule schemas are stricter](#stricter-rule-schemas) +- [New checks in `no-implicit-coercion` by default](#no-implicit-coercion) +- [Case-sensitive flags in `no-invalid-regexp`](#no-invalid-regexp) +- [`varsIgnorePattern` option of `no-unused-vars` no longer applies to catch arguments](#vars-ignore-pattern) +- [`no-restricted-imports` now accepts multiple config entries with the same `name`](#no-restricted-imports) +- [`"eslint:recommended"` and `"eslint:all"` strings no longer accepted in flat config](#string-config) +- [`no-inner-declarations` has a new default behavior with a new option](#no-inner-declarations) +- [`no-unused-vars` now defaults `caughtErrors` to `"all"`](#no-unused-vars) +- [`no-useless-computed-key` flags unnecessary computed member names in classes by default](#no-useless-computed-key) +- [`camelcase` allow option only accepts an array of strings](#camelcase) ### Breaking changes for plugin developers -* [Node.js < v18.18, v19 are no longer supported](#drop-old-node) -* [Removed multiple `context` methods](#removed-context-methods) -* [Removed `sourceCode.getComments()`](#removed-sourcecode-getcomments) -* [Removed `CodePath#currentSegments`](#removed-codepath-currentsegments) -* [Code paths are now precalculated](#codepath-precalc) -* [Function-style rules are no longer supported](#drop-function-style-rules) -* [`meta.schema` is required for rules with options](#meta-schema-required) -* [`FlatRuleTester` is now `RuleTester`](#flat-rule-tester) -* [Stricter `RuleTester` checks](#stricter-rule-tester) +- [Node.js < v18.18, v19 are no longer supported](#drop-old-node) +- [Removed multiple `context` methods](#removed-context-methods) +- [Removed `sourceCode.getComments()`](#removed-sourcecode-getcomments) +- [Removed `CodePath#currentSegments`](#removed-codepath-currentsegments) +- [Code paths are now precalculated](#codepath-precalc) +- [Function-style rules are no longer supported](#drop-function-style-rules) +- [`meta.schema` is required for rules with options](#meta-schema-required) +- [`FlatRuleTester` is now `RuleTester`](#flat-rule-tester) +- [Stricter `RuleTester` checks](#stricter-rule-tester) ### Breaking changes for integration developers -* [Node.js < v18.18, v19 are no longer supported](#drop-old-node) -* [`FlatESLint` is now `ESLint`](#flat-eslint) -* [`Linter` now expects flat config format](#flat-linter) +- [Node.js < v18.18, v19 are no longer supported](#drop-old-node) +- [`FlatESLint` is now `ESLint`](#flat-eslint) +- [`Linter` now expects flat config format](#flat-linter) --- @@ -62,9 +61,9 @@ The lists below are ordered roughly by the number of users each change is expect ESLint is officially dropping support for these versions of Node.js starting with ESLint v9.0.0. ESLint now supports the following versions of Node.js: -* Node.js v18.18.0 and above -* Node.js v20.9.0 and above -* Node.js v21 and above +- Node.js v18.18.0 and above +- Node.js v20.9.0 and above +- Node.js v21 and above **To address:** Make sure you upgrade to at least Node.js v18.18.0 when using ESLint v9.0.0. One important thing to double check is the Node.js version supported by your editor when using ESLint via editor integrations. If you are unable to upgrade, we recommend continuing to use ESLint v8.56.0 until you are able to upgrade Node.js. @@ -82,15 +81,15 @@ As announced in our [blog post](/blog/2023/10/flat-config-rollout-plans/), in ES ESLint v9.0.0 has removed the following formatters from the core: -| **Removed Formatter** | **Replacement npm Package** | -|-----------------------|-----------------------------| -| `checkstyle` | `eslint-formatter-checkstyle` | -| `compact` | `eslint-formatter-compact` | -| `jslint-xml` | `eslint-formatter-jslint-xml` | -| `junit` | `eslint-formatter-junit` | -| `tap` | `eslint-formatter-tap` | -| `unix` | `eslint-formatter-unix` | -| `visualstudio` | `eslint-formatter-visualstudio` | +| **Removed Formatter** | **Replacement npm Package** | +| --------------------- | ------------------------------- | +| `checkstyle` | `eslint-formatter-checkstyle` | +| `compact` | `eslint-formatter-compact` | +| `jslint-xml` | `eslint-formatter-jslint-xml` | +| `junit` | `eslint-formatter-junit` | +| `tap` | `eslint-formatter-tap` | +| `unix` | `eslint-formatter-unix` | +| `visualstudio` | `eslint-formatter-visualstudio` | **To address:** If you are using any of these formatters via the `-f` command line flag, you'll need to install the respective package for the formatter. @@ -108,17 +107,17 @@ The `require-jsdoc` and `valid-jsdoc` rules have been removed in ESLint v9.0.0. Four new rules have been enabled in `eslint:recommended`: -* [`no-constant-binary-expression`](../rules/no-constant-binary-expression) -* [`no-empty-static-block`](../rules/no-empty-static-block) -* [`no-new-native-nonconstructor`](../rules/no-new-native-nonconstructor) -* [`no-unused-private-class-members`](../rules/no-unused-private-class-members) +- [`no-constant-binary-expression`](../rules/no-constant-binary-expression) +- [`no-empty-static-block`](../rules/no-empty-static-block) +- [`no-new-native-nonconstructor`](../rules/no-new-native-nonconstructor) +- [`no-unused-private-class-members`](../rules/no-unused-private-class-members) Additionally, the following rules have been removed from `eslint:recommended`: -* [`no-extra-semi`](../rules/no-extra-semi) -* [`no-inner-declarations`](../rules/no-inner-declarations) -* [`no-mixed-spaces-and-tabs`](../rules/no-mixed-spaces-and-tabs) -* [`no-new-symbol`](../rules/no-new-symbol) +- [`no-extra-semi`](../rules/no-extra-semi) +- [`no-inner-declarations`](../rules/no-inner-declarations) +- [`no-mixed-spaces-and-tabs`](../rules/no-mixed-spaces-and-tabs) +- [`no-new-symbol`](../rules/no-new-symbol) **To address:** Fix errors or disable these rules. @@ -146,8 +145,8 @@ Prior to ESLint v9.0.0, the `--output-file` flag would skip writing a file to di Prior to ESLint v9.0.0, running the ESLint CLI without any file or directory patterns would result in no files being linted and would exit with code 0. This was confusing because it wasn't clear that nothing had actually happened. In ESLint v9.0.0, this behavior has been updated: -* **Flat config.** If you are using flat config, you can run `npx eslint` or `eslint` (if globally installed) and ESLint will assume you want to lint the current directory. Effectively, passing no patterns is equivalent to passing `.`. -* **eslintrc.** If you are using the deprecated eslintrc config, you'll now receive an error when running the CLI without any patterns. +- **Flat config.** If you are using flat config, you can run `npx eslint` or `eslint` (if globally installed) and ESLint will assume you want to lint the current directory. Effectively, passing no patterns is equivalent to passing `.`. +- **eslintrc.** If you are using the deprecated eslintrc config, you'll now receive an error when running the CLI without any patterns. **To address:** In most cases, no change is necessary, and you may find some locations where you thought ESLint was running but it wasn't. If you'd like to keep the v8.x behavior, where passing no patterns results in ESLint exiting with code 0, add the `--pass-on-no-patterns` flag to the CLI call. @@ -164,11 +163,13 @@ For example, if you have the following config file: ```js // eslint.config.js -export default [{ - rules: { - curly: ["error", "multi"] - } -}]; +export default [ + { + rules: { + curly: ["error", "multi"], + }, + }, +]; ``` and the following configuration comment: @@ -201,7 +202,7 @@ Prior to ESLint v9.0.0, if the file being linted contained multiple `/* eslint * /* eslint semi: ["error", "always"] */ /* eslint semi: ["error", "never"] */ -foo() // valid, because the configuration is "never" +foo(); // valid, because the configuration is "never" ``` In ESLint v9.0.0, the first one is applied, while the others are reported as lint errors: @@ -210,7 +211,7 @@ In ESLint v9.0.0, the first one is applied, while the others are reported as lin /* eslint semi: ["error", "always"] */ /* eslint semi: ["error", "never"] */ // error: Rule "semi" is already configured by another configuration comment in the preceding code. This configuration is ignored. -foo() // error: Missing semicolon +foo(); // error: Missing semicolon ``` **To address:** Remove duplicate `/* eslint */` comments. @@ -247,15 +248,15 @@ In previous versions of ESLint, `no-constructor-return` and `no-sequences` rules This has been fixed in ESLint v9.0.0: -* The `no-constructor-return` rule does not accept any options. -* The `no-sequences` rule can take one option, an object with a property `"allowInParentheses"` (boolean). +- The `no-constructor-return` rule does not accept any options. +- The `no-sequences` rule can take one option, an object with a property `"allowInParentheses"` (boolean). ```json { - "rules": { - "no-constructor-return": ["error"], - "no-sequences": ["error", { "allowInParentheses": false }] - } + "rules": { + "no-constructor-return": ["error"], + "no-sequences": ["error", { "allowInParentheses": false }] + } } ``` @@ -276,9 +277,9 @@ foo - 0; ```json { - "rules": { - "no-implicit-coercion": [2, { "allow": ["-", "- -"] } ], - } + "rules": { + "no-implicit-coercion": [2, { "allow": ["-", "- -"] }] + } } ``` @@ -300,11 +301,11 @@ In previous versions of ESLint, the `varsIgnorePattern` option of `no-unused-var /*eslint no-unused-vars: ["error", { "caughtErrors": "all", "varsIgnorePattern": "^err" }]*/ try { - //... -} catch (err) { // 'err' will be reported. - console.error("errors"); + //... +} catch (err) { + // 'err' will be reported. + console.error("errors"); } - ``` **To address:** If you want to specify ignore patterns for `catch` clause variable names, use the `caughtErrorsIgnorePattern` option in addition to `varsIgnorePattern`. @@ -352,10 +353,7 @@ In ESLint v8.x, `eslint.config.js` could refer to `"eslint:recommended"` and `"e ```js // eslint.config.js -export default [ - "eslint:recommended", - "eslint:all" -]; +export default ["eslint:recommended", "eslint:all"]; ``` In ESLint v9.0.0, this format is no longer supported and will result in an error. @@ -366,10 +364,7 @@ In ESLint v9.0.0, this format is no longer supported and will result in an error // eslint.config.js import js from "@eslint/js"; -export default [ - js.configs.recommended, - js.configs.all -]; +export default [js.configs.recommended, js.configs.all]; ``` **Related issue(s):** [#17488](https://github.com/eslint/eslint/issues/17488) @@ -383,7 +378,7 @@ ESLint v9.0.0 introduces a new option in `no-inner-declarations` rule called `bl "use strict"; if (test) { - function foo () { } // no error + function foo() {} // no error } ``` @@ -399,9 +394,9 @@ It now defaults to `"all"` to check caught errors for being used. ```js /*eslint no-unused-vars: "error"*/ -try {} -catch (error) { - // 'error' is defined but never used +try { +} catch (error) { + // 'error' is defined but never used } ``` @@ -410,9 +405,9 @@ Otherwise, delete the unused caught errors. ```js /*eslint no-unused-vars: "error"*/ -try {} -catch { - // no error +try { +} catch { + // no error } ``` @@ -427,7 +422,7 @@ The effect of this change is that unnecessary computed member names in classes w /*eslint no-useless-computed-key: "error"*/ class SomeClass { - ["someMethod"]() {} // ok in ESLint v8, error in ESLint v9. + ["someMethod"]() {} // ok in ESLint v8, error in ESLint v9. } ``` @@ -447,38 +442,38 @@ Previously the camelcase rule didn't enforce the `allow` option to be an array o ESLint v9.0.0 removes multiple deprecated methods from the `context` object and moves them onto the `SourceCode` object: -|**Removed on `context`**|**Replacement(s) on `SourceCode`**| -|-----------------------|--------------------------| -|`context.getSource()`|`sourceCode.getText()`| -|`context.getSourceLines()`|`sourceCode.getLines()`| -|`context.getAllComments()`|`sourceCode.getAllComments()`| -|`context.getNodeByRangeIndex()`|`sourceCode.getNodeByRangeIndex()`| -|`context.getComments()`|`sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, `sourceCode.getCommentsInside()`| -|`context.getCommentsBefore()`|`sourceCode.getCommentsBefore()`| -|`context.getCommentsAfter()`|`sourceCode.getCommentsAfter()`| -|`context.getCommentsInside()`|`sourceCode.getCommentsInside()`| -|`context.getJSDocComment()`|`sourceCode.getJSDocComment()`| -|`context.getFirstToken()`|`sourceCode.getFirstToken()`| -|`context.getFirstTokens()`|`sourceCode.getFirstTokens()`| -|`context.getLastToken()`|`sourceCode.getLastToken()`| -|`context.getLastTokens()`|`sourceCode.getLastTokens()`| -|`context.getTokenAfter()`|`sourceCode.getTokenAfter()`| -|`context.getTokenBefore()`|`sourceCode.getTokenBefore()`| -|`context.getTokenByRangeStart()`|`sourceCode.getTokenByRangeStart()`| -|`context.getTokens()`|`sourceCode.getTokens()`| -|`context.getTokensAfter()`|`sourceCode.getTokensAfter()`| -|`context.getTokensBefore()`|`sourceCode.getTokensBefore()`| -|`context.getTokensBetween()`|`sourceCode.getTokensBetween()`| -|`context.parserServices`|`sourceCode.parserServices`| -|`context.getDeclaredVariables()`|`sourceCode.getDeclaredVariables()`| +| **Removed on `context`** | **Replacement(s) on `SourceCode`** | +| -------------------------------- | --------------------------------------------------------------------------------------------------- | +| `context.getSource()` | `sourceCode.getText()` | +| `context.getSourceLines()` | `sourceCode.getLines()` | +| `context.getAllComments()` | `sourceCode.getAllComments()` | +| `context.getNodeByRangeIndex()` | `sourceCode.getNodeByRangeIndex()` | +| `context.getComments()` | `sourceCode.getCommentsBefore()`, `sourceCode.getCommentsAfter()`, `sourceCode.getCommentsInside()` | +| `context.getCommentsBefore()` | `sourceCode.getCommentsBefore()` | +| `context.getCommentsAfter()` | `sourceCode.getCommentsAfter()` | +| `context.getCommentsInside()` | `sourceCode.getCommentsInside()` | +| `context.getJSDocComment()` | `sourceCode.getJSDocComment()` | +| `context.getFirstToken()` | `sourceCode.getFirstToken()` | +| `context.getFirstTokens()` | `sourceCode.getFirstTokens()` | +| `context.getLastToken()` | `sourceCode.getLastToken()` | +| `context.getLastTokens()` | `sourceCode.getLastTokens()` | +| `context.getTokenAfter()` | `sourceCode.getTokenAfter()` | +| `context.getTokenBefore()` | `sourceCode.getTokenBefore()` | +| `context.getTokenByRangeStart()` | `sourceCode.getTokenByRangeStart()` | +| `context.getTokens()` | `sourceCode.getTokens()` | +| `context.getTokensAfter()` | `sourceCode.getTokensAfter()` | +| `context.getTokensBefore()` | `sourceCode.getTokensBefore()` | +| `context.getTokensBetween()` | `sourceCode.getTokensBetween()` | +| `context.parserServices` | `sourceCode.parserServices` | +| `context.getDeclaredVariables()` | `sourceCode.getDeclaredVariables()` | In addition to the methods in the above table, there are several other methods that are also moved but required different method signatures: -|**Removed on `context`**|**Replacement(s) on `SourceCode`**| -|-----------------------|--------------------------| -|`context.getAncestors()`|`sourceCode.getAncestors(node)`| -|`context.getScope()`|`sourceCode.getScope(node)`| -|`context.markVariableAsUsed(name)`|`sourceCode.markVariableAsUsed(name, node)`| +| **Removed on `context`** | **Replacement(s) on `SourceCode`** | +| ---------------------------------- | ------------------------------------------- | +| `context.getAncestors()` | `sourceCode.getAncestors(node)` | +| `context.getScope()` | `sourceCode.getScope(node)` | +| `context.markVariableAsUsed(name)` | `sourceCode.markVariableAsUsed(name, node)` | **To address:** Use the automated upgrade tool as recommended in the [blog post](https://eslint.org/blog/2023/09/preparing-custom-rules-eslint-v9/#automatically-update-your-rules). @@ -508,8 +503,8 @@ ESLint v9.0.0 now precalculates code path information before the traversal used **To address:** If you are accessing any array properties on `CodePath` or `CodePathSegment`, you'll need to update your code. Specifically: -* If you are checking the `length` of any array properties, ensure you are using relative comparison operators like `<`, `>`, `<=`, and `>=` instead of equals. -* If you are accessing the `nextSegments`, `prevSegments`, `allNextSegments`, or `allPrevSegments` properties on a `CodePathSegment`, or `CodePath#childCodePaths`, verify that your code will still work as expected. To be backwards compatible, consider moving the logic that checks these properties into `onCodePathEnd`. +- If you are checking the `length` of any array properties, ensure you are using relative comparison operators like `<`, `>`, `<=`, and `>=` instead of equals. +- If you are accessing the `nextSegments`, `prevSegments`, `allNextSegments`, or `allPrevSegments` properties on a `CodePathSegment`, or `CodePath#childCodePaths`, verify that your code will still work as expected. To be backwards compatible, consider moving the logic that checks these properties into `onCodePathEnd`. **Related Issues(s):** [#16999](https://github.com/eslint/eslint/issues/16999) @@ -529,9 +524,9 @@ As of ESLint v9.0.0, an error will be thrown if any options are [passed](../use/ **To address:** -* If your rule expects [options](../extend/custom-rules#accessing-options-passed-to-a-rule), set [`meta.schema`](../extend/custom-rules#options-schemas) property to a JSON Schema format description of the rule’s options. This schema will be used by ESLint to validate configured options and prevent invalid or unexpected inputs to your rule. -* If your rule doesn't expect any options, there is no action required. This change ensures that end users will not mistakenly configure options for rules that don't expect options. -* **(not recommended)** you can also set `meta.schema` to `false` to disable this validation, but it is highly recommended to provide a schema if the rule expects options and omit the schema (or set `[]`) if the rule doesn't expect options so that ESLint can ensure that your users' configurations are valid. +- If your rule expects [options](../extend/custom-rules#accessing-options-passed-to-a-rule), set [`meta.schema`](../extend/custom-rules#options-schemas) property to a JSON Schema format description of the rule’s options. This schema will be used by ESLint to validate configured options and prevent invalid or unexpected inputs to your rule. +- If your rule doesn't expect any options, there is no action required. This change ensures that end users will not mistakenly configure options for rules that don't expect options. +- **(not recommended)** you can also set `meta.schema` to `false` to disable this validation, but it is highly recommended to provide a schema if the rule expects options and omit the schema (or set `[]`) if the rule doesn't expect options so that ESLint can ensure that your users' configurations are valid. The [eslint-plugin/require-meta-schema](https://github.com/eslint-community/eslint-plugin-eslint-plugin/blob/main/docs/rules/require-meta-schema.md) rule can help enforce that rules have schemas when required. @@ -543,47 +538,47 @@ As announced in our [blog post](/blog/2023/10/flat-config-rollout-plans/), the t **To address:** Update your rule tests to use the new `RuleTester`. To do so, here are some of the common changes you'll need to make: -* **Be aware of new defaults for `ecmaVersion` and `sourceType`.** By default, `RuleTester` uses the flat config default of `ecmaVersion: "latest"` and `sourceType: "module"`. This may cause some tests to break if they were expecting the old default of `ecmaVersion: 5` and `sourceType: "script"`. If you'd like to use the old default, you'll need to manually specify that in your `RuleTester` like this: +- **Be aware of new defaults for `ecmaVersion` and `sourceType`.** By default, `RuleTester` uses the flat config default of `ecmaVersion: "latest"` and `sourceType: "module"`. This may cause some tests to break if they were expecting the old default of `ecmaVersion: 5` and `sourceType: "script"`. If you'd like to use the old default, you'll need to manually specify that in your `RuleTester` like this: ```js // use eslintrc defaults const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ``` -* **Change `parserOptions` to `languageOptions`.** If you're setting `ecmaVersion` or `sourceType` on your tests, move those from `parserOptions` to `languageOptions`, like this: +- **Change `parserOptions` to `languageOptions`.** If you're setting `ecmaVersion` or `sourceType` on your tests, move those from `parserOptions` to `languageOptions`, like this: ```js ruleTester.run("my-rule", myRule, { - valid: [ - { - code: "foo", - parserOptions: { - ecmaVersion: 6 - } - } - ] + valid: [ + { + code: "foo", + parserOptions: { + ecmaVersion: 6, + }, + }, + ], }); // becomes ruleTester.run("my-rule", myRule, { - valid: [ - { - code: "foo", - languageOptions: { - ecmaVersion: 6 - } - } - ] + valid: [ + { + code: "foo", + languageOptions: { + ecmaVersion: 6, + }, + }, + ], }); ``` -* **Translate other config keys.** Keys such as `env` and `parser` that used to run on the eslintrc config system must be translated into the flat config system. Please refer to the [Configuration Migration Guide](configure/migration-guide) for details on translating other keys you may be using. +- **Translate other config keys.** Keys such as `env` and `parser` that used to run on the eslintrc config system must be translated into the flat config system. Please refer to the [Configuration Migration Guide](configure/migration-guide) for details on translating other keys you may be using. **Related Issues(s):** [#13481](https://github.com/eslint/eslint/issues/13481) @@ -591,7 +586,7 @@ As announced in our [blog post](/blog/2023/10/flat-config-rollout-plans/), the t In order to aid in the development of high-quality custom rules that are free from common bugs, ESLint v9.0.0 implements several changes to `RuleTester`: -1. **Test case `output` must be different from `code`.** In ESLint v8.x, if `output` is the same as `code`, it asserts that there was no autofix. When looking at a test case, it's not always immediately clear whether `output` differs from `code`, especially if the strings are longer or multiline, making it difficult for developers to determine whether or not the test case expects an autofix. In ESLint v9.0.0, to avoid this ambiguity, `RuleTester` now throws an error if the test `output` has the same value as the test `code`. Therefore, specifying `output` now necessarily means that the test case expects an autofix and asserts its result. If the test case doesn't expect an autofix, omit the `output` property or set it to `null`. This asserts that there was no autofix. +1. **Test case `output` must be different from `code`.** In ESLint v8.x, if `output` is the same as `code`, it asserts that there was no autofix. When looking at a test case, it's not always immediately clear whether `output` differs from `code`, especially if the strings are longer or multiline, making it difficult for developers to determine whether or not the test case expects an autofix. In ESLint v9.0.0, to avoid this ambiguity, `RuleTester` now throws an error if the test `output` has the same value as the test `code`. Therefore, specifying `output` now necessarily means that the test case expects an autofix and asserts its result. If the test case doesn't expect an autofix, omit the `output` property or set it to `null`. This asserts that there was no autofix. 1. **Test error objects must specify `message` or `messageId`.** To improve the quality of test coverage, `RuleTester` now throws an error if neither `message` nor `messageId` is specified on test error objects. 1. **Test error object must specify `suggestions` if the actual error provides suggestions.** In ESLint v8.x, if the `suggestions` property was omitted from test error objects, `RuleTester` wasn't performing any checks related to suggestions, so it was easy to forget to assert if a test case produces suggestions. In ESLint v9.0.0, omitting the `suggestions` property asserts that the actual error does not provide suggestions, while you need to specify the `suggestions` property if the actual error does provide suggestions. We highly recommend that you test suggestions in detail by specifying an array of test suggestion objects, but you can also specify `suggestions: ` to assert just the number of suggestions. 1. **Test suggestion objects must specify `output`.** To improve the quality of test coverage, `RuleTester` now throws an error if `output` property is not specified on test suggestion objects. @@ -634,16 +629,16 @@ If you're passing configuration objects that are incompatible with the flat conf ```js // eslintrc config format linter.verify(code, { - parserOptions: { - ecmaVersion: 6 - } + parserOptions: { + ecmaVersion: 6, + }, }); // flat config format linter.verify(code, { - languageOptions: { - ecmaVersion: 6 - } + languageOptions: { + ecmaVersion: 6, + }, }); ``` @@ -655,42 +650,42 @@ Rules and parsers can be defined directly in the configuration. // eslintrc mode linter.defineRule("my-rule1", myRule1); linter.defineRules({ - "my-rule2": myRule2, - "my-rule3": myRule3 + "my-rule2": myRule2, + "my-rule3": myRule3, }); linter.defineParser("my-parser", myParser); linter.verify(code, { - rules: { - "my-rule1": "error", - "my-rule2": "error", - "my-rule3": "error" - }, - parser: "my-parser" + rules: { + "my-rule1": "error", + "my-rule2": "error", + "my-rule3": "error", + }, + parser: "my-parser", }); // flat config mode linter.verify(code, { - plugins: { - "my-plugin-foo": { - rules: { - "my-rule1": myRule1 - } - }, - "my-plugin-bar": { - rules: { - "my-rule2": myRule2, - "my-rule3": myRule3 - } - } - }, - rules: { - "my-plugin-foo/my-rule1": "error", - "my-plugin-bar/my-rule2": "error", - "my-plugin-bar/my-rule3": "error" - }, - languageOptions: { - parser: myParser - } + plugins: { + "my-plugin-foo": { + rules: { + "my-rule1": myRule1, + }, + }, + "my-plugin-bar": { + rules: { + "my-rule2": myRule2, + "my-rule3": myRule3, + }, + }, + }, + rules: { + "my-plugin-foo/my-rule1": "error", + "my-plugin-bar/my-rule2": "error", + "my-plugin-bar/my-rule3": "error", + }, + languageOptions: { + parser: myParser, + }, }); ``` @@ -700,9 +695,9 @@ If you still need the v8.x `Linter` functionality, pass `configType: "eslintrc"` const linter = new Linter({ configType: "eslintrc" }); linter.verify(code, { - parserOptions: { - ecmaVersion: 6 - } + parserOptions: { + ecmaVersion: 6, + }, }); linter.getRules(); diff --git a/docs/src/use/migrating-from-jscs.md b/docs/src/use/migrating-from-jscs.md index f119ddd00427..4634a2b897a8 100644 --- a/docs/src/use/migrating-from-jscs.md +++ b/docs/src/use/migrating-from-jscs.md @@ -1,6 +1,5 @@ --- title: Migrating from JSCS - --- {%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} @@ -11,8 +10,8 @@ In April 2016, we [announced](https://eslint.org/blog/2016/04/welcoming-jscs-to- Before beginning the process of migrating to ESLint, it's helpful to understand some of the terminology that ESLint uses and how it relates to terminology that JSCS uses. -* **Configuration File** - In JSCS, the configuration file is `.jscsrc`, `.jscsrc.json`, `.jscsrc.yaml`, or `.jscsrs.js`. In ESLint, the configuration file can be `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc.js` (there is also a deprecated `.eslintrc` file format). -* **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](../extend/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see [the "Converting Presets" section](#converting-presets) below). Additionally, the `preset` option in a configuration file is the equivalent of the ESLint `extends` option. +- **Configuration File** - In JSCS, the configuration file is `.jscsrc`, `.jscsrc.json`, `.jscsrc.yaml`, or `.jscsrs.js`. In ESLint, the configuration file can be `.eslintrc.json`, `.eslintrc.yml`, `.eslintrc.yaml`, or `.eslintrc.js` (there is also a deprecated `.eslintrc` file format). +- **Presets** - In JSCS, there were numerous predefined configurations shipped directly within JSCS. ESLint ships with just one predefined configuration (`eslint:recommended`) that has no style rules enabled. However, ESLint does support [shareable configs](../extend/shareable-configs). Shareable configs are configurations that are published on their own to npm and there are shareable configs available for almost all of the JSCS presets (see [the "Converting Presets" section](#converting-presets) below). Additionally, the `preset` option in a configuration file is the equivalent of the ESLint `extends` option. ## Convert Configuration Files Using Polyjuice @@ -60,24 +59,24 @@ You'll be guided through a series of questions that will help you setup a basic There are shareable configs available for most JSCS presets. The equivalent shareable configs for each JSCS preset are listed in the following table: -| **JSCS Preset** | **ESLint Shareable Config** | -|-----------------|-----------------------------| -| `airbnb` | [`eslint-config-airbnb-base`](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base) | -| `crockford` | (not available) | -| `google` | [`eslint-config-google`](https://github.com/google/eslint-config-google) | -| `grunt` | [`eslint-config-grunt`](https://github.com/markelog/eslint-config-grunt) | -| `idiomatic` | [`eslint-config-idiomatic`](https://github.com/jamespamplin/eslint-config-idiomatic) | -| `jquery` | [`eslint-config-jquery`](https://github.com/jquery/eslint-config-jquery) | -| `mdcs` | [`eslint-config-mdcs`](https://github.com/zz85/mrdoobapproves) | -| `node-style-guide` | [`eslint-config-node-style-guide`](https://github.com/pdehaan/eslint-config-node-style-guide) | -| `wikimedia` | [`eslint-config-wikimedia`](https://github.com/wikimedia/eslint-config-wikimedia) | -| `wordpress` | [`eslint-config-wordpress`](https://github.com/WordPress-Coding-Standards/eslint-config-wordpress) | +| **JSCS Preset** | **ESLint Shareable Config** | +| ------------------ | ------------------------------------------------------------------------------------------------------------------ | +| `airbnb` | [`eslint-config-airbnb-base`](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base) | +| `crockford` | (not available) | +| `google` | [`eslint-config-google`](https://github.com/google/eslint-config-google) | +| `grunt` | [`eslint-config-grunt`](https://github.com/markelog/eslint-config-grunt) | +| `idiomatic` | [`eslint-config-idiomatic`](https://github.com/jamespamplin/eslint-config-idiomatic) | +| `jquery` | [`eslint-config-jquery`](https://github.com/jquery/eslint-config-jquery) | +| `mdcs` | [`eslint-config-mdcs`](https://github.com/zz85/mrdoobapproves) | +| `node-style-guide` | [`eslint-config-node-style-guide`](https://github.com/pdehaan/eslint-config-node-style-guide) | +| `wikimedia` | [`eslint-config-wikimedia`](https://github.com/wikimedia/eslint-config-wikimedia) | +| `wordpress` | [`eslint-config-wordpress`](https://github.com/WordPress-Coding-Standards/eslint-config-wordpress) | As an example, suppose that you are using the `airbnb` preset, so your `.jscsrc` file looks like this: ```json { - "preset": "airbnb" + "preset": "airbnb" } ``` @@ -93,7 +92,7 @@ And then you would modify your configuration file like this: ```json { - "extends": "airbnb-base" + "extends": "airbnb-base" } ``` @@ -103,15 +102,15 @@ ESLint sees `"airbnb-base"` and will look for `eslint-config-airbnb-base` (to sa Both JSCS and ESLint use comments inside of files to disable rules around certain parts of your code. The following table lists out the JSCS inline configuration comments and their ESLint equivalents. -| **Description** | **JSCS Comment** | **ESLint Comment** | -|-----------------|------------------|--------------------| -| Disable all rules | `// jscs:disable` or `/* jscs:disable */` | `/* eslint-disable */` | -| Enable all rules | `// jscs:enable` or `/* jscs:enable */` | `/* eslint-enable */` | -| Disable one rule | `// jscs:disable ruleName` or `/* jscs:disable ruleName */` | `/* eslint-disable rule-name */` | -| Enable one rule | `// jscs:enable ruleName` or `/* jscs:enable ruleName */` | `/* eslint-enable rule-name */` | -| Disable multiple rules | `// jscs:disable ruleName1, ruleName2` or `/* jscs:disable ruleName1, ruleName2 */` | `/* eslint-disable rule-name1, rule-name2 */` | -| Enable multiple rules | `// jscs:enable ruleName1, ruleName2` or `/* jscs:enable ruleName1, ruleName2 */` | `/* eslint-enable rule-name1, rule-name2 */` | -| Disable one rule on single line | `// jscs:ignore ruleName` | `// eslint-disable-line rule-name` | +| **Description** | **JSCS Comment** | **ESLint Comment** | +| ------------------------------- | ----------------------------------------------------------------------------------- | --------------------------------------------- | +| Disable all rules | `// jscs:disable` or `/* jscs:disable */` | `/* eslint-disable */` | +| Enable all rules | `// jscs:enable` or `/* jscs:enable */` | `/* eslint-enable */` | +| Disable one rule | `// jscs:disable ruleName` or `/* jscs:disable ruleName */` | `/* eslint-disable rule-name */` | +| Enable one rule | `// jscs:enable ruleName` or `/* jscs:enable ruleName */` | `/* eslint-enable rule-name */` | +| Disable multiple rules | `// jscs:disable ruleName1, ruleName2` or `/* jscs:disable ruleName1, ruleName2 */` | `/* eslint-disable rule-name1, rule-name2 */` | +| Enable multiple rules | `// jscs:enable ruleName1, ruleName2` or `/* jscs:enable ruleName1, ruleName2 */` | `/* eslint-enable rule-name1, rule-name2 */` | +| Disable one rule on single line | `// jscs:ignore ruleName` | `// eslint-disable-line rule-name` | ## Command Line Options diff --git a/docs/src/use/migrating-to-1.0.0.md b/docs/src/use/migrating-to-1.0.0.md index 1ed8496359e6..9f3726269e7f 100644 --- a/docs/src/use/migrating-to-1.0.0.md +++ b/docs/src/use/migrating-to-1.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v1.0.0 - --- ESLint v1.0.0 is the first major version release. As a result, there are some significant changes between how ESLint worked during its life in 0.x and how it will work going forward. These changes are the direct result of feedback from the ESLint community of users and were not made without due consideration for the upgrade path. We believe that these changes make ESLint even better, and while some work is necessary to upgrade, we hope the pain of this upgrade is small enough that you will see the benefit of upgrading. @@ -13,7 +12,7 @@ When using `--init`, your configuration file will automatically include the foll ```json { - "extends": "eslint:recommended" + "extends": "eslint:recommended" } ``` @@ -23,62 +22,62 @@ This setting mimics some of the default behavior from 0.x, but not all. If you d The `"eslint:recommended"` configuration contains many of the same default rule settings from 0.x, but not all. These rules are no longer on by default, so you should review your settings to ensure they are still as you expect: -* [no-alert](../rules/no-alert) -* [no-array-constructor](../rules/no-array-constructor) -* [no-caller](../rules/no-caller) -* [no-catch-shadow](../rules/no-catch-shadow) -* [no-empty-label](../rules/no-empty-label) -* [no-eval](../rules/no-eval) -* [no-extend-native](../rules/no-extend-native) -* [no-extra-bind](../rules/no-extra-bind) -* [no-extra-strict](../rules/no-extra-strict) -* [no-implied-eval](../rules/no-implied-eval) -* [no-iterator](../rules/no-iterator) -* [no-label-var](../rules/no-label-var) -* [no-labels](../rules/no-labels) -* [no-lone-blocks](../rules/no-lone-blocks) -* [no-loop-func](../rules/no-loop-func) -* [no-multi-spaces](../rules/no-multi-spaces) -* [no-multi-str](../rules/no-multi-str) -* [no-native-reassign](../rules/no-native-reassign) -* [no-new](../rules/no-new) -* [no-new-func](../rules/no-new-func) -* [no-new-object](../rules/no-new-object) -* [no-new-wrappers](../rules/no-new-wrappers) -* [no-octal-escape](../rules/no-octal-escape) -* [no-process-exit](../rules/no-process-exit) -* [no-proto](../rules/no-proto) -* [no-return-assign](../rules/no-return-assign) -* [no-script-url](../rules/no-script-url) -* [no-sequences](../rules/no-sequences) -* [no-shadow](../rules/no-shadow) -* [no-shadow-restricted-names](../rules/no-shadow-restricted-names) -* [no-spaced-func](../rules/no-spaced-func) -* [no-trailing-spaces](../rules/no-trailing-spaces) -* [no-undef-init](../rules/no-undef-init) -* [no-underscore-dangle](../rules/no-underscore-dangle) -* [no-unused-expressions](../rules/no-unused-expressions) -* [no-use-before-define](../rules/no-use-before-define) -* [no-with](../rules/no-with) -* [no-wrap-func](../rules/no-wrap-func) -* [camelcase](../rules/camelcase) -* [comma-spacing](../rules/comma-spacing) -* [consistent-return](../rules/consistent-return) -* [curly](../rules/curly) -* [dot-notation](../rules/dot-notation) -* [eol-last](../rules/eol-last) -* [eqeqeq](../rules/eqeqeq) -* [key-spacing](../rules/key-spacing) -* [new-cap](../rules/new-cap) -* [new-parens](../rules/new-parens) -* [quotes](../rules/quotes) -* [semi](../rules/semi) -* [semi-spacing](../rules/semi-spacing) -* [space-infix-ops](../rules/space-infix-ops) -* [space-return-throw-case](../rules/space-return-throw-case) -* [space-unary-ops](../rules/space-unary-ops) -* [strict](../rules/strict) -* [yoda](../rules/yoda) +- [no-alert](../rules/no-alert) +- [no-array-constructor](../rules/no-array-constructor) +- [no-caller](../rules/no-caller) +- [no-catch-shadow](../rules/no-catch-shadow) +- [no-empty-label](../rules/no-empty-label) +- [no-eval](../rules/no-eval) +- [no-extend-native](../rules/no-extend-native) +- [no-extra-bind](../rules/no-extra-bind) +- [no-extra-strict](../rules/no-extra-strict) +- [no-implied-eval](../rules/no-implied-eval) +- [no-iterator](../rules/no-iterator) +- [no-label-var](../rules/no-label-var) +- [no-labels](../rules/no-labels) +- [no-lone-blocks](../rules/no-lone-blocks) +- [no-loop-func](../rules/no-loop-func) +- [no-multi-spaces](../rules/no-multi-spaces) +- [no-multi-str](../rules/no-multi-str) +- [no-native-reassign](../rules/no-native-reassign) +- [no-new](../rules/no-new) +- [no-new-func](../rules/no-new-func) +- [no-new-object](../rules/no-new-object) +- [no-new-wrappers](../rules/no-new-wrappers) +- [no-octal-escape](../rules/no-octal-escape) +- [no-process-exit](../rules/no-process-exit) +- [no-proto](../rules/no-proto) +- [no-return-assign](../rules/no-return-assign) +- [no-script-url](../rules/no-script-url) +- [no-sequences](../rules/no-sequences) +- [no-shadow](../rules/no-shadow) +- [no-shadow-restricted-names](../rules/no-shadow-restricted-names) +- [no-spaced-func](../rules/no-spaced-func) +- [no-trailing-spaces](../rules/no-trailing-spaces) +- [no-undef-init](../rules/no-undef-init) +- [no-underscore-dangle](../rules/no-underscore-dangle) +- [no-unused-expressions](../rules/no-unused-expressions) +- [no-use-before-define](../rules/no-use-before-define) +- [no-with](../rules/no-with) +- [no-wrap-func](../rules/no-wrap-func) +- [camelcase](../rules/camelcase) +- [comma-spacing](../rules/comma-spacing) +- [consistent-return](../rules/consistent-return) +- [curly](../rules/curly) +- [dot-notation](../rules/dot-notation) +- [eol-last](../rules/eol-last) +- [eqeqeq](../rules/eqeqeq) +- [key-spacing](../rules/key-spacing) +- [new-cap](../rules/new-cap) +- [new-parens](../rules/new-parens) +- [quotes](../rules/quotes) +- [semi](../rules/semi) +- [semi-spacing](../rules/semi-spacing) +- [space-infix-ops](../rules/space-infix-ops) +- [space-return-throw-case](../rules/space-return-throw-case) +- [space-unary-ops](../rules/space-unary-ops) +- [strict](../rules/strict) +- [yoda](../rules/yoda) See also: the [full diff](https://github.com/eslint/eslint/commit/e3e9dbd9876daf4bdeb4e15f8a76a9d5e6e03e39#diff-b01a5cfd9361ca9280a460fd6bb8edbbL1) where the defaults were changed. @@ -86,64 +85,64 @@ Here's a configuration file with the closest equivalent of the old defaults: ```json { - "extends": "eslint:recommended", - "rules": { - "no-alert": 2, - "no-array-constructor": 2, - "no-caller": 2, - "no-catch-shadow": 2, - "no-empty-label": 2, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-implied-eval": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-loop-func": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-native-reassign": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-object": 2, - "no-new-wrappers": 2, - "no-octal-escape": 2, - "no-process-exit": 2, - "no-proto": 2, - "no-return-assign": 2, - "no-script-url": 2, - "no-sequences": 2, - "no-shadow": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-trailing-spaces": 2, - "no-undef-init": 2, - "no-underscore-dangle": 2, - "no-unused-expressions": 2, - "no-use-before-define": 2, - "no-with": 2, - "camelcase": 2, - "comma-spacing": 2, - "consistent-return": 2, - "curly": [2, "all"], - "dot-notation": [2, { "allowKeywords": true }], - "eol-last": 2, - "no-extra-parens": [2, "functions"], - "eqeqeq": 2, - "key-spacing": [2, { "beforeColon": false, "afterColon": true }], - "new-cap": 2, - "new-parens": 2, - "quotes": [2, "double"], - "semi": 2, - "semi-spacing": [2, {"before": false, "after": true}], - "space-infix-ops": 2, - "space-return-throw-case": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "strict": [2, "function"], - "yoda": [2, "never"] - } + "extends": "eslint:recommended", + "rules": { + "no-alert": 2, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 2, + "no-empty-label": 2, + "no-eval": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-implied-eval": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-loop-func": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-native-reassign": 2, + "no-new": 2, + "no-new-func": 2, + "no-new-object": 2, + "no-new-wrappers": 2, + "no-octal-escape": 2, + "no-process-exit": 2, + "no-proto": 2, + "no-return-assign": 2, + "no-script-url": 2, + "no-sequences": 2, + "no-shadow": 2, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-trailing-spaces": 2, + "no-undef-init": 2, + "no-underscore-dangle": 2, + "no-unused-expressions": 2, + "no-use-before-define": 2, + "no-with": 2, + "camelcase": 2, + "comma-spacing": 2, + "consistent-return": 2, + "curly": [2, "all"], + "dot-notation": [2, { "allowKeywords": true }], + "eol-last": 2, + "no-extra-parens": [2, "functions"], + "eqeqeq": 2, + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "new-cap": 2, + "new-parens": 2, + "quotes": [2, "double"], + "semi": 2, + "semi-spacing": [2, { "before": false, "after": true }], + "space-infix-ops": 2, + "space-return-throw-case": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "strict": [2, "function"], + "yoda": [2, "never"] + } } ``` @@ -151,19 +150,19 @@ Here's a configuration file with the closest equivalent of the old defaults: Over the past several releases, we have been deprecating rules and introducing new rules to take their place. The following is a list of the removed rules and their replacements: -* [generator-star](../rules/generator-star) is replaced by [generator-star-spacing](../rules/generator-star-spacing) -* [global-strict](../rules/global-strict) is replaced by [strict](../rules/strict) -* [no-comma-dangle](../rules/no-comma-dangle) is replaced by [comma-dangle](../rules/comma-dangle) -* [no-empty-class](../rules/no-empty-class) is replaced by [no-empty-character-class](../rules/no-empty-character-class) -* [no-extra-strict](../rules/no-extra-strict) is replaced by [strict](../rules/strict) -* [no-reserved-keys](../rules/no-reserved-keys) is replaced by [quote-props](../rules/quote-props) -* [no-space-before-semi](../rules/no-space-before-semi) is replaced by [semi-spacing](../rules/semi-spacing) -* [no-wrap-func](../rules/no-wrap-func) is replaced by [no-extra-parens](../rules/no-extra-parens) -* [space-after-function-name](../rules/space-after-function-name) is replaced by [space-before-function-paren](../rules/space-before-function-paren) -* [space-before-function-parentheses](../rules/space-before-function-parentheses) is replaced by [space-before-function-paren](../rules/space-before-function-paren) -* [space-in-brackets](../rules/space-in-brackets) is replaced by[object-curly-spacing](../rules/object-curly-spacing) and [array-bracket-spacing](../rules/array-bracket-spacing) -* [space-unary-word-ops](../rules/space-unary-word-ops) is replaced by [space-unary-ops](../rules/space-unary-ops) -* [spaced-line-comment](../rules/spaced-line-comment) is replaced by [spaced-comment](../rules/spaced-comment) +- [generator-star](../rules/generator-star) is replaced by [generator-star-spacing](../rules/generator-star-spacing) +- [global-strict](../rules/global-strict) is replaced by [strict](../rules/strict) +- [no-comma-dangle](../rules/no-comma-dangle) is replaced by [comma-dangle](../rules/comma-dangle) +- [no-empty-class](../rules/no-empty-class) is replaced by [no-empty-character-class](../rules/no-empty-character-class) +- [no-extra-strict](../rules/no-extra-strict) is replaced by [strict](../rules/strict) +- [no-reserved-keys](../rules/no-reserved-keys) is replaced by [quote-props](../rules/quote-props) +- [no-space-before-semi](../rules/no-space-before-semi) is replaced by [semi-spacing](../rules/semi-spacing) +- [no-wrap-func](../rules/no-wrap-func) is replaced by [no-extra-parens](../rules/no-extra-parens) +- [space-after-function-name](../rules/space-after-function-name) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +- [space-before-function-parentheses](../rules/space-before-function-parentheses) is replaced by [space-before-function-paren](../rules/space-before-function-paren) +- [space-in-brackets](../rules/space-in-brackets) is replaced by[object-curly-spacing](../rules/object-curly-spacing) and [array-bracket-spacing](../rules/array-bracket-spacing) +- [space-unary-word-ops](../rules/space-unary-word-ops) is replaced by [space-unary-ops](../rules/space-unary-ops) +- [spaced-line-comment](../rules/spaced-line-comment) is replaced by [spaced-comment](../rules/spaced-comment) **To address:** You'll need to update your rule configurations to use the new rules. ESLint v1.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. @@ -189,12 +188,12 @@ The replacement for `eslint-tester` is called `RuleTester`. It's a simplified ve ```js var eslint = require("../../../lib/eslint"), - ESLintTester = require("eslint-tester"); + ESLintTester = require("eslint-tester"); var eslintTester = new ESLintTester(eslint); eslintTester.addRuleTest("lib/rules/your-rule", { - valid: [], - invalid: [] + valid: [], + invalid: [], }); ``` @@ -202,11 +201,11 @@ Then you can change to: ```js var rule = require("../../../lib/rules/your-rule"), - RuleTester = require("eslint").RuleTester; + RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); ruleTester.run("your-rule", rule, { - valid: [], - invalid: [] + valid: [], + invalid: [], }); ``` diff --git a/docs/src/use/migrating-to-2.0.0.md b/docs/src/use/migrating-to-2.0.0.md index 753fed743a71..3a95a127d6c5 100644 --- a/docs/src/use/migrating-to-2.0.0.md +++ b/docs/src/use/migrating-to-2.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v2.0.0 - --- ESLint v2.0.0 is the second major version release. As a result, there are some significant changes between how ESLint worked during its life in 0.x and 1.x and how it will work going forward. These changes are the direct result of feedback from the ESLint community of users and were not made without due consideration for the upgrade path. We believe that these changes make ESLint even better, and while some work is necessary to upgrade, we hope the pain of this upgrade is small enough that you will see the benefit of upgrading. @@ -13,17 +12,17 @@ Due to a quirk in the way rule schemas worked, it was possible that you'd need t ```js module.exports = { - "type": "array", - "items": [ - { - "enum": [0, 1, 2] - }, - { - "enum": ["always", "never"] - } - ], - "minItems": 1, - "maxItems": 2 + type: "array", + items: [ + { + enum: [0, 1, 2], + }, + { + enum: ["always", "never"], + }, + ], + minItems: 1, + maxItems: 2, }; ``` @@ -39,14 +38,14 @@ Here's what the schema from above looks like when properly converted: ```js module.exports = { - "type": "array", - "items": [ - { - "enum": ["always", "never"] - } - ], - "minItems": 0, - "maxItems": 1 + type: "array", + items: [ + { + enum: ["always", "never"], + }, + ], + minItems: 0, + maxItems: 1, }; ``` @@ -54,11 +53,11 @@ module.exports = { The following rules have been deprecated with new rules created to take their place. The following is a list of the removed rules and their replacements: -* [no-arrow-condition](../rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](../rules/no-confusing-arrow) and [no-constant-condition](../rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. -* [no-empty-label](../rules/no-empty-label) is replaced by [no-labels](../rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. -* [space-after-keywords](../rules/space-after-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). -* [space-before-keywords](../rules/space-before-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). -* [space-return-throw-case](../rules/space-return-throw-case) is replaced by [keyword-spacing](../rules/keyword-spacing). +- [no-arrow-condition](../rules/no-arrow-condition) is replaced by a combination of [no-confusing-arrow](../rules/no-confusing-arrow) and [no-constant-condition](../rules/no-constant-condition). Turn on both of these rules to get the same functionality as `no-arrow-condition`. +- [no-empty-label](../rules/no-empty-label) is replaced by [no-labels](../rules/no-labels) with `{"allowLoop": true, "allowSwitch": true}` option. +- [space-after-keywords](../rules/space-after-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +- [space-before-keywords](../rules/space-before-keywords) is replaced by [keyword-spacing](../rules/keyword-spacing). +- [space-return-throw-case](../rules/space-return-throw-case) is replaced by [keyword-spacing](../rules/keyword-spacing). **To address:** You'll need to update your rule configurations to use the new rules. ESLint v2.0.0 will also warn you when you're using a rule that has been removed and will suggest the replacement rules. Hopefully, this will result in few surprises during the upgrade process. @@ -77,9 +76,9 @@ Prior to 2.0.0, new global variables that were standardized as part of ES6 such ```js // In your .eslintrc { - env: { - es6: true - } + env: { + es6: true; + } } // Or in a configuration comment @@ -90,41 +89,41 @@ Prior to 2.0.0, new global variables that were standardized as part of ES6 such Prior to 2.0.0, the way to enable language options was by using `ecmaFeatures` in your configuration. In 2.0.0: -* The `ecmaFeatures` property is now under a top-level `parserOptions` property. -* All ECMAScript 6 `ecmaFeatures` flags have been removed in favor of a `ecmaVersion` property under `parserOptions` that can be set to 3, 5 (default), or 6. -* The `ecmaFeatures.modules` flag has been replaced by a `sourceType` property under `parserOptions` which can be set to `"script"` (default) or `"module"` for ES6 modules. +- The `ecmaFeatures` property is now under a top-level `parserOptions` property. +- All ECMAScript 6 `ecmaFeatures` flags have been removed in favor of a `ecmaVersion` property under `parserOptions` that can be set to 3, 5 (default), or 6. +- The `ecmaFeatures.modules` flag has been replaced by a `sourceType` property under `parserOptions` which can be set to `"script"` (default) or `"module"` for ES6 modules. **To address:** If you are using any ECMAScript 6 feature flags in `ecmaFeatures`, you'll need to use `ecmaVersion: 6` instead. The ECMAScript 6 feature flags are: -* `arrowFunctions` - enable [arrow functions](https://leanpub.com/understandinges6/read#leanpub-auto-arrow-functions) -* `binaryLiterals` - enable [binary literals](https://leanpub.com/understandinges6/read#leanpub-auto-octal-and-binary-literals) -* `blockBindings` - enable `let` and `const` (aka [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings)) -* `classes` - enable classes -* `defaultParams` - enable [default function parameters](https://leanpub.com/understandinges6/read/#leanpub-auto-default-parameters) -* `destructuring` - enable [destructuring](https://leanpub.com/understandinges6/read#leanpub-auto-destructuring-assignment) -* `forOf` - enable [`for-of` loops](https://leanpub.com/understandinges6/read#leanpub-auto-iterables-and-for-of) -* `generators` - enable [generators](https://leanpub.com/understandinges6/read#leanpub-auto-generators) -* `modules` - enable modules and global strict mode -* `objectLiteralComputedProperties` - enable [computed object literal property names](https://leanpub.com/understandinges6/read#leanpub-auto-computed-property-names) -* `objectLiteralDuplicateProperties` - enable [duplicate object literal properties](https://leanpub.com/understandinges6/read#leanpub-auto-duplicate-object-literal-properties) in strict mode -* `objectLiteralShorthandMethods` - enable [object literal shorthand methods](https://leanpub.com/understandinges6/read#leanpub-auto-method-initializer-shorthand) -* `objectLiteralShorthandProperties` - enable [object literal shorthand properties](https://leanpub.com/understandinges6/read#leanpub-auto-property-initializer-shorthand) -* `octalLiterals` - enable [octal literals](https://leanpub.com/understandinges6/read#leanpub-auto-octal-and-binary-literals) -* `regexUFlag` - enable the [regular expression `u` flag](https://leanpub.com/understandinges6/read#leanpub-auto-the-regular-expression-u-flag) -* `regexYFlag` - enable the [regular expression `y` flag](https://leanpub.com/understandinges6/read#leanpub-auto-the-regular-expression-y-flag) -* `restParams` - enable the [rest parameters](https://leanpub.com/understandinges6/read#leanpub-auto-rest-parameters) -* `spread` - enable the [spread operator](https://leanpub.com/understandinges6/read#leanpub-auto-the-spread-operator) for arrays -* `superInFunctions` - enable `super` references inside of functions -* `templateStrings` - enable [template strings](https://leanpub.com/understandinges6/read/#leanpub-auto-template-strings) -* `unicodeCodePointEscapes` - enable [code point escapes](https://leanpub.com/understandinges6/read/#leanpub-auto-escaping-non-bmp-characters) +- `arrowFunctions` - enable [arrow functions](https://leanpub.com/understandinges6/read#leanpub-auto-arrow-functions) +- `binaryLiterals` - enable [binary literals](https://leanpub.com/understandinges6/read#leanpub-auto-octal-and-binary-literals) +- `blockBindings` - enable `let` and `const` (aka [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings)) +- `classes` - enable classes +- `defaultParams` - enable [default function parameters](https://leanpub.com/understandinges6/read/#leanpub-auto-default-parameters) +- `destructuring` - enable [destructuring](https://leanpub.com/understandinges6/read#leanpub-auto-destructuring-assignment) +- `forOf` - enable [`for-of` loops](https://leanpub.com/understandinges6/read#leanpub-auto-iterables-and-for-of) +- `generators` - enable [generators](https://leanpub.com/understandinges6/read#leanpub-auto-generators) +- `modules` - enable modules and global strict mode +- `objectLiteralComputedProperties` - enable [computed object literal property names](https://leanpub.com/understandinges6/read#leanpub-auto-computed-property-names) +- `objectLiteralDuplicateProperties` - enable [duplicate object literal properties](https://leanpub.com/understandinges6/read#leanpub-auto-duplicate-object-literal-properties) in strict mode +- `objectLiteralShorthandMethods` - enable [object literal shorthand methods](https://leanpub.com/understandinges6/read#leanpub-auto-method-initializer-shorthand) +- `objectLiteralShorthandProperties` - enable [object literal shorthand properties](https://leanpub.com/understandinges6/read#leanpub-auto-property-initializer-shorthand) +- `octalLiterals` - enable [octal literals](https://leanpub.com/understandinges6/read#leanpub-auto-octal-and-binary-literals) +- `regexUFlag` - enable the [regular expression `u` flag](https://leanpub.com/understandinges6/read#leanpub-auto-the-regular-expression-u-flag) +- `regexYFlag` - enable the [regular expression `y` flag](https://leanpub.com/understandinges6/read#leanpub-auto-the-regular-expression-y-flag) +- `restParams` - enable the [rest parameters](https://leanpub.com/understandinges6/read#leanpub-auto-rest-parameters) +- `spread` - enable the [spread operator](https://leanpub.com/understandinges6/read#leanpub-auto-the-spread-operator) for arrays +- `superInFunctions` - enable `super` references inside of functions +- `templateStrings` - enable [template strings](https://leanpub.com/understandinges6/read/#leanpub-auto-template-strings) +- `unicodeCodePointEscapes` - enable [code point escapes](https://leanpub.com/understandinges6/read/#leanpub-auto-escaping-non-bmp-characters) If you're using any of these flags, such as: ```js { - ecmaFeatures: { - arrowFunctions: true - } + ecmaFeatures: { + arrowFunctions: true; + } } ``` @@ -132,9 +131,9 @@ Then you should enable ES6 using `ecmaVersion`: ```js { - parserOptions: { - ecmaVersion: 6 - } + parserOptions: { + ecmaVersion: 6; + } } ``` @@ -142,9 +141,9 @@ If you're using any non-ES6 flags in `ecmaFeatures`, you need to move those insi ```js { - ecmaFeatures: { - jsx: true - } + ecmaFeatures: { + jsx: true; + } } ``` @@ -152,11 +151,11 @@ Then you should move `ecmaFeatures` under `parserOptions`: ```js { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } + parserOptions: { + ecmaFeatures: { + jsx: true; + } + } } ``` @@ -164,17 +163,17 @@ If you were using `ecmaFeatures.modules` to enable ES6 module support like this: ```js { - ecmaFeatures: { - modules: true - } + ecmaFeatures: { + modules: true; + } } ``` ```js { - parserOptions: { - sourceType: "module" - } + parserOptions: { + sourceType: "module"; + } } ``` @@ -189,12 +188,12 @@ If you have a plugin with rules and you are using RuleTester, then you also need ```js var ruleTester = new RuleTester(); ruleTester.run("no-var", rule, { - valid: [ - { - code: "let x;", - parserOptions: { ecmaVersion: 6 } - } - ] + valid: [ + { + code: "let x;", + parserOptions: { ecmaVersion: 6 }, + }, + ], }); ``` @@ -204,42 +203,42 @@ If you're not using `ecmaFeatures` in your configuration or your custom/plugin r ```json { - "extends": "eslint:recommended" + "extends": "eslint:recommended" } ``` In 2.0.0, the following 11 rules were added to `"eslint:recommended"`. -* [constructor-super](../rules/constructor-super) -* [no-case-declarations](../rules/no-case-declarations) -* [no-class-assign](../rules/no-class-assign) -* [no-const-assign](../rules/no-const-assign) -* [no-dupe-class-members](../rules/no-dupe-class-members) -* [no-empty-pattern](../rules/no-empty-pattern) -* [no-new-symbol](../rules/no-new-symbol) -* [no-self-assign](../rules/no-self-assign) -* [no-this-before-super](../rules/no-this-before-super) -* [no-unexpected-multiline](../rules/no-unexpected-multiline) -* [no-unused-labels](../rules/no-unused-labels) +- [constructor-super](../rules/constructor-super) +- [no-case-declarations](../rules/no-case-declarations) +- [no-class-assign](../rules/no-class-assign) +- [no-const-assign](../rules/no-const-assign) +- [no-dupe-class-members](../rules/no-dupe-class-members) +- [no-empty-pattern](../rules/no-empty-pattern) +- [no-new-symbol](../rules/no-new-symbol) +- [no-self-assign](../rules/no-self-assign) +- [no-this-before-super](../rules/no-this-before-super) +- [no-unexpected-multiline](../rules/no-unexpected-multiline) +- [no-unused-labels](../rules/no-unused-labels) **To address:** If you don't want to be notified by those rules, you can simply disable those rules. ```json { - "extends": "eslint:recommended", - "rules": { - "no-case-declarations": 0, - "no-class-assign": 0, - "no-const-assign": 0, - "no-dupe-class-members": 0, - "no-empty-pattern": 0, - "no-new-symbol": 0, - "no-self-assign": 0, - "no-this-before-super": 0, - "no-unexpected-multiline": 0, - "no-unused-labels": 0, - "constructor-super": 0 - } + "extends": "eslint:recommended", + "rules": { + "no-case-declarations": 0, + "no-class-assign": 0, + "no-const-assign": 0, + "no-dupe-class-members": 0, + "no-empty-pattern": 0, + "no-new-symbol": 0, + "no-self-assign": 0, + "no-this-before-super": 0, + "no-unexpected-multiline": 0, + "no-unused-labels": 0, + "constructor-super": 0 + } } ``` @@ -249,15 +248,15 @@ We found some bugs in our scope analysis that needed to be addressed. Specifical Originally, `Variable` objects and `Reference` objects refer each other: -* `Variable#references` property is an array of `Reference` objects which are referencing the variable. -* `Reference#resolved` property is a `Variable` object which are referenced. +- `Variable#references` property is an array of `Reference` objects which are referencing the variable. +- `Reference#resolved` property is a `Variable` object which are referenced. But until 1.x, the following variables and references had the wrong value (empty) in those properties: -* `var` declarations in the global. -* `function` declarations in the global. -* Variables defined in config files. -* Variables defined in `/* global */` comments. +- `var` declarations in the global. +- `function` declarations in the global. +- Variables defined in config files. +- Variables defined in `/* global */` comments. Now, those variables and references have correct values in these properties. @@ -269,10 +268,10 @@ For example, this is how you might locate the `window` global variable in 1.x: ```js var globalScope = context.getScope(); -globalScope.through.forEach(function(reference) { - if (reference.identifier.name === "window") { - checkForWindow(reference); - } +globalScope.through.forEach(function (reference) { + if (reference.identifier.name === "window") { + checkForWindow(reference); + } }); ``` @@ -284,7 +283,7 @@ In 2.0.0, `window` is no longer located in `Scope#through` because we have added var globalScope = context.getScope(); var variable = globalScope.set.get("window"); if (variable) { - variable.references.forEach(checkForWindow); + variable.references.forEach(checkForWindow); } ``` @@ -296,11 +295,11 @@ This will affect you if you are extending from `eslint:recommended`, and are ena ```json { - "extends": "eslint:recommended", - "rules": { - "no-multiple-empty-lines": 2, - "func-style": 2 - } + "extends": "eslint:recommended", + "rules": { + "no-multiple-empty-lines": 2, + "func-style": 2 + } } ``` @@ -314,11 +313,11 @@ ESLint 2.0.0 removes these conflicting defaults, and so you may begin seeing lin ```json { - "extends": "eslint:recommended", - "rules": { - "no-multiple-empty-lines": [2, {"max": 2}], - "func-style": [2, "declaration"] - } + "extends": "eslint:recommended", + "rules": { + "no-multiple-empty-lines": [2, { "max": 2 }], + "func-style": [2, "declaration"] + } } ``` @@ -350,7 +349,7 @@ var sourceCode = new SourceCode(text, ast); ## Rule Changes -* [`strict`](../rules/strict) - defaults to `"safe"` (previous default was `"function"`) +- [`strict`](../rules/strict) - defaults to `"safe"` (previous default was `"function"`) ## Plugins No Longer Have Default Configurations diff --git a/docs/src/use/migrating-to-3.0.0.md b/docs/src/use/migrating-to-3.0.0.md index 289aa145a613..a87dad21aef7 100644 --- a/docs/src/use/migrating-to-3.0.0.md +++ b/docs/src/use/migrating-to-3.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v3.0.0 - --- ESLint v3.0.0 is the third major version release. We have made several breaking changes in this release, however, we believe the changes to be small enough that they should not require significant changes for ESLint users. This guide is intended to walk you through the changes. @@ -32,36 +31,36 @@ To create a new configuration, use `eslint --init`. ```json { - "extends": "eslint:recommended" + "extends": "eslint:recommended" } ``` In 3.0.0, the following rules were added to `"eslint:recommended"`: -* [`no-unsafe-finally`](../rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. -* [`no-native-reassign`](../rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. -* [`require-yield`](../rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. +- [`no-unsafe-finally`](../rules/no-unsafe-finally) helps catch `finally` clauses that may not behave as you think. +- [`no-native-reassign`](../rules/no-native-reassign) was previously part of `no-undef`, but was split out because it didn't make sense as part of another rule. The `no-native-reassign` rule warns whenever you try to overwrite a read-only global variable. +- [`require-yield`](../rules/require-yield) helps to identify generator functions that do not have the `yield` keyword. The following rules were removed from `"eslint:recommended"`: -* [`comma-dangle`](../rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. +- [`comma-dangle`](../rules/comma-dangle) used to be recommended because Internet Explorer 8 and earlier threw a syntax error when it found a dangling comma on object literal properties. However, [Internet Explorer 8 was end-of-lifed](https://www.microsoft.com/en-us/WindowsForBusiness/End-of-IE-support) in January 2016 and all other active browsers allow dangling commas. As such, we consider dangling commas to now be a stylistic issue instead of a possible error. The following rules were modified: -* [`complexity`](../rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. +- [`complexity`](../rules/complexity) used to have a hardcoded default of 11 in `eslint:recommended` that would be used if you turned the rule on without specifying a maximum. The default is now 20. The rule actually always had a default of 20, but `eslint:recommended` was overriding it by mistake. **To address:** If you want to mimic how `eslint:recommended` worked in v2.x, you can use the following: ```json { - "extends": "eslint:recommended", - "rules": { - "no-unsafe-finally": "off", - "no-native-reassign": "off", - "complexity": ["off", 11], - "comma-dangle": "error", - "require-yield": "error" - } + "extends": "eslint:recommended", + "rules": { + "no-unsafe-finally": "off", + "no-native-reassign": "off", + "complexity": ["off", 11], + "comma-dangle": "error", + "require-yield": "error" + } } ``` diff --git a/docs/src/use/migrating-to-4.0.0.md b/docs/src/use/migrating-to-4.0.0.md index 70d337169c0f..2568dbe6f6c8 100644 --- a/docs/src/use/migrating-to-4.0.0.md +++ b/docs/src/use/migrating-to-4.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v4.0.0 - --- ESLint v4.0.0 is the fourth major version release. We have made several breaking changes in this release; however, we expect that most of the changes will only affect a very small percentage of users. This guide is intended to walk you through the changes. @@ -37,19 +36,19 @@ The lists below are ordered roughly by the number of users each change is expect Two new rules have been added to the [`eslint:recommended`](configure#using-eslintrecommended) config: -* [`no-compare-neg-zero`](../rules/no-compare-neg-zero) disallows comparisons to `-0` -* [`no-useless-escape`](../rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions +- [`no-compare-neg-zero`](../rules/no-compare-neg-zero) disallows comparisons to `-0` +- [`no-useless-escape`](../rules/no-useless-escape) disallows uselessly-escaped characters in strings and regular expressions **To address:** To mimic the `eslint:recommended` behavior from 3.x, you can disable these rules in a config file: ```json { - "extends": "eslint:recommended", + "extends": "eslint:recommended", - "rules": { - "no-compare-neg-zero": "off", - "no-useless-escape": "off" - } + "rules": { + "no-compare-neg-zero": "off", + "no-useless-escape": "off" + } } ``` @@ -98,13 +97,16 @@ By default, the [`space-before-function-paren`](../rules/space-before-function-p ```json { - "rules": { - "space-before-function-paren": ["error", { - "anonymous": "always", - "named": "always", - "asyncArrow": "ignore" - }] - } + "rules": { + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "always", + "asyncArrow": "ignore" + } + ] + } } ``` @@ -116,9 +118,9 @@ By default, the [`no-multi-spaces`](../rules/no-multi-spaces) rule will now disa ```json { - "rules": { - "no-multi-spaces": ["error", {"ignoreEOLComments": true}] - } + "rules": { + "no-multi-spaces": ["error", { "ignoreEOLComments": true }] + } } ``` @@ -128,12 +130,10 @@ In 3.x, there was a bug where references to scoped NPM packages as plugins in co ```json { - "plugins": [ - "@my-organization/foo" - ], - "rules": { - "foo/some-rule": "error" - } + "plugins": ["@my-organization/foo"], + "rules": { + "foo/some-rule": "error" + } } ``` @@ -143,12 +143,10 @@ To avoid this ambiguity, in 4.0 references to scoped plugins must include the sc ```json { - "plugins": [ - "@my-organization/foo" - ], - "rules": { - "@my-organization/foo/some-rule": "error" - } + "plugins": ["@my-organization/foo"], + "rules": { + "@my-organization/foo/some-rule": "error" + } } ``` @@ -176,16 +174,16 @@ For rule authors concerned about supporting ESLint v3.0 in addition to v4.0, the Finally, please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: -* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` -* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option -* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option +- `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()` +- `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option +- `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option ## `LineComment` and `BlockComment` events will no longer be emitted during AST traversal Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this: -* This behavior was relying on comment attachment happening at the parser level, which does not happen anymore, to ensure that all comments would be accounted for. -* Thinking of comments in the context of tokens is more predictable and easier to reason about than thinking about comment tokens in the context of AST nodes. +- This behavior was relying on comment attachment happening at the parser level, which does not happen anymore, to ensure that all comments would be accounted for. +- Thinking of comments in the context of tokens is more predictable and easier to reason about than thinking about comment tokens in the context of AST nodes. **To address:** Instead of relying on `LineComment` and `BlockComment`, rules can now use `sourceCode.getAllComments()` to get all comments in a file. To check all comments of a specific type, rules can use the following pattern: @@ -216,7 +214,7 @@ Previously, the `linter.verify()` API accepted a `global` config option, which w ## More report messages now have full location ranges -Starting in 3.1.0, rules have been able to specify the *end* location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a *node* is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations. +Starting in 3.1.0, rules have been able to specify the _end_ location of a reported problem, in addition to the start location, by explicitly specifying an end location in the `report` call. This is useful for tools like editor integrations, which can use the range to precisely display where a reported problem occurs. Starting in 4.0, if a _node_ is reported rather than a location, the end location of the range will automatically be inferred from the end location of the node. As a result, many more reported problems will have end locations. This is not expected to cause breakage. However, it will likely result in larger report locations than before. For example, if a rule reports the root node of the AST, the reported problem's range will be the entire program. In some integrations, this could result in a poor user experience (e.g. if the entire program is highlighted to indicate an error). diff --git a/docs/src/use/migrating-to-5.0.0.md b/docs/src/use/migrating-to-5.0.0.md index f426cde1c3ca..b455000c82c1 100644 --- a/docs/src/use/migrating-to-5.0.0.md +++ b/docs/src/use/migrating-to-5.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v5.0.0 - --- ESLint v5.0.0 is the fifth major version release. We have made a few breaking changes in this release, but we expect that most users will be able to upgrade without any modifications to their build. This guide is intended to walk you through the breaking changes. @@ -42,9 +41,9 @@ The lists below are ordered roughly by the number of users each change is expect As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving security updates. As a result, we have decided to drop support for it in ESLint v5. We now support the following versions of Node.js: -* Node.js 6 (6.14.0 and above) -* Node.js 8 (8.10.0 and above) -* Anything above Node.js 9.10.0 +- Node.js 6 (6.14.0 and above) +- Node.js 8 (8.10.0 and above) +- Anything above Node.js 9.10.0 **To address:** Make sure you upgrade to at least Node.js 6 when using ESLint v5. If you are unable to upgrade, we recommend continuing to use ESLint v4.x until you are able to upgrade Node.js. @@ -52,19 +51,19 @@ As of April 30th, 2018, Node.js 4 will be at EOL and will no longer be receiving Two new rules have been added to the [`eslint:recommended`](configure/configuration-files#using-eslintrecommended) config: -* [`for-direction`](../rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. -* [`getter-return`](../rules/getter-return) enforces that a `return` statement is present in property getters. +- [`for-direction`](../rules/for-direction) enforces that a `for` loop update clause moves the counter in the right direction. +- [`getter-return`](../rules/getter-return) enforces that a `return` statement is present in property getters. **To address:** To mimic the `eslint:recommended` behavior from 4.x, you can disable these rules in a config file: ```json { - "extends": "eslint:recommended", + "extends": "eslint:recommended", - "rules": { - "for-direction": "off", - "getter-return": "off" - } + "rules": { + "for-direction": "off", + "getter-return": "off" + } } ``` @@ -74,11 +73,11 @@ Previously, when using the default parser it was possible to use the `experiment ```json { - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } - } + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + } } ``` @@ -86,9 +85,9 @@ Object rest/spread is now an official part of the JavaScript language, so our su ```json { - "parserOptions": { - "ecmaVersion": 2018 - } + "parserOptions": { + "ecmaVersion": 2018 + } } ``` @@ -110,8 +109,8 @@ Many users found this behavior confusing, because if they made a typo in a filen ESLint v5 will report a fatal error when either of the following conditions is met: -* A file provided on the command line does not exist. -* A glob or folder provided on the command line does not match any lintable files. +- A file provided on the command line does not exist. +- A glob or folder provided on the command line does not match any lintable files. Note that this also affects the [`CLIEngine.executeOnFiles()`](../integrate/nodejs-api#cliengineexecuteonfiles) API. @@ -121,17 +120,17 @@ If you use a boilerplate generator that relies on this behavior (e.g. to generat ## The default options for some rules have changed -* The default options for the [`object-curly-newline`](../rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. -* The default options object for the [`no-self-assign`](../rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. +- The default options for the [`object-curly-newline`](../rules/object-curly-newline) rule have changed from `{ multiline: true }` to `{ consistent: true }`. +- The default options object for the [`no-self-assign`](../rules/no-self-assign) rule has changed from `{ props: false }` to `{ props: true }`. **To address:** To restore the rule behavior from ESLint v4, you can update your config file to include the previous options: ```json { - "rules": { - "object-curly-newline": ["error", { "multiline": true }], - "no-self-assign": ["error", { "props": false }] - } + "rules": { + "object-curly-newline": ["error", { "multiline": true }], + "no-self-assign": ["error", { "props": false }] + } } ``` @@ -143,12 +142,12 @@ Some global variables have been deprecated or removed for code running in Node.j ```json { - "env": { - "browser": true - }, - "globals": { - "SVGAltGlyphElement": false - } + "env": { + "browser": true + }, + "globals": { + "SVGAltGlyphElement": false + } } ``` @@ -173,8 +172,9 @@ When ESLint v5 encounters a plugin name in a config starting with `@`, the plugi `eslint-disable-line` and `eslint-disable-next-line` directive comments are only allowed to span a single line. For example, the following directive comment is invalid: ```js -alert('foo'); /* eslint-disable-line - no-alert */ alert('bar'); +alert("foo"); +/* eslint-disable-line + no-alert */ alert("bar"); // (which line is the rule disabled on?) ``` @@ -257,8 +257,8 @@ As announced in [October 2016](/blog/2016/10/eslint-v3.8.0-released#additional-p When using ESLint v4, both of the following scenarios resulted in an exit code of 1 when running ESLint on the command line: -* Linting completed successfully, but there are some linting errors -* Linting was unsuccessful due to a fatal error (e.g. an invalid config file) +- Linting completed successfully, but there are some linting errors +- Linting was unsuccessful due to a fatal error (e.g. an invalid config file) As a result, it was difficult for an integration to distinguish between the two cases to determine whether it should try to extract linting results from the output. diff --git a/docs/src/use/migrating-to-6.0.0.md b/docs/src/use/migrating-to-6.0.0.md index 0d2595ee062a..26edbcb7f172 100644 --- a/docs/src/use/migrating-to-6.0.0.md +++ b/docs/src/use/migrating-to-6.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v6.0.0 - --- ESLint v6.0.0 is a major release of ESLint. We have made a few breaking changes in this release. This guide is intended to walk you through the breaking changes. @@ -41,9 +40,9 @@ The lists below are ordered roughly by the number of users each change is expect As of April 2019, Node.js 6 will be at EOL and will no longer be receiving security updates. As a result, we have decided to drop support for it in ESLint v6. We now support the following versions of Node.js: -* Node.js 8 (8.10.0 and above) -* Node.js 10 (10.13.0 and above) -* Anything above Node.js 11.10.1 +- Node.js 8 (8.10.0 and above) +- Node.js 10 (10.13.0 and above) +- Anything above Node.js 11.10.1 **To address:** Make sure you upgrade to at least Node.js 8 when using ESLint v6. If you are unable to upgrade, we recommend continuing to use ESLint v5.x until you are able to upgrade Node.js. @@ -53,17 +52,17 @@ As of April 2019, Node.js 6 will be at EOL and will no longer be receiving secur The following rules have been added to the [`eslint:recommended`](../use/configure#using-eslintrecommended) config: -* [`no-async-promise-executor`](../rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. -* [`no-misleading-character-class`](../rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. -* [`no-prototype-builtins`](../rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. -* [`no-shadow-restricted-names`](../rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. -* [`no-useless-catch`](../rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. -* [`no-with`](../rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. -* [`require-atomic-updates`](../rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. +- [`no-async-promise-executor`](../rules/no-async-promise-executor) disallows using an `async` function as the argument to the `Promise` constructor, which is usually a bug. +- [`no-misleading-character-class`](../rules/no-misleading-character-class) reports character classes in regular expressions that might not behave as expected. +- [`no-prototype-builtins`](../rules/no-prototype-builtins) reports method calls like `foo.hasOwnProperty("bar")` (which are a frequent source of bugs), and suggests that they be replaced with `Object.prototype.hasOwnProperty.call(foo, "bar")` instead. +- [`no-shadow-restricted-names`](../rules/no-shadow-restricted-names) disallows shadowing variables like `undefined` (e.g. with code like `let undefined = 5;`), since is likely to confuse readers. +- [`no-useless-catch`](../rules/no-useless-catch) reports `catch` clauses that are redundant and can be removed from the code without changing its behavior. +- [`no-with`](../rules/no-with) disallows use of the [`with` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with), which can make code difficult to understand and cause compatibility problems. +- [`require-atomic-updates`](../rules/require-atomic-updates) reports race condition bugs that can occur when reassigning variables in async functions. -Additionally, the following rule has been *removed* from `eslint:recommended`: +Additionally, the following rule has been _removed_ from `eslint:recommended`: -* [`no-console`](../rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). +- [`no-console`](../rules/no-console) disallows calling functions like `console.log`. While this rule is useful in many cases (e.g. to avoid inadvertently leaving debugging statements in production code), it is not as broadly applicable as the other rules in `eslint:recommended`, and it was a source of false positives in cases where `console.log` is acceptable (e.g. in CLI applications). Finally, in ESLint v5 `eslint:recommended` would explicitly disable all core rules that were not considered "recommended". This could cause confusing behavior if `eslint:recommended` was loaded after another config, since `eslint:recommended` would have the effect of turning off some rules. In ESLint v6, `eslint:recommended` has no effect on non-recommended rules. @@ -71,19 +70,19 @@ Finally, in ESLint v5 `eslint:recommended` would explicitly disable all core rul ```json { - "extends": "eslint:recommended", - - "rules": { - "no-async-promise-executor": "off", - "no-misleading-character-class": "off", - "no-prototype-builtins": "off", - "no-shadow-restricted-names": "off", - "no-useless-catch": "off", - "no-with": "off", - "require-atomic-updates": "off", - - "no-console": "error" - } + "extends": "eslint:recommended", + + "rules": { + "no-async-promise-executor": "off", + "no-misleading-character-class": "off", + "no-prototype-builtins": "off", + "no-shadow-restricted-names": "off", + "no-useless-catch": "off", + "no-with": "off", + "require-atomic-updates": "off", + + "no-console": "error" + } } ``` @@ -107,9 +106,9 @@ If you use a config file located outside of a local project (with the `--config` `espree`, the default parser used by ESLint, will now raise an error in the following cases: -* The `ecmaVersion` parser option is set to something other than a number, such as the string `"2015"`. (Previously, a non-number option would simply be ignored.) -* The `sourceType: "module"` parser option is set while `ecmaVersion` is set to `5` or left unspecified. (Previously, setting `sourceType: "module"` would implicitly cause `ecmaVersion` to be set to a minimum of 2015, which could be surprising.) -* The `sourceType` is set to anything other than `"script"` or `"module"`. +- The `ecmaVersion` parser option is set to something other than a number, such as the string `"2015"`. (Previously, a non-number option would simply be ignored.) +- The `sourceType: "module"` parser option is set while `ecmaVersion` is set to `5` or left unspecified. (Previously, setting `sourceType: "module"` would implicitly cause `ecmaVersion` to be set to a minimum of 2015, which could be surprising.) +- The `sourceType` is set to anything other than `"script"` or `"module"`. **To address:** If your config sets `ecmaVersion` to something other than a number, you can restore the previous behavior by removing `ecmaVersion`. (However, you may want to double-check that your config is actually working as expected.) If your config sets `parserOptions: { sourceType: "module" }` without also setting `parserOptions.ecmaVersion`, you should add `parserOptions: { ecmaVersion: 2015 }` to restore the previous behavior. @@ -119,13 +118,13 @@ If you use a config file located outside of a local project (with the `--config` To catch config errors earlier, ESLint v6 will report a linting error if you are trying to configure a non-existent rule. -config | ESLint v5 | ESLint v6 -------------- | ------------- | ------------- -`/*eslint-enable foo*/` | no error | linting error -`/*eslint-disable(-line) foo*/` | no error | linting error -`/*eslint foo: 0*/` | no error | linting error -`{rules: {foo: 0}}` | no error | no error -`{rules: {foo: 1}` | linting warning | linting error +| config | ESLint v5 | ESLint v6 | +| ------------------------------- | --------------- | ------------- | +| `/*eslint-enable foo*/` | no error | linting error | +| `/*eslint-disable(-line) foo*/` | no error | linting error | +| `/*eslint foo: 0*/` | no error | linting error | +| `{rules: {foo: 0}}` | no error | no error | +| `{rules: {foo: 1}` | linting warning | linting error | **To address:** You can remove the non-existent rule in your (inline) config. @@ -141,9 +140,9 @@ To restore the previous options for the rule, you can configure it as follows: ```json { - "rules": { - "no-redeclare": ["error", { "builtinGlobals": false }] - } + "rules": { + "no-redeclare": ["error", { "builtinGlobals": false }] + } } ``` @@ -159,15 +158,18 @@ Previously, the [`comma-dangle`](../rules/comma-dangle) rule would ignore traili ```json { - "rules": { - "comma-dangle": ["error", { - "arrays": "never", - "objects": "never", - "imports": "never", - "exports": "never", - "functions": "ignore" - }] - } + "rules": { + "comma-dangle": [ + "error", + { + "arrays": "never", + "objects": "never", + "imports": "never", + "exports": "never", + "functions": "ignore" + } + ] + } } ``` @@ -183,9 +185,9 @@ The default options for the [`no-confusing-arrow`](../rules/no-confusing-arrow) ```json { - "rules": { - "no-confusing-arrow": ["error", { "allowParens": false }] - } + "rules": { + "no-confusing-arrow": ["error", { "allowParens": false }] + } } ``` @@ -206,22 +208,22 @@ Due to a bug, it was previously the case that an `overrides` block in a shareabl ```js // .eslintrc.js module.exports = { - extends: ["foo"], - rules: { - semi: "off" - } + extends: ["foo"], + rules: { + semi: "off", + }, }; ``` ```js // eslint-config-foo/index.js module.exports = { - overrides: { - files: ["*.js"], - rules: { - semi: "error" - } - } + overrides: { + files: ["*.js"], + rules: { + semi: "error", + }, + }, }; ``` @@ -238,11 +240,11 @@ Previously, when configuring a set of global variables with an object, it was po ```js // .eslintrc.js module.exports = { - globals: { - foo: "readonly", - bar: "writable", - baz: "hello!" // ??? - } + globals: { + foo: "readonly", + bar: "writable", + baz: "hello!", // ??? + }, }; ``` @@ -256,11 +258,11 @@ Previously, when using the default parser, a config could use the `experimentalO ```json { - "parserOptions": { - "ecmaFeatures": { - "experimentalObjectRestSpread": true - } - } + "parserOptions": { + "ecmaFeatures": { + "experimentalObjectRestSpread": true + } + } } ``` @@ -270,9 +272,9 @@ Since ESLint v5, `ecmaFeatures: { experimentalObjectRestSpread: true }` has been ```json { - "parserOptions": { - "ecmaVersion": 2018 - } + "parserOptions": { + "ecmaVersion": 2018 + } } ``` diff --git a/docs/src/use/migrating-to-7.0.0.md b/docs/src/use/migrating-to-7.0.0.md index 86b81cf832a6..7d28913a9d28 100644 --- a/docs/src/use/migrating-to-7.0.0.md +++ b/docs/src/use/migrating-to-7.0.0.md @@ -1,6 +1,5 @@ --- title: Migrating to v7.0.0 - --- ESLint v7.0.0 is a major release of ESLint. We have made a few breaking changes in this release. This guide is intended to walk you through the breaking changes. @@ -11,29 +10,29 @@ The lists below are ordered roughly by the number of users each change is expect ### Breaking changes for users -* [Node.js 8 is no longer supported](#drop-node-8) -* [Lint files matched by `overrides[].files` by default](#additional-lint-targets) -* [The base path of `overrides` and `ignorePatterns` is changed if the config file is given by the `--config`/`--ignore-path` options](#base-path-change) -* [The place where ESLint loads plugins from is changed](#plugin-loading-change) -* [Runtime deprecation warnings for `~/.eslintrc.*` config files](#runtime-deprecation-on-personal-config-files) -* [Default ignore patterns have changed](#default-ignore-patterns) -* [Description in directive comments](#description-in-directive-comments) -* [Node.js/CommonJS rules are deprecated](#deprecate-node-rules) -* [Several rules have been updated to cover more cases](#rules-strict) -* [`eslint:recommended` has been updated](#eslint-recommended) +- [Node.js 8 is no longer supported](#drop-node-8) +- [Lint files matched by `overrides[].files` by default](#additional-lint-targets) +- [The base path of `overrides` and `ignorePatterns` is changed if the config file is given by the `--config`/`--ignore-path` options](#base-path-change) +- [The place where ESLint loads plugins from is changed](#plugin-loading-change) +- [Runtime deprecation warnings for `~/.eslintrc.*` config files](#runtime-deprecation-on-personal-config-files) +- [Default ignore patterns have changed](#default-ignore-patterns) +- [Description in directive comments](#description-in-directive-comments) +- [Node.js/CommonJS rules are deprecated](#deprecate-node-rules) +- [Several rules have been updated to cover more cases](#rules-strict) +- [`eslint:recommended` has been updated](#eslint-recommended) ### Breaking changes for plugin developers -* [Node.js 8 is no longer supported](#drop-node-8) -* [Lint files matched by `overrides[].files` by default](#additional-lint-targets) -* [Plugin resolution has been updated](#plugin-loading-change) -* [Additional validation added to the `RuleTester` class](#rule-tester-strict) +- [Node.js 8 is no longer supported](#drop-node-8) +- [Lint files matched by `overrides[].files` by default](#additional-lint-targets) +- [Plugin resolution has been updated](#plugin-loading-change) +- [Additional validation added to the `RuleTester` class](#rule-tester-strict) ### Breaking changes for integration developers -* [Node.js 8 is no longer supported](#drop-node-8) -* [Plugin resolution has been updated](#plugin-loading-change) -* [The `CLIEngine` class has been deprecated](#deprecate-cliengine) +- [Node.js 8 is no longer supported](#drop-node-8) +- [Plugin resolution has been updated](#plugin-loading-change) +- [The `CLIEngine` class has been deprecated](#deprecate-cliengine) --- @@ -41,8 +40,8 @@ The lists below are ordered roughly by the number of users each change is expect Node.js 8 reached EOL in December 2019, and we are officially dropping support for it in this release. ESLint now supports the following versions of Node.js: -* Node.js 10 (`10.12.0` and above) -* Node.js 12 and above +- Node.js 10 (`10.12.0` and above) +- Node.js 12 and above **To address:** Make sure you upgrade to at least Node.js `10.12.0` when using ESLint v7.0.0. One important thing to double check is the Node.js version supported by your editor when using ESLint via editor integrations. If you are unable to upgrade, we recommend continuing to use ESLint 6 until you are able to upgrade Node.js. @@ -58,13 +57,13 @@ ESLint v7.0.0 will now additionally lint files with other extensions (`.ts`, `.v # .eslintrc.yml extends: my-config-js overrides: - - files: "*.ts" - extends: my-config-ts + - files: "*.ts" + extends: my-config-ts ``` then running `eslint src` would check both `*.js` and `*.ts` files in the `src` directory. -**To address:** Using the `--ext` CLI option will override this new behavior. Run ESLint with `--ext .js` if you are using `overrides` but only want to lint files that have a `.js` extension. +**To address:** Using the `--ext` CLI option will override this new behavior. Run ESLint with `--ext .js` if you are using `overrides` but only want to lint files that have a `.js` extension. If you maintain plugins that check files with extensions other than `.js`, this feature will allow you to check these files by default by configuring an `overrides` setting in your `recommended` preset. @@ -74,12 +73,12 @@ If you maintain plugins that check files with extensions other than `.js`, this Up until now, ESLint has resolved the following paths relative to the directory path of the _entry_ configuration file: -* Configuration files (`.eslintrc.*`) - * relative paths in the `overrides[].files` setting - * relative paths in the `overrides[].excludedFiles` setting - * paths which start with `/` in the `ignorePatterns` setting -* Ignore files (`.eslintignore`) - * paths which start with `/` +- Configuration files (`.eslintrc.*`) + - relative paths in the `overrides[].files` setting + - relative paths in the `overrides[].excludedFiles` setting + - paths which start with `/` in the `ignorePatterns` setting +- Ignore files (`.eslintignore`) + - paths which start with `/` Starting in ESLint v7.0.0, configuration files and ignore files passed to ESLint using the `--config path/to/a-config` and `--ignore-path path/to/a-ignore` CLI flags, respectively, will resolve from the current working directory rather than the file location. This allows for users to utilize shared plugins without having to install them directly in their project. @@ -116,14 +115,14 @@ Personal config files have been deprecated since [v6.7.0](https://eslint.org/blo Up until now, ESLint has ignored the following files by default: -* Dotfiles (`.*`) -* `node_modules` in the current working directory (`/node_modules/*`) -* `bower_components` in the current working directory (`/bower_components/*`) +- Dotfiles (`.*`) +- `node_modules` in the current working directory (`/node_modules/*`) +- `bower_components` in the current working directory (`/bower_components/*`) ESLint v7.0.0 ignores `node_modules/*` of subdirectories as well, but no longer ignores `bower_components/*` and `.eslintrc.js`. Therefore, the new default ignore patterns are: -* Dotfiles except `.eslintrc.*` (`.*` but not `.eslintrc.*`) -* `node_modules` (`/**/node_modules/*`) +- Dotfiles except `.eslintrc.*` (`.*` but not `.eslintrc.*`) +- `node_modules` (`/**/node_modules/*`) **To address:** Modify your `.eslintignore` or the `ignorePatterns` property of your config file if you don't want to lint `bower_components/*` and `.eslintrc.js`. @@ -149,8 +148,8 @@ The ten Node.js/CommonJS rules in core have been deprecated and moved to the [es **To address:** As per [our deprecation policy](../use/rule-deprecation), the deprecated rules will remain in core for the foreseeable future and are still available for use. However, we will no longer be updating or fixing any bugs in those rules. To use a supported version of the rules, we recommend using the corresponding rules in the plugin instead. -| Deprecated Rules | Replacement | -| :--------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ | +| Deprecated Rules | Replacement | +| :------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------ | | [callback-return](../rules/callback-return) | [node/callback-return](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/callback-return.md) | | [global-require](../rules/global-require) | [node/global-require](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/global-require.md) | | [handle-callback-err](../rules/handle-callback-err) | [node/handle-callback-err](https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/handle-callback-err.md) | @@ -168,16 +167,16 @@ The ten Node.js/CommonJS rules in core have been deprecated and moved to the [es Several rules have been enhanced and now report additional errors: -* [accessor-pairs](../rules/accessor-pairs) rule now recognizes class members by default. -* [array-callback-return](../rules/array-callback-return) rule now recognizes `flatMap` method. -* [computed-property-spacing](../rules/computed-property-spacing) rule now recognizes class members by default. -* [func-names](../rules/func-names) rule now recognizes function declarations in default exports. -* [no-extra-parens](../rules/no-extra-parens) rule now recognizes parentheses in assignment targets. -* [no-dupe-class-members](../rules/no-dupe-class-members) rule now recognizes computed keys for static class members. -* [no-magic-numbers](../rules/no-magic-numbers) rule now recognizes bigint literals. -* [radix](../rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. -* [use-isnan](../rules/use-isnan) rule now recognizes class members by default. -* [yoda](../rules/yoda) rule now recognizes bigint literals. +- [accessor-pairs](../rules/accessor-pairs) rule now recognizes class members by default. +- [array-callback-return](../rules/array-callback-return) rule now recognizes `flatMap` method. +- [computed-property-spacing](../rules/computed-property-spacing) rule now recognizes class members by default. +- [func-names](../rules/func-names) rule now recognizes function declarations in default exports. +- [no-extra-parens](../rules/no-extra-parens) rule now recognizes parentheses in assignment targets. +- [no-dupe-class-members](../rules/no-dupe-class-members) rule now recognizes computed keys for static class members. +- [no-magic-numbers](../rules/no-magic-numbers) rule now recognizes bigint literals. +- [radix](../rules/radix) rule now recognizes invalid numbers for the second parameter of `parseInt()`. +- [use-isnan](../rules/use-isnan) rule now recognizes class members by default. +- [yoda](../rules/yoda) rule now recognizes bigint literals. **To address:** Fix errors or disable these rules. @@ -187,9 +186,9 @@ Several rules have been enhanced and now report additional errors: Three new rules have been enabled in the `eslint:recommended` preset. -* [no-dupe-else-if](../rules/no-dupe-else-if) -* [no-import-assign](../rules/no-import-assign) -* [no-setter-return](../rules/no-setter-return) +- [no-dupe-else-if](../rules/no-dupe-else-if) +- [no-import-assign](../rules/no-import-assign) +- [no-setter-return](../rules/no-setter-return) **To address:** Fix errors or disable these rules. @@ -199,9 +198,9 @@ Three new rules have been enabled in the `eslint:recommended` preset. The `RuleTester` now validates the following: -* It fails test cases if the rule under test uses the non-standard `node.start` or `node.end` properties. Rules should use `node.range` instead. -* It fails test cases if the rule under test provides an autofix but a test case doesn't have an `output` property. Add an `output` property to test cases to test the rule's autofix functionality. -* It fails test cases if any unknown properties are found in the objects in the `errors` property. +- It fails test cases if the rule under test uses the non-standard `node.start` or `node.end` properties. Rules should use `node.range` instead. +- It fails test cases if the rule under test provides an autofix but a test case doesn't have an `output` property. Add an `output` property to test cases to test the rule's autofix functionality. +- It fails test cases if any unknown properties are found in the objects in the `errors` property. **To address:** Modify your rule or test case if existing test cases fail. @@ -229,7 +228,7 @@ The `CLIEngine` class provides a synchronous API that is blocking the implementa | `getRules()` | (not implemented yet) | | `resolveFileGlobPatterns()` | (removed â€ģ2) | -* â€ģ1 The `engine.getFormatter()` method currently returns the object of loaded packages as-is, which made it difficult to add new features to formatters for backward compatibility reasons. The new `eslint.loadFormatter()` method returns an adapter object that wraps the object of loaded packages, to ease the process of adding new features. Additionally, the adapter object has access to the `ESLint` instance to calculate default data (using loaded plugin rules to make `rulesMeta`, for example). As a result, the `ESLint` class only implements an instance version of the `loadFormatter()` method. -* â€ģ2 Since ESLint 6, ESLint uses different logic from the `resolveFileGlobPatterns()` method to iterate files, making this method obsolete. +- â€ģ1 The `engine.getFormatter()` method currently returns the object of loaded packages as-is, which made it difficult to add new features to formatters for backward compatibility reasons. The new `eslint.loadFormatter()` method returns an adapter object that wraps the object of loaded packages, to ease the process of adding new features. Additionally, the adapter object has access to the `ESLint` instance to calculate default data (using loaded plugin rules to make `rulesMeta`, for example). As a result, the `ESLint` class only implements an instance version of the `loadFormatter()` method. +- â€ģ2 Since ESLint 6, ESLint uses different logic from the `resolveFileGlobPatterns()` method to iterate files, making this method obsolete. **Related issue(s):** [RFC40](https://github.com/eslint/rfcs/blob/master/designs/2019-move-to-async-api/README.md), [#12939](https://github.com/eslint/eslint/pull/12939) diff --git a/docs/src/use/rule-deprecation.md b/docs/src/use/rule-deprecation.md index a4cbfb5a8452..3bd1fa485ce3 100644 --- a/docs/src/use/rule-deprecation.md +++ b/docs/src/use/rule-deprecation.md @@ -1,17 +1,16 @@ --- title: Rule Deprecation - --- Balancing the trade-offs of improving a tool and the frustration these changes can cause is a difficult task. One key area in which this affects our users is in the removal of rules. The ESLint team is committed to making upgrading as easy and painless as possible. To that end, the team has agreed upon the following set of guidelines for deprecating rules in the future. The goal of these guidelines is to allow for improvements and changes to be made without breaking existing configurations. -* Rules will never be removed from ESLint unless one of the following is true: - * The rule has been replaced by another core rule. - * A plugin exists with a functionally equivalent rule. -* Rules will be deprecated as needed, and marked as such in all documentation. -* After a rule has been deprecated, the team will no longer do any work on it. This includes bug fixes, enhancements, and updates to the rule's documentation. Issues and pull requests related to deprecated rule will not be accepted and will be closed. +- Rules will never be removed from ESLint unless one of the following is true: + - The rule has been replaced by another core rule. + - A plugin exists with a functionally equivalent rule. +- Rules will be deprecated as needed, and marked as such in all documentation. +- After a rule has been deprecated, the team will no longer do any work on it. This includes bug fixes, enhancements, and updates to the rule's documentation. Issues and pull requests related to deprecated rule will not be accepted and will be closed. You can continue to use deprecated rules indefinitely if they are working for you. However, keep in mind that deprecated rules will effectively be unmaintained and may be removed at some point. diff --git a/docs/src/use/troubleshooting/circular-fixes.md b/docs/src/use/troubleshooting/circular-fixes.md new file mode 100644 index 000000000000..e5ca8610b8e9 --- /dev/null +++ b/docs/src/use/troubleshooting/circular-fixes.md @@ -0,0 +1,38 @@ +--- +title: Circular fixes detected â€Ļ +eleventyNavigation: + key: circular fixes + parent: troubleshooting + title: Circular fixes detected â€Ļ +--- + +## Symptoms + +When running ESLint with the `--fix` option, you may see the following warning: + +```plaintext +ESLintCircularFixesWarning: Circular fixes detected while fixing path/to/file. It is likely that you have conflicting rules in your configuration. +``` + +## Cause + +You have conflicting fixable rules in your configuration. ESLint autofixes code in multiple passes, meaning it's possible that a fix in one pass is undone in a subsequent pass. For example, in the first pass a rule removes a trailing comma and in the following pass a different rule adds a trailing comma in the same place, effectively changing the code back to the previous version. ESLint emits a warning when it detects cycles like this. + +## Resolution + +Common resolutions for this issue include: + +- Remove or reconfigure one of the conflicting rules in your configuration file. + +How to find the conflicting rules: + +1. Open the file specified in the warning in an editor that supports applying individual fixes (for example, VS Code). +1. In the list of lint problems, find a fixable rule. That is one of the conflicting rules. +1. Apply the fix ("Fix this (rule-name) problem" action in VS Code). +1. Check what new lint problem has appeared in the list. That is the other conflicting rule. + +## Resources + +For more information, see: + +- [Configure Rules](../configure/rules) for documentation on how to configure rules diff --git a/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md b/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md index 77a870e67f75..90b511ddc392 100644 --- a/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md +++ b/docs/src/use/troubleshooting/couldnt-determine-the-plugin-uniquely.md @@ -29,8 +29,8 @@ Those configurations may depend on plugins to provide certain functionality in t For example, if your config depends on `eslint-plugin-a@2` and `eslint-plugin-b@3`, and you extend `eslint-config-b` that depends on `eslint-plugin-a@1`, then the `eslint-plugin-a` package might have two different versions on disk: -* `node_modules/eslint-plugin-a` -* `node_modules/eslint-plugin-b/node_modules/eslint-plugin-a` +- `node_modules/eslint-plugin-a` +- `node_modules/eslint-plugin-b/node_modules/eslint-plugin-a` If the legacy ESLint configuration system sees that both plugins exists in multiple places with different versions, it won't know which one to use. @@ -41,14 +41,14 @@ The new ["flat" config system](../configure/configuration-files) has you `import Common resolutions for this issue include: -* Upgrading all versions of all packages to their latest version. -* Running `npm dedupe` or the equivalent package manager command to deduplicate packages, if their version ranges are compatible. -* Using `overrides` or the equivalent package manager `package.json` field, to force a specific version of a plugin package. - * Note that this may cause bugs in linting if the plugin package had breaking changes between versions. +- Upgrading all versions of all packages to their latest version. +- Running `npm dedupe` or the equivalent package manager command to deduplicate packages, if their version ranges are compatible. +- Using `overrides` or the equivalent package manager `package.json` field, to force a specific version of a plugin package. + - Note that this may cause bugs in linting if the plugin package had breaking changes between versions. ## Resources For more information, see: -* [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins -* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins +- [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins +- [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins diff --git a/docs/src/use/troubleshooting/couldnt-find-the-config.md b/docs/src/use/troubleshooting/couldnt-find-the-config.md index ccfb85805b95..e7ddbbbd091a 100644 --- a/docs/src/use/troubleshooting/couldnt-find-the-config.md +++ b/docs/src/use/troubleshooting/couldnt-find-the-config.md @@ -24,7 +24,7 @@ For example, the following ESLint config will first try to load a module located ```js module.exports = { - extends: ["eslint-config-yours"], + extends: ["eslint-config-yours"], }; ``` @@ -32,31 +32,31 @@ The error is output when you attempt to extend from a configuration and the pack Common reasons for this occurring include: -* Not running `npm install` or the equivalent package manager command -* Mistyping the case-sensitive name of the package and/or configuration +- Not running `npm install` or the equivalent package manager command +- Mistyping the case-sensitive name of the package and/or configuration ### Config Name Variations Note that ESLint supports several config name formats: -* The `eslint-config-` config name prefix may be omitted for brevity, e.g. `extends: ["yours"]` - * [`@` npm scoped packages](https://docs.npmjs.com/cli/v10/using-npm/scope) put the `eslint-config-` prefix after the org scope, e.g. `extends: ["@org/yours"]` to load from `@org/eslint-config-yours` -* A `plugin:` prefix indicates a config is loaded from a shared plugin, e.g. `extends: [plugin:yours/recommended]` to load from `eslint-plugin-yours` +- The `eslint-config-` config name prefix may be omitted for brevity, e.g. `extends: ["yours"]` + - [`@` npm scoped packages](https://docs.npmjs.com/cli/v10/using-npm/scope) put the `eslint-config-` prefix after the org scope, e.g. `extends: ["@org/yours"]` to load from `@org/eslint-config-yours` +- A `plugin:` prefix indicates a config is loaded from a shared plugin, e.g. `extends: [plugin:yours/recommended]` to load from `eslint-plugin-yours` ## Resolution Common resolutions for this issue include: -* Upgrading all versions of all packages to their latest version. -* Adding the config as a `devDependency` in your `package.json`. -* Running `npm install` or the equivalent package manager command. -* Checking that the name in your config file matches the name of the config package. +- Upgrading all versions of all packages to their latest version. +- Adding the config as a `devDependency` in your `package.json`. +- Running `npm install` or the equivalent package manager command. +- Checking that the name in your config file matches the name of the config package. ## Resources For more information, see: -* [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format - * [Legacy ESLint configuration files > Using a shareable configuration package](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on using shareable configurations -* [Share Configurations](../../extend/shareable-configs) for documentation on how to define standalone shared configs -* [Create Plugins > Configs in Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define shared configs in plugins +- [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format + - [Legacy ESLint configuration files > Using a shareable configuration package](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on using shareable configurations +- [Share Configurations](../../extend/shareable-configs) for documentation on how to define standalone shared configs +- [Create Plugins > Configs in Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define shared configs in plugins diff --git a/docs/src/use/troubleshooting/couldnt-find-the-plugin.md b/docs/src/use/troubleshooting/couldnt-find-the-plugin.md index d0656e5e52d0..1b7d88096132 100644 --- a/docs/src/use/troubleshooting/couldnt-find-the-plugin.md +++ b/docs/src/use/troubleshooting/couldnt-find-the-plugin.md @@ -30,7 +30,7 @@ For example, the following ESLint config will first try to load a module located ```js module.exports = { - extends: ["plugin:eslint-plugin-yours/config-name"], + extends: ["plugin:eslint-plugin-yours/config-name"], }; ``` @@ -38,8 +38,8 @@ If the package is not found in any searched `node_modules/`, ESLint will print t Common reasons for this occurring include: -* Not running `npm install` or the equivalent package manager command -* Mistyping the case-sensitive name of the plugin +- Not running `npm install` or the equivalent package manager command +- Mistyping the case-sensitive name of the plugin ### Plugin Name Variations @@ -51,15 +51,15 @@ Note that the `eslint-plugin-` plugin name prefix may be omitted for brevity, e. Common resolutions for this issue include: -* Upgrading all versions of all packages to their latest version. -* Adding the plugin as a `devDependency` in your `package.json`. -* Running `npm install` or the equivalent package manager command. -* Checking that the name in your config file matches the name of the plugin package. +- Upgrading all versions of all packages to their latest version. +- Adding the plugin as a `devDependency` in your `package.json`. +- Running `npm install` or the equivalent package manager command. +- Checking that the name in your config file matches the name of the plugin package. ## Resources For more information, see: -* [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format -* [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins -* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins +- [Legacy ESLint configuration files](../configure/configuration-files-deprecated#using-a-shareable-configuration-package) for documentation on the legacy ESLint configuration format +- [Configure Plugins](../configure/plugins) for documentation on how to extend from plugins +- [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins diff --git a/docs/src/use/troubleshooting/index.md b/docs/src/use/troubleshooting/index.md index cb876696a443..0f7901fe005c 100644 --- a/docs/src/use/troubleshooting/index.md +++ b/docs/src/use/troubleshooting/index.md @@ -11,13 +11,14 @@ This page serves as a reference for common issues working with ESLint. ## Configuration -* [`TypeError: context.getScope is not a function`](./v9-rule-api-changes) +- [`Circular fixes detected â€Ļ`](./circular-fixes) +- [`TypeError: context.getScope is not a function`](./v9-rule-api-changes) ## Legacy (eslintrc) Configuration -* [`ESLint couldn't determine the plugin â€Ļ uniquely`](./couldnt-determine-the-plugin-uniquely) -* [`ESLint couldn't find the config â€Ļ to extend from`](./couldnt-find-the-config) -* [`ESLint couldn't find the plugin â€Ļ`](./couldnt-find-the-plugin) +- [`ESLint couldn't determine the plugin â€Ļ uniquely`](./couldnt-determine-the-plugin-uniquely) +- [`ESLint couldn't find the config â€Ļ to extend from`](./couldnt-find-the-config) +- [`ESLint couldn't find the plugin â€Ļ`](./couldnt-find-the-plugin) Issues oftentimes can be resolved by updating the to latest versions of the `eslint` package and any related packages, such as for ESLint shareable configs and plugins. diff --git a/docs/src/use/troubleshooting/v9-rule-api-changes.md b/docs/src/use/troubleshooting/v9-rule-api-changes.md index 529e2cfe2c4e..7a82fdf2b03f 100644 --- a/docs/src/use/troubleshooting/v9-rule-api-changes.md +++ b/docs/src/use/troubleshooting/v9-rule-api-changes.md @@ -25,8 +25,8 @@ ESLint v9.0.0 introduces [changes to the rules API](https://eslint.org/blog/2023 Common resolutions for this issue include: -* Upgrade the plugin to the latest version. -* Use the [compatibility utilities](https://eslint.org/blog/2024/05/eslint-compatibility-utilities/) to patch the plugin in your config file. +- Upgrade the plugin to the latest version. +- Use the [compatibility utilities](https://eslint.org/blog/2024/05/eslint-compatibility-utilities/) to patch the plugin in your config file. ::: important If you are already using the latest version of the plugin and you need to use the compatibility utilities to make the plugin work with ESLint v9.0.0 and later, make sure to open an issue on the plugin's repository to ask the maintainer to make the necessary API changes. @@ -36,5 +36,5 @@ If you are already using the latest version of the plugin and you need to use th For more information, see: -* [Configure Plugins](../configure/plugins) for documentation on how to configure plugins -* [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins +- [Configure Plugins](../configure/plugins) for documentation on how to configure plugins +- [Create Plugins](../../extend/plugins#configs-in-plugins) for documentation on how to define plugins diff --git a/docs/tools/code-block-utils.js b/docs/tools/code-block-utils.js index 6c398eea8bd0..75e855d332ec 100644 --- a/docs/tools/code-block-utils.js +++ b/docs/tools/code-block-utils.js @@ -12,18 +12,20 @@ * @returns {string} The parsable code. */ function docsExampleCodeToParsableCode(code) { - return code + return ( + code - // Code blocks always contain an extra line break at the end, so remove it. - .replace(/\n$/u, "") + // Code blocks always contain an extra line break at the end, so remove it. + .replace(/\n$/u, "") - // Replace LF line breaks with CRLF after `\r\n` sequences. - .replace(/(?<=\\r\\n)\n/gu, "\r\n") + // Replace LF line breaks with CRLF after `\r\n` sequences. + .replace(/(?<=\\r\\n)\n/gu, "\r\n") - // Remove presentational `⏎` characters at the end of lines. - .replace(/⏎(?=\n)/gu, ""); + // Remove presentational `⏎` characters at the end of lines. + .replace(/⏎(?=\n)/gu, "") + ); } module.exports = { - docsExampleCodeToParsableCode + docsExampleCodeToParsableCode, }; diff --git a/docs/tools/markdown-it-rule-example.js b/docs/tools/markdown-it-rule-example.js index 7b95e3c4c644..6586eb2b2133 100644 --- a/docs/tools/markdown-it-rule-example.js +++ b/docs/tools/markdown-it-rule-example.js @@ -58,33 +58,44 @@ const { docsExampleCodeToParsableCode } = require("./code-block-utils"); * */ function markdownItRuleExample({ open, close }) { - return { - validate(info) { - return /^\s*(?:in)?correct(?!\S)/u.test(info); - }, - render(tokens, index, options, env) { - const tagToken = tokens[index]; + return { + validate(info) { + return /^\s*(?:in)?correct(?!\S)/u.test(info); + }, + render(tokens, index, options, env) { + const tagToken = tokens[index]; - if (tagToken.nesting < 0) { - const text = close ? close() : void 0; + if (tagToken.nesting < 0) { + const text = close ? close() : void 0; - // Return an empty string to avoid appending unexpected text to the output. - return typeof text === "string" ? text : ""; - } + // Return an empty string to avoid appending unexpected text to the output. + return typeof text === "string" ? text : ""; + } - const { type, languageOptionsJSON } = /^\s*(?\S+)(\s+(?\S.*?))?\s*$/u.exec(tagToken.info).groups; - const languageOptions = languageOptionsJSON ? JSON.parse(languageOptionsJSON) : void 0; - const codeBlockToken = tokens[index + 1]; + const { type, languageOptionsJSON } = + /^\s*(?\S+)(\s+(?\S.*?))?\s*$/u.exec( + tagToken.info, + ).groups; + const languageOptions = languageOptionsJSON + ? JSON.parse(languageOptionsJSON) + : void 0; + const codeBlockToken = tokens[index + 1]; - // Remove trailing newline and presentational `⏎` characters (https://github.com/eslint/eslint/issues/17627): - const code = docsExampleCodeToParsableCode(codeBlockToken.content); + // Remove trailing newline and presentational `⏎` characters (https://github.com/eslint/eslint/issues/17627): + const code = docsExampleCodeToParsableCode(codeBlockToken.content); - const text = open({ type, code, languageOptions, codeBlockToken, env }); + const text = open({ + type, + code, + languageOptions, + codeBlockToken, + env, + }); - // Return an empty string to avoid appending unexpected text to the output. - return typeof text === "string" ? text : ""; - } - }; + // Return an empty string to avoid appending unexpected text to the output. + return typeof text === "string" ? text : ""; + }, + }; } module.exports = markdownItRuleExample; diff --git a/docs/tools/prism-eslint-hook.js b/docs/tools/prism-eslint-hook.js index bbbe764c9d6a..f7552d958bf6 100644 --- a/docs/tools/prism-eslint-hook.js +++ b/docs/tools/prism-eslint-hook.js @@ -19,12 +19,11 @@ let astUtils = null; * to highlight the code. */ try { - Linter = require("../../lib/api").Linter; - astUtils = require("../../lib/shared/ast-utils"); - isAvailable = true; + Linter = require("../../lib/api").Linter; + astUtils = require("../../lib/shared/ast-utils"); + isAvailable = true; } catch { - - // ignore + // ignore } /** @typedef {import("../../lib/shared/types").LanguageOptions} LanguageOptions */ @@ -48,8 +47,8 @@ let contentLanguageOptions; * @returns {void} */ function addContentMustBeMarked(content, options) { - contentMustBeMarked = content; - contentLanguageOptions = options; + contentMustBeMarked = content; + contentLanguageOptions = options; } /** @@ -57,382 +56,451 @@ function addContentMustBeMarked(content, options) { * @returns {void} */ function installPrismESLintMarkerHook() { - - /** - * A token type for marking the range reported by a rule. - * This is also used for the `class` attribute of ``. - */ - const TOKEN_TYPE_ESLINT_MARKED = "eslint-marked"; - - /** - * Use in the class attribute of `` when an error is displayed in the BOM code or empty string. - */ - const CLASS_ESLINT_MARKED_ON_ZERO_WIDTH = "eslint-marked-on-zero-width"; - - /** - * Use in the class attribute of `` when an error is displayed in the line-feed. - */ - const CLASS_ESLINT_MARKED_ON_LINE_FEED = "eslint-marked-on-line-feed"; - - /** - * A Map that holds message IDs and messages. - * @type {Map} - */ - const messageMap = new Map(); - - /** - * Gets the message ID from the given message. - * @param {string} message Message - * @returns {string} Message ID - */ - function getMessageIdFromMessage(message) { - let messageId; - - for (const [key, value] of messageMap.entries()) { - if (value === message) { - messageId = key; - break; - } - } - if (!messageId) { - messageId = `eslint-message-id-${messageMap.size + 1}`; - messageMap.set(messageId, message); - } - return messageId; - } - - - const linter = new Linter({ configType: "flat" }); - - Prism.hooks.add("after-tokenize", env => { - messageMap.clear(); - - if (contentMustBeMarked !== env.code) { - - // Ignore unmarked content. - return; - } - contentMustBeMarked = void 0; - const config = contentLanguageOptions ? { languageOptions: contentLanguageOptions } : {}; - - const code = env.code; - - /** Copied from SourceCode constructor */ - const lineStartIndices = [0]; - const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); - let match; - - while ((match = lineEndingPattern.exec(code))) { - lineStartIndices.push(match.index + match[0].length); - } - - /** - * Converts a (line, column) pair into a range index. - * @param {Object} loc A line/column location - * @param {number} loc.line The line number of the location (1-indexed) - * @param {number} loc.column The column number of the location (1-indexed) - * @returns {number} The range index of the location in the file. - * Copied from SourceCode#getIndexFromLoc - */ - function getIndexFromLoc(loc) { - const lineStartIndex = lineStartIndices[loc.line - 1]; - const positionIndex = lineStartIndex + loc.column - 1; - - return positionIndex; - } - - /* - * Run lint to extract the error range. - */ - const lintMessages = linter.verify( - - // Remove trailing newline and presentational `⏎` characters - docsExampleCodeToParsableCode(code), - config, - { filename: "code.js" } - ); - - if (lintMessages.some(m => m.fatal)) { - - // ESLint fatal error. - return; - } - const messages = lintMessages.map(message => { - const start = getIndexFromLoc({ - line: message.line, - column: message.column - }); - - return { - message: message.message, - range: [ - start, - typeof message.endLine === "undefined" - ? start - : getIndexFromLoc({ - line: message.endLine, - column: message.endColumn - }) - ] - }; - }); - - /** - * Get the content of the token. - * @param {string | Prism.Token} token The token - * @returns {string} The content of the token - */ - function getTokenContent(token) { - if (typeof token === "string") { - return token; - } - if (typeof token.content === "string") { - return token.content; - } - return [token.content].flat().map(getTokenContent).join(""); - } - - /** - * @typedef {Object} SplitTokenResult - * @property {string | Prism.Token | null} before The token before the marked range - * @property {Object} marked The marked token information - * @property {Prism.Token} marked.token The token with the marked range - * @property {boolean} marked.canBeMerged If true, it can be merged with previous and subsequent marked tokens. - * @property {string | Prism.Token | null} after The token after the marked range - */ - - /** - * Splits the given token into the `eslint-marked` token and the token before and after it with the specified range. - * @param {Object} params Parameters - * @param {string | Prism.Token} params.token Token to be split - * @param {[number, number]} params.range Range to be marked - * @param {string} params.message Report message - * @param {number} params.tokenStart Starting position of the token - * @returns {SplitTokenResult} Splitted tokens - */ - function splitToken({ token, range, message, tokenStart }) { - - const content = getTokenContent(token); - const tokenEnd = tokenStart + content.length; - - if (range[0] <= tokenStart && tokenEnd <= range[1]) { - - // The token is in range. - const marked = new Prism.Token( - TOKEN_TYPE_ESLINT_MARKED, - [token], - [getMessageIdFromMessage(message)] - ); - - return { before: null, marked: { token: marked, canBeMerged: true }, after: null }; - } - - let buildToken; - - if (typeof token === "string") { - buildToken = newContent => newContent; - } else { - if (typeof token.content !== "string") { - if (token.content.every(childContent => typeof childContent === "string")) { - - // It can be flatten. - buildToken = newContent => new Prism.Token(token.type, newContent, token.alias); - } else { - // eslint-disable-next-line no-use-before-define -- Safe - token.content = [...convertMarked({ tokens: token.content, range, message, tokenStart })]; - return { before: null, marked: { token, canBeMerged: false }, after: null }; - } - } else { - buildToken = newContent => new Prism.Token(token.type, newContent, token.alias); - } - } - - const before = tokenStart < range[0] ? buildToken(content.slice(0, range[0] - tokenStart)) : null; - const mark = content.slice(before ? range[0] - tokenStart : 0, range[1] - tokenStart); - const marked = new Prism.Token( - TOKEN_TYPE_ESLINT_MARKED, - mark ? [buildToken(mark)] : mark, - [getMessageIdFromMessage(message)] - ); - const after = range[1] - tokenStart < content.length ? buildToken(content.slice(range[1] - tokenStart)) : null; - - return { before, marked: { token: marked, canBeMerged: true }, after }; - } - - /** - * Splits the given ESLint marked tokens with line feed code. - * The line feed code is not displayed because there is no character width, - * so by making it a single character token, the "wrap hook" applies CLASS_ESLINT_MARKED_ON_LINE_FEED - * to each character and makes it visible. - * @param {Prism.Token} token Token to be split - * @returns {IterableIterator} Splitted tokens - */ - function *splitMarkedTokenByLineFeed(token) { - for (const contentToken of [token.content].flat()) { - if (typeof contentToken !== "string") { - const content = getTokenContent(contentToken); - - if (/[\r\n]/u.test(content)) { - yield new Prism.Token(token.type, [...splitMarkedTokenByLineFeed(contentToken)], token.alias); - } else { - yield new Prism.Token(token.type, [contentToken], token.alias); - } - continue; - } - if (!/[\r\n]/u.test(contentToken)) { - yield new Prism.Token(token.type, [contentToken], token.alias); - continue; - } - const contents = []; - const reLineFeed = /\r\n?|\n/ug; - let startIndex = 0; - let matchLineFeed; - - while ((matchLineFeed = reLineFeed.exec(contentToken))) { - contents.push(contentToken.slice(startIndex, matchLineFeed.index)); - contents.push(matchLineFeed[0]); - startIndex = reLineFeed.lastIndex; - } - contents.push(contentToken.slice(startIndex)); - yield* contents.filter(Boolean).map(str => new Prism.Token(token.type, [str], token.alias)); - } - } - - /** - * Splits the given tokens by line feed code. - * @param {Prism.Token[]} tokens Token to be Separate - * @returns {IterableIterator} Splitted tokens - */ - function *splitTokensByLineFeed(tokens) { - for (const token of tokens) { - if (typeof token === "string") { - yield token; - continue; - } - - const content = getTokenContent(token); - - if (!/[\r\n]/u.test(content)) { - yield token; - continue; - } - if (token.type === TOKEN_TYPE_ESLINT_MARKED) { - yield* splitMarkedTokenByLineFeed(token); - continue; - } - - if (typeof token.content !== "string") { - token.content = [...splitTokensByLineFeed([token.content].flat())]; - } - yield token; - } - } - - /** - * Generates a token stream with the `eslint-marked` class assigned to the error range. - * @param {Object} params Parameters - * @param {string | Prism.Token | (string | Prism.Token[])} params.tokens Tokens to be converted - * @param {[number, number]} params.range Range to be marked - * @param {string} params.message Report message - * @param {number} params.tokenStart Starting position of the tokens - * @returns {IterableIterator} converted tokens - */ - function *convertMarked({ tokens, range, message, tokenStart = 0 }) { - let start = tokenStart; - - const buffer = [tokens].flat(); - - let token; - - // Find the first token to mark - while ((token = buffer.shift())) { - const content = getTokenContent(token); - const end = start + content.length; - - if (!content || end <= range[0]) { - yield token; - start = end; - continue; - } - - break; - } - if (!token) { - return; - } - - // Mark the token. - const { before, marked, after } = splitToken({ token, range, message, tokenStart: start }); - - if (before) { - yield before; - } - if (after) { - yield* splitTokensByLineFeed([marked.token]); - yield after; - } else { - - // Subsequent tokens may still be present in the range. - let nextTokenStartIndex = start + getTokenContent(token).length; - let prevMarked = marked; - let nextAfter; - - while (nextTokenStartIndex < range[1] && buffer.length) { - const nextToken = buffer.shift(); - const next = splitToken({ token: nextToken, range, message, tokenStart: nextTokenStartIndex }); - - if (prevMarked.canBeMerged && next.marked.canBeMerged) { - prevMarked.token.content.push(...next.marked.token.content); - } else { - yield* splitTokensByLineFeed([prevMarked.token]); - prevMarked = next.marked; - } - if (next.after) { - nextAfter = next.after; - } - nextTokenStartIndex += getTokenContent(nextToken).length; - } - - yield* splitTokensByLineFeed([prevMarked.token]); - if (nextAfter) { - yield nextAfter; - } - } - - yield* buffer; - } - - for (const { range, message } of messages) { - env.tokens = [...convertMarked({ tokens: env.tokens, range, message })]; - } - }); - - Prism.hooks.add("wrap", env => { - if (env.type === TOKEN_TYPE_ESLINT_MARKED) { - const messageId = env.classes.find(c => messageMap.has(c)); - - if (messageId) { - env.attributes.title = messageMap.get(messageId); - } - - if ( - env.content === "" || - env.content === "\ufeff" - ) { - env.classes.push(CLASS_ESLINT_MARKED_ON_ZERO_WIDTH); - } else if ( - env.content === "\n" || - env.content === "\r" || - env.content === "\r\n" - ) { - env.classes.push(CLASS_ESLINT_MARKED_ON_LINE_FEED); - } - } - }); + /** + * A token type for marking the range reported by a rule. + * This is also used for the `class` attribute of ``. + */ + const TOKEN_TYPE_ESLINT_MARKED = "eslint-marked"; + + /** + * Use in the class attribute of `` when an error is displayed in the BOM code or empty string. + */ + const CLASS_ESLINT_MARKED_ON_ZERO_WIDTH = "eslint-marked-on-zero-width"; + + /** + * Use in the class attribute of `` when an error is displayed in the line-feed. + */ + const CLASS_ESLINT_MARKED_ON_LINE_FEED = "eslint-marked-on-line-feed"; + + /** + * A Map that holds message IDs and messages. + * @type {Map} + */ + const messageMap = new Map(); + + /** + * Gets the message ID from the given message. + * @param {string} message Message + * @returns {string} Message ID + */ + function getMessageIdFromMessage(message) { + let messageId; + + for (const [key, value] of messageMap.entries()) { + if (value === message) { + messageId = key; + break; + } + } + if (!messageId) { + messageId = `eslint-message-id-${messageMap.size + 1}`; + messageMap.set(messageId, message); + } + return messageId; + } + + const linter = new Linter({ configType: "flat" }); + + Prism.hooks.add("after-tokenize", env => { + messageMap.clear(); + + if (contentMustBeMarked !== env.code) { + // Ignore unmarked content. + return; + } + contentMustBeMarked = void 0; + const config = contentLanguageOptions + ? { languageOptions: contentLanguageOptions } + : {}; + + const code = env.code; + + /** Copied from SourceCode constructor */ + const lineStartIndices = [0]; + const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + while ((match = lineEndingPattern.exec(code))) { + lineStartIndices.push(match.index + match[0].length); + } + + /** + * Converts a (line, column) pair into a range index. + * @param {Object} loc A line/column location + * @param {number} loc.line The line number of the location (1-indexed) + * @param {number} loc.column The column number of the location (1-indexed) + * @returns {number} The range index of the location in the file. + * Copied from SourceCode#getIndexFromLoc + */ + function getIndexFromLoc(loc) { + const lineStartIndex = lineStartIndices[loc.line - 1]; + const positionIndex = lineStartIndex + loc.column - 1; + + return positionIndex; + } + + /* + * Run lint to extract the error range. + */ + const lintMessages = linter.verify( + // Remove trailing newline and presentational `⏎` characters + docsExampleCodeToParsableCode(code), + config, + { filename: "code.js" }, + ); + + if (lintMessages.some(m => m.fatal)) { + // ESLint fatal error. + return; + } + const messages = lintMessages.map(message => { + const start = getIndexFromLoc({ + line: message.line, + column: message.column, + }); + + return { + message: message.message, + range: [ + start, + typeof message.endLine === "undefined" + ? start + : getIndexFromLoc({ + line: message.endLine, + column: message.endColumn, + }), + ], + }; + }); + + /** + * Get the content of the token. + * @param {string | Prism.Token} token The token + * @returns {string} The content of the token + */ + function getTokenContent(token) { + if (typeof token === "string") { + return token; + } + if (typeof token.content === "string") { + return token.content; + } + return [token.content].flat().map(getTokenContent).join(""); + } + + /** + * @typedef {Object} SplitTokenResult + * @property {string | Prism.Token | null} before The token before the marked range + * @property {Object} marked The marked token information + * @property {Prism.Token} marked.token The token with the marked range + * @property {boolean} marked.canBeMerged If true, it can be merged with previous and subsequent marked tokens. + * @property {string | Prism.Token | null} after The token after the marked range + */ + + /** + * Splits the given token into the `eslint-marked` token and the token before and after it with the specified range. + * @param {Object} params Parameters + * @param {string | Prism.Token} params.token Token to be split + * @param {[number, number]} params.range Range to be marked + * @param {string} params.message Report message + * @param {number} params.tokenStart Starting position of the token + * @returns {SplitTokenResult} Splitted tokens + */ + function splitToken({ token, range, message, tokenStart }) { + const content = getTokenContent(token); + const tokenEnd = tokenStart + content.length; + + if (range[0] <= tokenStart && tokenEnd <= range[1]) { + // The token is in range. + const marked = new Prism.Token( + TOKEN_TYPE_ESLINT_MARKED, + [token], + [getMessageIdFromMessage(message)], + ); + + return { + before: null, + marked: { token: marked, canBeMerged: true }, + after: null, + }; + } + + let buildToken; + + if (typeof token === "string") { + buildToken = newContent => newContent; + } else { + if (typeof token.content !== "string") { + if ( + token.content.every( + childContent => typeof childContent === "string", + ) + ) { + // It can be flatten. + buildToken = newContent => + new Prism.Token( + token.type, + newContent, + token.alias, + ); + } else { + token.content = [ + // eslint-disable-next-line no-use-before-define -- Safe + ...convertMarked({ + tokens: token.content, + range, + message, + tokenStart, + }), + ]; + return { + before: null, + marked: { token, canBeMerged: false }, + after: null, + }; + } + } else { + buildToken = newContent => + new Prism.Token(token.type, newContent, token.alias); + } + } + + const before = + tokenStart < range[0] + ? buildToken(content.slice(0, range[0] - tokenStart)) + : null; + const mark = content.slice( + before ? range[0] - tokenStart : 0, + range[1] - tokenStart, + ); + const marked = new Prism.Token( + TOKEN_TYPE_ESLINT_MARKED, + mark ? [buildToken(mark)] : mark, + [getMessageIdFromMessage(message)], + ); + const after = + range[1] - tokenStart < content.length + ? buildToken(content.slice(range[1] - tokenStart)) + : null; + + return { + before, + marked: { token: marked, canBeMerged: true }, + after, + }; + } + + /** + * Splits the given ESLint marked tokens with line feed code. + * The line feed code is not displayed because there is no character width, + * so by making it a single character token, the "wrap hook" applies CLASS_ESLINT_MARKED_ON_LINE_FEED + * to each character and makes it visible. + * @param {Prism.Token} token Token to be split + * @returns {IterableIterator} Splitted tokens + */ + function* splitMarkedTokenByLineFeed(token) { + for (const contentToken of [token.content].flat()) { + if (typeof contentToken !== "string") { + const content = getTokenContent(contentToken); + + if (/[\r\n]/u.test(content)) { + yield new Prism.Token( + token.type, + [...splitMarkedTokenByLineFeed(contentToken)], + token.alias, + ); + } else { + yield new Prism.Token( + token.type, + [contentToken], + token.alias, + ); + } + continue; + } + if (!/[\r\n]/u.test(contentToken)) { + yield new Prism.Token( + token.type, + [contentToken], + token.alias, + ); + continue; + } + const contents = []; + const reLineFeed = /\r\n?|\n/gu; + let startIndex = 0; + let matchLineFeed; + + while ((matchLineFeed = reLineFeed.exec(contentToken))) { + contents.push( + contentToken.slice(startIndex, matchLineFeed.index), + ); + contents.push(matchLineFeed[0]); + startIndex = reLineFeed.lastIndex; + } + contents.push(contentToken.slice(startIndex)); + yield* contents + .filter(Boolean) + .map( + str => new Prism.Token(token.type, [str], token.alias), + ); + } + } + + /** + * Splits the given tokens by line feed code. + * @param {Prism.Token[]} tokens Token to be Separate + * @returns {IterableIterator} Splitted tokens + */ + function* splitTokensByLineFeed(tokens) { + for (const token of tokens) { + if (typeof token === "string") { + yield token; + continue; + } + + const content = getTokenContent(token); + + if (!/[\r\n]/u.test(content)) { + yield token; + continue; + } + if (token.type === TOKEN_TYPE_ESLINT_MARKED) { + yield* splitMarkedTokenByLineFeed(token); + continue; + } + + if (typeof token.content !== "string") { + token.content = [ + ...splitTokensByLineFeed([token.content].flat()), + ]; + } + yield token; + } + } + + /** + * Generates a token stream with the `eslint-marked` class assigned to the error range. + * @param {Object} params Parameters + * @param {string | Prism.Token | (string | Prism.Token[])} params.tokens Tokens to be converted + * @param {[number, number]} params.range Range to be marked + * @param {string} params.message Report message + * @param {number} params.tokenStart Starting position of the tokens + * @returns {IterableIterator} converted tokens + */ + function* convertMarked({ tokens, range, message, tokenStart = 0 }) { + let start = tokenStart; + + const buffer = [tokens].flat(); + + let token; + + // Find the first token to mark + while ((token = buffer.shift())) { + const content = getTokenContent(token); + const end = start + content.length; + + if (!content || end <= range[0]) { + yield token; + start = end; + continue; + } + + break; + } + if (!token) { + return; + } + + // Mark the token. + const { before, marked, after } = splitToken({ + token, + range, + message, + tokenStart: start, + }); + + if (before) { + yield before; + } + if (after) { + yield* splitTokensByLineFeed([marked.token]); + yield after; + } else { + // Subsequent tokens may still be present in the range. + let nextTokenStartIndex = start + getTokenContent(token).length; + let prevMarked = marked; + let nextAfter; + + while (nextTokenStartIndex < range[1] && buffer.length) { + const nextToken = buffer.shift(); + const next = splitToken({ + token: nextToken, + range, + message, + tokenStart: nextTokenStartIndex, + }); + + if (prevMarked.canBeMerged && next.marked.canBeMerged) { + prevMarked.token.content.push( + ...next.marked.token.content, + ); + } else { + yield* splitTokensByLineFeed([prevMarked.token]); + prevMarked = next.marked; + } + if (next.after) { + nextAfter = next.after; + } + nextTokenStartIndex += getTokenContent(nextToken).length; + } + + yield* splitTokensByLineFeed([prevMarked.token]); + if (nextAfter) { + yield nextAfter; + } + } + + yield* buffer; + } + + for (const { range, message } of messages) { + env.tokens = [ + ...convertMarked({ tokens: env.tokens, range, message }), + ]; + } + }); + + Prism.hooks.add("wrap", env => { + if (env.type === TOKEN_TYPE_ESLINT_MARKED) { + const messageId = env.classes.find(c => messageMap.has(c)); + + if (messageId) { + env.attributes.title = messageMap.get(messageId); + } + + if (env.content === "" || env.content === "\ufeff") { + env.classes.push(CLASS_ESLINT_MARKED_ON_ZERO_WIDTH); + } else if ( + env.content === "\n" || + env.content === "\r" || + env.content === "\r\n" + ) { + env.classes.push(CLASS_ESLINT_MARKED_ON_LINE_FEED); + } + } + }); } - module.exports = { - installPrismESLintMarkerHook: isAvailable ? installPrismESLintMarkerHook : () => { /* noop */ }, - addContentMustBeMarked: isAvailable ? addContentMustBeMarked : () => { /* noop */ } + installPrismESLintMarkerHook: isAvailable + ? installPrismESLintMarkerHook + : () => { + /* noop */ + }, + addContentMustBeMarked: isAvailable + ? addContentMustBeMarked + : () => { + /* noop */ + }, }; diff --git a/docs/tools/validate-links.js b/docs/tools/validate-links.js index 1ca0dd271861..eb31a106f349 100644 --- a/docs/tools/validate-links.js +++ b/docs/tools/validate-links.js @@ -10,17 +10,17 @@ const tapRenderInstance = new TapRender(); tapRenderInstance.pipe(spot()).pipe(process.stdout); const skipPatterns = [ - "https://", - "fragment-redirect", - "migrating-to", - "/blog", - "/play", - "/team", - "/donate", - "/docs/latest", - "/docs/next", - "/docs/v8.x", - 'src="null"' + "https://", + "fragment-redirect", + "migrating-to", + "/blog", + "/play", + "/team", + "/donate", + "/docs/latest", + "/docs/next", + "/docs/v8.x", + 'src="null"', ]; /** @@ -30,30 +30,31 @@ const skipPatterns = [ * @returns {boolean} `true` if the report contains any of `skipPatterns`. */ function skipFilter(report) { - return Object.values(report).some(value => - skipPatterns.some(pattern => String(value).includes(pattern))); + return Object.values(report).some(value => + skipPatterns.some(pattern => String(value).includes(pattern)), + ); } (async () => { - try { - await hyperlink( - { - inputUrls: ["../_site/index.html"], - root: path.resolve(__dirname, "../_site"), - canonicalRoot: "https://eslint.org/docs/head/", - recursive: true, - internalOnly: true, - pretty: true, - concurrency: 25, - skipFilter - }, - tapRenderInstance - ); - } catch (err) { - console.log(err.stack); - process.exit(1); - } - const results = tapRenderInstance.close(); + try { + await hyperlink( + { + inputUrls: ["../_site/index.html"], + root: path.resolve(__dirname, "../_site"), + canonicalRoot: "https://eslint.org/docs/head/", + recursive: true, + internalOnly: true, + pretty: true, + concurrency: 25, + skipFilter, + }, + tapRenderInstance, + ); + } catch (err) { + console.log(err.stack); + process.exit(1); + } + const results = tapRenderInstance.close(); - process.exit(results.fail ? 1 : 0); + process.exit(results.fail ? 1 : 0); })(); diff --git a/eslint.config.js b/eslint.config.js index 5a3dc71df5f8..f34aa2bf29e5 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,7 +15,6 @@ const eslintPluginRulesRecommendedConfig = require("eslint-plugin-eslint-plugin/ const eslintPluginTestsRecommendedConfig = require("eslint-plugin-eslint-plugin/configs/tests-recommended"); const globals = require("globals"); const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); -const eslintConfigESLintFormatting = require("eslint-config-eslint/formatting"); const eslintPluginYml = require("eslint-plugin-yml"); const json = require("@eslint/json").default; const expectType = require("eslint-plugin-expect-type"); @@ -27,16 +26,16 @@ const { defineConfig, globalIgnores } = require("./lib/config-api.js"); //----------------------------------------------------------------------------- const INTERNAL_PATHS = { - 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/**/*" + 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/**/*", }; // same paths but with `.js` at the end const INTERNAL_FILES = Object.fromEntries( - Object.entries(INTERNAL_PATHS).map(([key, value]) => [key, `${value}.js`]) + Object.entries(INTERNAL_PATHS).map(([key, value]) => [key, `${value}.js`]), ); const ALL_JS_FILES = "**/*.js"; @@ -48,7 +47,7 @@ const ALL_YAML_FILES = "**/*.y?(a)ml"; * @returns {string} The resolved path or glob pattern. */ function resolveAbsolutePath(pathOrPattern) { - return path.resolve(__dirname, pathOrPattern); + return path.resolve(__dirname, pathOrPattern); } /** @@ -57,267 +56,308 @@ function resolveAbsolutePath(pathOrPattern) { * @returns {Object[]} The array of `no-restricted-require` entries. */ function createInternalFilesPatterns(pattern = null) { - return Object.values(INTERNAL_PATHS) - .filter(p => p !== pattern) - .map(p => ({ - name: [ + return Object.values(INTERNAL_PATHS) + .filter(p => p !== pattern) + .map(p => ({ + name: [ + // Disallow all children modules. + resolveAbsolutePath(p), - // Disallow all children modules. - resolveAbsolutePath(p), - - // Allow the main `index.js` module. - `!${resolveAbsolutePath(p.replace(/\*\*\/\*$/u, "index.js"))}` - ] - })); + // Allow the main `index.js` module. + `!${resolveAbsolutePath(p.replace(/\*\*\/\*$/u, "index.js"))}`, + ], + })); } /** * @type {import("./lib/types/index.js").Linter.Config[]} */ module.exports = defineConfig([ - { - name: "eslint/cjs", - files: [ALL_JS_FILES], - extends: [eslintConfigESLintCJS] - }, - { - name: "eslint/formatting", - files: [ALL_JS_FILES], - extends: [eslintConfigESLintFormatting] - }, - globalIgnores([ - "build/**", - "coverage/**", - "docs/!(src|tools)/", - "docs/src/!(_data)", - "jsdoc/**", - "lib/types/**/*.ts", - "templates/**", - "tests/bench/**", - "tests/fixtures/**", - "tests/performance/**", - "tmp/**", - "**/test.js", - ".vscode" - ], "eslint/global-ignores"), - { - name: "eslint/internal-rules", - files: [ALL_JS_FILES], - plugins: { - "internal-rules": internalPlugin - }, - languageOptions: { - ecmaVersion: "latest" - }, - rules: { - "internal-rules/multiline-comment-style": "error" - } - }, - { - name: "eslint/tools", - files: ["tools/*.js", "docs/tools/*.js"], - rules: { - "no-console": "off", - "n/no-process-exit": "off" - } - }, - { - name: "eslint/rules", - files: ["lib/rules/*.js", "tools/internal-rules/*.js"], - ignores: ["**/index.js"], - extends: [eslintPluginRulesRecommendedConfig], - 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" - } - }, - { - name: "eslint/core-rules", - files: ["lib/rules/*.js"], - ignores: ["**/index.js"], - rules: { - "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }] - } - }, - { - name: "eslint/rules-tests", - files: ["tests/lib/rules/*.js", "tests/tools/internal-rules/*.js"], - extends: [eslintPluginTestsRecommendedConfig], - rules: { - "eslint-plugin/test-case-property-ordering": [ - "error", - [ - "name", - "filename", - "code", - "output", - "options", - "languageOptions", - "errors" - ] - ], - "eslint-plugin/test-case-shorthand-strings": "error" - } - }, - { - name: "eslint/tests", - files: ["tests/**/*.js"], - languageOptions: { - globals: { - ...globals.mocha - } - }, - 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." - }] - } - }, - - // JSON files - { - name: "eslint/json", - files: ["**/*.json", ".c8rc"], - ignores: ["**/package-lock.json"], - plugins: { json }, - language: "json/json", - extends: ["json/recommended"] - }, + { + name: "eslint/cjs", + files: [ALL_JS_FILES], + extends: [eslintConfigESLintCJS], + }, + globalIgnores( + [ + "build/**", + "coverage/**", + "docs/!(src|tools)/", + "docs/src/!(_data)", + "jsdoc/**", + "lib/types/**/*.ts", + "templates/**", + "tests/bench/**", + "tests/fixtures/**", + "tests/performance/**", + "tmp/**", + "**/test.js", + ".vscode", + ], + "eslint/global-ignores", + ), + { + name: "eslint/internal-rules", + files: [ALL_JS_FILES], + plugins: { + "internal-rules": internalPlugin, + }, + languageOptions: { + ecmaVersion: "latest", + }, + rules: { + "internal-rules/multiline-comment-style": "error", + }, + }, + { + name: "eslint/tools", + files: ["tools/*.js", "docs/tools/*.js"], + rules: { + "no-console": "off", + "n/no-process-exit": "off", + }, + }, + { + name: "eslint/rules", + files: ["lib/rules/*.js", "tools/internal-rules/*.js"], + ignores: ["**/index.js"], + extends: [eslintPluginRulesRecommendedConfig], + 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", + }, + }, + { + name: "eslint/core-rules", + files: ["lib/rules/*.js"], + ignores: ["**/index.js"], + rules: { + "eslint-plugin/require-meta-docs-url": [ + "error", + { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }, + ], + }, + }, + { + name: "eslint/rules-tests", + files: ["tests/lib/rules/*.js", "tests/tools/internal-rules/*.js"], + extends: [eslintPluginTestsRecommendedConfig], + rules: { + "eslint-plugin/test-case-property-ordering": [ + "error", + [ + "name", + "filename", + "code", + "output", + "options", + "languageOptions", + "errors", + ], + ], + "eslint-plugin/test-case-shorthand-strings": "error", + "no-useless-concat": "off", + }, + }, + { + name: "eslint/tests", + files: ["tests/**/*.js"], + languageOptions: { + globals: { + ...globals.mocha, + }, + }, + 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.", + }, + ], + }, + }, - // JSONC files - { - name: "eslint/jsonc", - files: ["knip.jsonc"], - plugins: { json }, - language: "json/jsonc", - extends: ["json/recommended"] - }, + // JSON files + { + name: "eslint/json", + files: ["**/*.json", ".c8rc"], + ignores: ["**/package-lock.json"], + plugins: { json }, + language: "json/json", + extends: ["json/recommended"], + }, - // JSON5 files - { - name: "eslint/json5", - files: ["**/*.json5"], - plugins: { json }, - language: "json/json5", - extends: ["json/recommended"] - }, + // JSONC files + { + name: "eslint/jsonc", + files: ["knip.jsonc"], + plugins: { json }, + language: "json/jsonc", + languageOptions: { allowTrailingCommas: true }, + extends: ["json/recommended"], + }, - // Restrict relative path imports - { - name: "eslint/lib", - files: ["lib/*.js"], - ignores: ["lib/unsupported-api.js", "lib/universal.js"], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns() - ]] - } - }, - { - name: "eslint/cli-engine", - files: [INTERNAL_FILES.CLI_ENGINE_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_PATHS.CLI_ENGINE_PATTERN) - ]] - } - }, - { - name: "eslint/linter", - files: [INTERNAL_FILES.LINTER_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_PATHS.LINTER_PATTERN), - "fs", - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/rule-tester/index.js") - ]] - } - }, - { - name: "eslint/rules", - files: [INTERNAL_FILES.RULES_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_PATHS.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") - ]] - } - }, - { - name: "eslint/shared", - files: ["lib/shared/**/*.js"], - 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") - ]] - } - }, - { - name: "eslint/source-code", - files: [INTERNAL_FILES.SOURCE_CODE_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_PATHS.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") - ]] - } - }, - { - name: "eslint/rule-tester", - files: [INTERNAL_FILES.RULE_TESTER_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - resolveAbsolutePath("lib/cli-engine/index.js") - ]] - } - }, - ...eslintPluginYml.configs["flat/recommended"].map(config => ({ - ...config, - files: [ALL_YAML_FILES] - })), - { - name: "eslint/ts-rules", - files: ["tests/lib/types/*.ts", "packages/**/*.{ts,mts,cts,tsx}"], - languageOptions: { - parser: tsParser, - parserOptions: { - project: ["tests/lib/types/tsconfig.json", "packages/js/tests/types/tsconfig.json", "packages/eslint-config-eslint/tsconfig.json"] - } - }, - plugins: { - "expect-type": expectType - }, - rules: { - "expect-type/expect": "error" - } - }, - { - name: "eslint/bin", - files: ["bin/eslint.js"], - rules: { + // JSON5 files + { + name: "eslint/json5", + files: ["**/*.json5"], + plugins: { json }, + language: "json/json5", + extends: ["json/recommended"], + }, - /* - * it was introduced in Node.js v22.8.0 - * refs: https://nodejs.org/en/blog/release/v22.8.0#new-js-api-for-compile-cache - */ - "n/no-unsupported-features/node-builtins": [2, { ignores: ["module.enableCompileCache"] }] - } - } + // Restrict relative path imports + { + name: "eslint/lib", + files: ["lib/*.js"], + ignores: ["lib/unsupported-api.js", "lib/universal.js"], + rules: { + "n/no-restricted-require": [ + "error", + [...createInternalFilesPatterns()], + ], + }, + }, + { + name: "eslint/cli-engine", + files: [INTERNAL_FILES.CLI_ENGINE_PATTERN], + rules: { + "n/no-restricted-require": [ + "error", + [ + ...createInternalFilesPatterns( + INTERNAL_PATHS.CLI_ENGINE_PATTERN, + ), + ], + ], + }, + }, + { + name: "eslint/linter", + files: [INTERNAL_FILES.LINTER_PATTERN], + rules: { + "n/no-restricted-require": [ + "error", + [ + ...createInternalFilesPatterns( + INTERNAL_PATHS.LINTER_PATTERN, + ), + "fs", + resolveAbsolutePath("lib/cli-engine/index.js"), + resolveAbsolutePath("lib/rule-tester/index.js"), + ], + ], + }, + }, + { + name: "eslint/rules", + files: [INTERNAL_FILES.RULES_PATTERN], + rules: { + "n/no-restricted-require": [ + "error", + [ + ...createInternalFilesPatterns( + INTERNAL_PATHS.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"), + ], + ], + }, + }, + { + name: "eslint/shared", + files: ["lib/shared/**/*.js"], + 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"), + ], + ], + }, + }, + { + name: "eslint/source-code", + files: [INTERNAL_FILES.SOURCE_CODE_PATTERN], + rules: { + "n/no-restricted-require": [ + "error", + [ + ...createInternalFilesPatterns( + INTERNAL_PATHS.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"), + ], + ], + }, + }, + { + name: "eslint/rule-tester", + files: [INTERNAL_FILES.RULE_TESTER_PATTERN], + rules: { + "n/no-restricted-require": [ + "error", + [resolveAbsolutePath("lib/cli-engine/index.js")], + ], + }, + }, + ...eslintPluginYml.configs["flat/recommended"].map(config => ({ + ...config, + files: [ALL_YAML_FILES], + })), + { + name: "eslint/ts-rules", + files: ["tests/lib/types/*.ts", "packages/**/*.{ts,mts,cts,tsx}"], + languageOptions: { + parser: tsParser, + parserOptions: { + project: [ + "tests/lib/types/tsconfig.json", + "packages/js/tests/types/tsconfig.json", + "packages/eslint-config-eslint/tsconfig.json", + ], + }, + }, + plugins: { + "expect-type": expectType, + }, + rules: { + "expect-type/expect": "error", + }, + }, + { + name: "eslint/bin", + files: ["bin/eslint.js"], + rules: { + /* + * it was introduced in Node.js v22.8.0 + * refs: https://nodejs.org/en/blog/release/v22.8.0#new-js-api-for-compile-cache + */ + "n/no-unsupported-features/node-builtins": [ + 2, + { ignores: ["module.enableCompileCache"] }, + ], + }, + }, ]); diff --git a/knip.jsonc b/knip.jsonc index 9ce573a4ba29..8fca56932e94 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,43 +1,41 @@ { - "workspaces": { - ".": { - // These entries are complementary to the ones found in package.json - "entry": ["lib/rules/index.js", "tools/internal-rules/*.js"], - "project": ["{conf,lib,tools}/**/*.js"], - "mocha": { - "entry": [ - "tests/{bin,conf,lib,tools}/**/*.js", // see Makefile.js - "tests/_utils/test-lazy-loading-rules.js" - ], - "project": ["tests/**/*.js"] - }, - "ignore": [ - // If Knip would consider exports as named, their usage is too dynamic: globals[`es${ecmaVersion}`] - // An alternative is to add `__esModule: true` to the export and we can remove it here from the ignores: - "conf/globals.js", - // These contain unresolved imports and other oddities: - "tests/bench/large.js", - "tests/lib/rule-tester/rule-tester.js", - "tests/performance/jshint.js", - // Many are required using dynamic paths such as `fs.readFileSync(path.join())`: - "tests/fixtures/**" - ], - "ignoreDependencies": [ - "c8", - "@wdio/cli", - "rollup-plugin-node-polyfills", - // Optional peer dependency used for loading TypeScript configuration files - "jiti" - ] - }, - "docs": { - "ignore": ["src/assets/js/search.js", "_examples/**"] - }, - // Workspaces with default configs: - "packages/*": { - "ignore": ["tests/types/**"], - "ignoreDependencies": ["eslint"] - }, - "tools/internal-rules": {} - } + "workspaces": { + ".": { + // These entries are complementary to the ones found in package.json + "entry": ["lib/rules/index.js", "tools/internal-rules/*.js"], + "project": ["{conf,lib,tools}/**/*.js"], + "mocha": { + "entry": [ + "tests/{bin,conf,lib,tools}/**/*.js", // see Makefile.js + "tests/_utils/test-lazy-loading-rules.js", + ], + "project": ["tests/**/*.js"], + }, + "ignore": [ + // If Knip would consider exports as named, their usage is too dynamic: globals[`es${ecmaVersion}`] + // An alternative is to add `__esModule: true` to the export and we can remove it here from the ignores: + "conf/globals.js", + // These contain unresolved imports and other oddities: + "tests/bench/large.js", + "tests/lib/rule-tester/rule-tester.js", + "tests/performance/jshint.js", + // Many are required using dynamic paths such as `fs.readFileSync(path.join())`: + "tests/fixtures/**", + ], + "ignoreDependencies": [ + "c8", + // Optional peer dependency used for loading TypeScript configuration files + "jiti", + ], + }, + "docs": { + "ignore": ["src/assets/js/search.js", "_examples/**"], + }, + // Workspaces with default configs: + "packages/*": { + "ignore": ["tests/types/**"], + "ignoreDependencies": ["eslint"], + }, + "tools/internal-rules": {}, + }, } diff --git a/lib/api.js b/lib/api.js index a134ecdaae88..ce6102dad522 100644 --- a/lib/api.js +++ b/lib/api.js @@ -26,15 +26,15 @@ const { SourceCode } = require("./languages/js/source-code"); * @returns {Promise} The ESLint constructor */ async function loadESLint({ useFlatConfig } = {}) { + /* + * Note: The v8.x version of this function also accepted a `cwd` option, but + * it is not used in this implementation so we silently ignore it. + */ - /* - * Note: The v8.x version of this function also accepted a `cwd` option, but - * it is not used in this implementation so we silently ignore it. - */ + const shouldESLintUseFlatConfig = + useFlatConfig ?? (await shouldUseFlatConfig()); - const shouldESLintUseFlatConfig = useFlatConfig ?? (await shouldUseFlatConfig()); - - return shouldESLintUseFlatConfig ? ESLint : LegacyESLint; + return shouldESLintUseFlatConfig ? ESLint : LegacyESLint; } //----------------------------------------------------------------------------- @@ -42,9 +42,9 @@ async function loadESLint({ useFlatConfig } = {}) { //----------------------------------------------------------------------------- module.exports = { - Linter, - loadESLint, - ESLint, - RuleTester, - SourceCode + Linter, + loadESLint, + ESLint, + RuleTester, + SourceCode, }; diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js index 43fcc73d029a..e0d70fda4931 100644 --- a/lib/cli-engine/cli-engine.js +++ b/lib/cli-engine/cli-engine.js @@ -20,16 +20,15 @@ const path = require("node:path"); const defaultOptions = require("../../conf/default-cli-options"); const pkg = require("../../package.json"); - const { - Legacy: { - ConfigOps, - naming, - CascadingConfigArrayFactory, - IgnorePattern, - getUsedExtractedConfigs, - ModuleResolver - } + Legacy: { + ConfigOps, + naming, + CascadingConfigArrayFactory, + IgnorePattern, + getUsedExtractedConfigs, + ModuleResolver, + }, } = require("@eslint/eslintrc"); const { FileEnumerator } = require("./file-enumerator"); @@ -42,15 +41,15 @@ const LintResultCache = require("./lint-result-cache"); const debug = require("debug")("eslint:cli-engine"); const removedFormatters = new Set([ - "checkstyle", - "codeframe", - "compact", - "jslint-xml", - "junit", - "table", - "tap", - "unix", - "visualstudio" + "checkstyle", + "codeframe", + "compact", + "jslint-xml", + "junit", + "table", + "tap", + "unix", + "visualstudio", ]); const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]); @@ -155,11 +154,11 @@ const internalSlotsMap = new WeakMap(); * @throws {Error} If an invalid fix type is found. */ function validateFixTypes(fixTypes) { - for (const fixType of fixTypes) { - if (!validFixTypes.has(fixType)) { - throw new Error(`Invalid fix type "${fixType}" found.`); - } - } + for (const fixType of fixTypes) { + if (!validFixTypes.has(fixType)) { + throw new Error(`Invalid fix type "${fixType}" found.`); + } + } } /** @@ -169,33 +168,33 @@ function validateFixTypes(fixTypes) { * @private */ function calculateStatsPerFile(messages) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < messages.length; i++) { - const message = messages[i]; - - if (message.fatal || message.severity === 2) { - stat.errorCount++; - if (message.fatal) { - stat.fatalErrorCount++; - } - if (message.fix) { - stat.fixableErrorCount++; - } - } else { - stat.warningCount++; - if (message.fix) { - stat.fixableWarningCount++; - } - } - } - return stat; + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + if (message.fatal || message.severity === 2) { + stat.errorCount++; + if (message.fatal) { + stat.fatalErrorCount++; + } + if (message.fix) { + stat.fixableErrorCount++; + } + } else { + stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; + } + } + } + return stat; } /** @@ -205,25 +204,25 @@ function calculateStatsPerFile(messages) { * @private */ function calculateStatsPerRun(results) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < results.length; i++) { - const result = results[i]; - - stat.errorCount += result.errorCount; - stat.fatalErrorCount += result.fatalErrorCount; - stat.warningCount += result.warningCount; - stat.fixableErrorCount += result.fixableErrorCount; - stat.fixableWarningCount += result.fixableWarningCount; - } - - return stat; + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }; + + for (let i = 0; i < results.length; i++) { + const result = results[i]; + + stat.errorCount += result.errorCount; + stat.fatalErrorCount += result.fatalErrorCount; + stat.warningCount += result.warningCount; + stat.fixableErrorCount += result.fixableErrorCount; + stat.fixableWarningCount += result.fixableWarningCount; + } + + return stat; } /** @@ -242,65 +241,62 @@ function calculateStatsPerRun(results) { * @private */ function verifyText({ - text, - cwd, - filePath: providedFilePath, - config, - fix, - allowInlineConfig, - reportUnusedDisableDirectives, - fileEnumerator, - linter + text, + cwd, + filePath: providedFilePath, + config, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter, }) { - const filePath = providedFilePath || ""; - - debug(`Lint ${filePath}`); - - /* - * Verify. - * `config.extractConfig(filePath)` requires an absolute path, but `linter` - * doesn't know CWD, so it gives `linter` an absolute path always. - */ - const filePathToVerify = filePath === "" ? path.join(cwd, filePath) : filePath; - const { fixed, messages, output } = linter.verifyAndFix( - text, - config, - { - allowInlineConfig, - filename: filePathToVerify, - fix, - reportUnusedDisableDirectives, - - /** - * Check if the linter should adopt a given code block or not. - * @param {string} blockFilename The virtual filename of a code block. - * @returns {boolean} `true` if the linter should adopt the code block. - */ - filterCodeBlock(blockFilename) { - return fileEnumerator.isTargetPath(blockFilename); - } - } - ); - - // Tweak and return. - const result = { - filePath, - messages, - suppressedMessages: linter.getSuppressedMessages(), - ...calculateStatsPerFile(messages) - }; - - if (fixed) { - result.output = output; - } - if ( - result.errorCount + result.warningCount > 0 && - typeof result.output === "undefined" - ) { - result.source = text; - } - - return result; + const filePath = providedFilePath || ""; + + debug(`Lint ${filePath}`); + + /* + * Verify. + * `config.extractConfig(filePath)` requires an absolute path, but `linter` + * doesn't know CWD, so it gives `linter` an absolute path always. + */ + const filePathToVerify = + filePath === "" ? path.join(cwd, filePath) : filePath; + const { fixed, messages, output } = linter.verifyAndFix(text, config, { + allowInlineConfig, + filename: filePathToVerify, + fix, + reportUnusedDisableDirectives, + + /** + * Check if the linter should adopt a given code block or not. + * @param {string} blockFilename The virtual filename of a code block. + * @returns {boolean} `true` if the linter should adopt the code block. + */ + filterCodeBlock(blockFilename) { + return fileEnumerator.isTargetPath(blockFilename); + }, + }); + + // Tweak and return. + const result = { + filePath, + messages, + suppressedMessages: linter.getSuppressedMessages(), + ...calculateStatsPerFile(messages), + }; + + if (fixed) { + result.output = output; + } + if ( + result.errorCount + result.warningCount > 0 && + typeof result.output === "undefined" + ) { + result.source = text; + } + + return result; } /** @@ -311,37 +307,42 @@ function verifyText({ * @private */ function createIgnoreResult(filePath, baseDir) { - let message; - const isHidden = filePath.split(path.sep) - .find(segment => /^\./u.test(segment)); - const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); - - if (isHidden) { - message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; - } else if (isInNodeModules) { - message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; - } else { - message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."; - } - - return { - filePath: path.resolve(filePath), - messages: [ - { - ruleId: null, - fatal: false, - severity: 1, - message, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 0, - fatalErrorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; + let message; + const isHidden = filePath + .split(path.sep) + .find(segment => /^\./u.test(segment)); + const isInNodeModules = + baseDir && path.relative(baseDir, filePath).startsWith("node_modules"); + + if (isHidden) { + message = + "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + } else if (isInNodeModules) { + message = + "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + } else { + message = + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.'; + } + + return { + filePath: path.resolve(filePath), + messages: [ + { + ruleId: null, + fatal: false, + severity: 1, + message, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 0, + fatalErrorCount: 0, + warningCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + }; } /** @@ -351,14 +352,14 @@ function createIgnoreResult(filePath, baseDir) { * @returns {Rule|null} The rule or null. */ function getRule(ruleId, configArrays) { - for (const configArray of configArrays) { - const rule = configArray.pluginRules.get(ruleId); - - if (rule) { - return rule; - } - } - return builtInRules.get(ruleId) || null; + for (const configArray of configArrays) { + const rule = configArray.pluginRules.get(ruleId); + + if (rule) { + return rule; + } + } + return builtInRules.get(ruleId) || null; } /** @@ -369,13 +370,13 @@ function getRule(ruleId, configArrays) { * @returns {boolean} Whether the message should be fixed. */ function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) { - if (!message.ruleId) { - return fixTypes.has("directive"); - } + if (!message.ruleId) { + return fixTypes.has("directive"); + } - const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays); + const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays); - return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); + return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); } /** @@ -383,41 +384,40 @@ function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) { * @param {ConfigArray[]} usedConfigArrays The config arrays which were used. * @returns {IterableIterator} Used deprecated rules. */ -function *iterateRuleDeprecationWarnings(usedConfigArrays) { - const processedRuleIds = new Set(); - - // Flatten used configs. - /** @type {ExtractedConfig[]} */ - const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs); - - // Traverse rule configs. - for (const config of configs) { - for (const [ruleId, ruleConfig] of Object.entries(config.rules)) { - - // Skip if it was processed. - if (processedRuleIds.has(ruleId)) { - continue; - } - processedRuleIds.add(ruleId); - - // Skip if it's not used. - if (!ConfigOps.getRuleSeverity(ruleConfig)) { - continue; - } - const rule = getRule(ruleId, usedConfigArrays); - - // Skip if it's not deprecated. - if (!(rule && rule.meta && rule.meta.deprecated)) { - continue; - } - - // This rule was used and deprecated. - yield { - ruleId, - replacedBy: rule.meta.replacedBy || [] - }; - } - } +function* iterateRuleDeprecationWarnings(usedConfigArrays) { + const processedRuleIds = new Set(); + + // Flatten used configs. + /** @type {ExtractedConfig[]} */ + const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs); + + // Traverse rule configs. + for (const config of configs) { + for (const [ruleId, ruleConfig] of Object.entries(config.rules)) { + // Skip if it was processed. + if (processedRuleIds.has(ruleId)) { + continue; + } + processedRuleIds.add(ruleId); + + // Skip if it's not used. + if (!ConfigOps.getRuleSeverity(ruleConfig)) { + continue; + } + const rule = getRule(ruleId, usedConfigArrays); + + // Skip if it's not deprecated. + if (!(rule && rule.meta && rule.meta.deprecated)) { + continue; + } + + // This rule was used and deprecated. + yield { + ruleId, + replacedBy: rule.meta.replacedBy || [], + }; + } + } } /** @@ -427,10 +427,9 @@ function *iterateRuleDeprecationWarnings(usedConfigArrays) { * @private */ function isErrorMessage(message) { - return message.severity === 2; + return message.severity === 2; } - /** * return the cacheFile to be used by eslint, based on whether the provided parameter is * a directory or looks like a directory (ends in `path.sep`), in which case the file @@ -442,65 +441,62 @@ function isErrorMessage(message) { * @returns {string} the resolved path to the cache file */ function getCacheFile(cacheFile, cwd) { - - /* - * make sure the path separators are normalized for the environment/os - * keeping the trailing path separator if present - */ - const normalizedCacheFile = path.normalize(cacheFile); - - const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); - const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; - - /** - * return the name for the cache file in case the provided parameter is a directory - * @returns {string} the resolved path to the cacheFile - */ - function getCacheFileForDirectory() { - return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`); - } - - let fileStats; - - try { - fileStats = fs.lstatSync(resolvedCacheFile); - } catch { - fileStats = null; - } - - - /* - * in case the file exists we need to verify if the provided path - * is a directory or a file. If it is a directory we want to create a file - * inside that directory - */ - if (fileStats) { - - /* - * is a directory or is a file, but the original file the user provided - * looks like a directory but `path.resolve` removed the `last path.sep` - * so we need to still treat this like a directory - */ - if (fileStats.isDirectory() || looksLikeADirectory) { - return getCacheFileForDirectory(); - } - - // is file so just use that file - return resolvedCacheFile; - } - - /* - * here we known the file or directory doesn't exist, - * so we will try to infer if its a directory if it looks like a directory - * for the current operating system. - */ - - // if the last character passed is a path separator we assume is a directory - if (looksLikeADirectory) { - return getCacheFileForDirectory(); - } - - return resolvedCacheFile; + /* + * make sure the path separators are normalized for the environment/os + * keeping the trailing path separator if present + */ + const normalizedCacheFile = path.normalize(cacheFile); + + const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); + const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; + + /** + * return the name for the cache file in case the provided parameter is a directory + * @returns {string} the resolved path to the cacheFile + */ + function getCacheFileForDirectory() { + return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`); + } + + let fileStats; + + try { + fileStats = fs.lstatSync(resolvedCacheFile); + } catch { + fileStats = null; + } + + /* + * in case the file exists we need to verify if the provided path + * is a directory or a file. If it is a directory we want to create a file + * inside that directory + */ + if (fileStats) { + /* + * is a directory or is a file, but the original file the user provided + * looks like a directory but `path.resolve` removed the `last path.sep` + * so we need to still treat this like a directory + */ + if (fileStats.isDirectory() || looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + // is file so just use that file + return resolvedCacheFile; + } + + /* + * here we known the file or directory doesn't exist, + * so we will try to infer if its a directory if it looks like a directory + * for the current operating system. + */ + + // if the last character passed is a path separator we assume is a directory + if (looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + return resolvedCacheFile; } /** @@ -512,23 +508,21 @@ function getCacheFile(cacheFile, cwd) { * @returns {Record} The boolean map. */ function toBooleanMap(keys, defaultValue, displayName) { - if (keys && !Array.isArray(keys)) { - throw new Error(`${displayName} must be an array.`); - } - if (keys && keys.length > 0) { - return keys.reduce((map, def) => { - const [key, value] = def.split(":"); - - if (key !== "__proto__") { - map[key] = value === void 0 - ? defaultValue - : value === "true"; - } - - return map; - }, {}); - } - return void 0; + if (keys && !Array.isArray(keys)) { + throw new Error(`${displayName} must be an array.`); + } + if (keys && keys.length > 0) { + return keys.reduce((map, def) => { + const [key, value] = def.split(":"); + + if (key !== "__proto__") { + map[key] = value === void 0 ? defaultValue : value === "true"; + } + + return map; + }, {}); + } + return void 0; } /** @@ -537,36 +531,30 @@ function toBooleanMap(keys, defaultValue, displayName) { * @returns {ConfigData|null} The created config data. */ function createConfigDataFromOptions(options) { - const { - ignorePattern, - parser, - parserOptions, - plugins, - rules - } = options; - const env = toBooleanMap(options.envs, true, "envs"); - const globals = toBooleanMap(options.globals, false, "globals"); - - if ( - env === void 0 && - globals === void 0 && - (ignorePattern === void 0 || ignorePattern.length === 0) && - parser === void 0 && - parserOptions === void 0 && - plugins === void 0 && - rules === void 0 - ) { - return null; - } - return { - env, - globals, - ignorePatterns: ignorePattern, - parser, - parserOptions, - plugins, - rules - }; + const { ignorePattern, parser, parserOptions, plugins, rules } = options; + const env = toBooleanMap(options.envs, true, "envs"); + const globals = toBooleanMap(options.globals, false, "globals"); + + if ( + env === void 0 && + globals === void 0 && + (ignorePattern === void 0 || ignorePattern.length === 0) && + parser === void 0 && + parserOptions === void 0 && + plugins === void 0 && + rules === void 0 + ) { + return null; + } + return { + env, + globals, + ignorePatterns: ignorePattern, + parser, + parserOptions, + plugins, + rules, + }; } /** @@ -576,14 +564,14 @@ function createConfigDataFromOptions(options) { * @returns {boolean} `true` if a directory exists */ function directoryExists(resolvedPath) { - try { - return fs.statSync(resolvedPath).isDirectory(); - } catch (error) { - if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { - return false; - } - throw error; - } + try { + return fs.statSync(resolvedPath).isDirectory(); + } catch (error) { + if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) { + return false; + } + throw error; + } } //------------------------------------------------------------------------------ @@ -594,496 +582,528 @@ function directoryExists(resolvedPath) { * Core CLI. */ class CLIEngine { - - /** - * Creates a new instance of the core CLI engine. - * @param {CLIEngineOptions} providedOptions The options for this instance. - * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions. - * @param {Record|null} [additionalData.preloadedPlugins] Preloaded plugins. - */ - constructor(providedOptions, { preloadedPlugins } = {}) { - const options = Object.assign( - Object.create(null), - defaultOptions, - { cwd: process.cwd() }, - providedOptions - ); - - if (options.fix === void 0) { - options.fix = false; - } - - const additionalPluginPool = new Map(); - - if (preloadedPlugins) { - for (const [id, plugin] of Object.entries(preloadedPlugins)) { - additionalPluginPool.set(id, plugin); - } - } - - const cacheFilePath = getCacheFile( - options.cacheLocation || options.cacheFile, - options.cwd - ); - const configArrayFactory = new CascadingConfigArrayFactory({ - additionalPluginPool, - baseConfig: options.baseConfig || null, - cliConfig: createConfigDataFromOptions(options), - cwd: options.cwd, - ignorePath: options.ignorePath, - resolvePluginsRelativeTo: options.resolvePluginsRelativeTo, - rulePaths: options.rulePaths, - specificConfigPath: options.configFile, - useEslintrc: options.useEslintrc, - builtInRules, - loadRules, - getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, - getEslintAllConfig: () => require("@eslint/js").configs.all - }); - const fileEnumerator = new FileEnumerator({ - configArrayFactory, - cwd: options.cwd, - extensions: options.extensions, - globInputPaths: options.globInputPaths, - errorOnUnmatchedPattern: options.errorOnUnmatchedPattern, - ignore: options.ignore - }); - const lintResultCache = - options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null; - const linter = new Linter({ cwd: options.cwd, configType: "eslintrc" }); - - /** @type {ConfigArray[]} */ - const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()]; - - // Store private data. - internalSlotsMap.set(this, { - additionalPluginPool, - cacheFilePath, - configArrayFactory, - defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd), - fileEnumerator, - lastConfigArrays, - lintResultCache, - linter, - options - }); - - // setup special filter for fixes - if (options.fix && options.fixTypes && options.fixTypes.length > 0) { - debug(`Using fix types ${options.fixTypes}`); - - // throw an error if any invalid fix types are found - validateFixTypes(options.fixTypes); - - // convert to Set for faster lookup - const fixTypes = new Set(options.fixTypes); - - // save original value of options.fix in case it's a function - const originalFix = (typeof options.fix === "function") - ? options.fix : () => true; - - options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message); - } - } - - getRules() { - const { lastConfigArrays } = internalSlotsMap.get(this); - - return new Map(function *() { - yield* builtInRules; - - for (const configArray of lastConfigArrays) { - yield* configArray.pluginRules; - } - }()); - } - - /** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ - static getErrorResults(results) { - const filtered = []; - - results.forEach(result => { - const filteredMessages = result.messages.filter(isErrorMessage); - const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); - - if (filteredMessages.length > 0) { - filtered.push({ - ...result, - messages: filteredMessages, - suppressedMessages: filteredSuppressedMessages, - errorCount: filteredMessages.length, - warningCount: 0, - fixableErrorCount: result.fixableErrorCount, - fixableWarningCount: 0 - }); - } - }); - - return filtered; - } - - /** - * Outputs fixes from the given results to files. - * @param {LintReport} report The report object created by CLIEngine. - * @returns {void} - */ - static outputFixes(report) { - report.results.filter(result => Object.hasOwn(result, "output")).forEach(result => { - fs.writeFileSync(result.filePath, result.output); - }); - } - - /** - * Resolves the patterns passed into executeOnFiles() into glob-based patterns - * for easier handling. - * @param {string[]} patterns The file patterns passed on the command line. - * @returns {string[]} The equivalent glob patterns. - */ - resolveFileGlobPatterns(patterns) { - const { options } = internalSlotsMap.get(this); - - if (options.globInputPaths === false) { - return patterns.filter(Boolean); - } - - const extensions = (options.extensions || [".js"]).map(ext => ext.replace(/^\./u, "")); - const dirSuffix = `/**/*.{${extensions.join(",")}}`; - - return patterns.filter(Boolean).map(pathname => { - const resolvedPath = path.resolve(options.cwd, pathname); - const newPath = directoryExists(resolvedPath) - ? pathname.replace(/[/\\]$/u, "") + dirSuffix - : pathname; - - return path.normalize(newPath).replace(/\\/gu, "/"); - }); - } - - /** - * Executes the current configuration on an array of file and directory names. - * @param {string[]} patterns An array of file and directory names. - * @throws {Error} As may be thrown by `fs.unlinkSync`. - * @returns {LintReport} The results for all files that were linted. - */ - executeOnFiles(patterns) { - const { - cacheFilePath, - fileEnumerator, - lastConfigArrays, - lintResultCache, - linter, - options: { - allowInlineConfig, - cache, - cwd, - fix, - reportUnusedDisableDirectives - } - } = internalSlotsMap.get(this); - const results = []; - const startTime = Date.now(); - - // Clear the last used config arrays. - lastConfigArrays.length = 0; - - // Delete cache file; should this do here? - if (!cache) { - try { - fs.unlinkSync(cacheFilePath); - } catch (error) { - const errorCode = error && error.code; - - // Ignore errors when no such file exists or file system is read only (and cache file does not exist) - if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath))) { - throw error; - } - } - } - - // Iterate source code files. - for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) { - if (ignored) { - results.push(createIgnoreResult(filePath, cwd)); - continue; - } - - /* - * Store used configs for: - * - this method uses to collect used deprecated rules. - * - `getRules()` method uses to collect all loaded rules. - * - `--fix-type` option uses to get the loaded rule's meta data. - */ - if (!lastConfigArrays.includes(config)) { - lastConfigArrays.push(config); - } - - // Skip if there is cached result. - if (lintResultCache) { - const cachedResult = - lintResultCache.getCachedLintResults(filePath, config); - - if (cachedResult) { - const hadMessages = - cachedResult.messages && - cachedResult.messages.length > 0; - - if (hadMessages && fix) { - debug(`Reprocessing cached file to allow autofix: ${filePath}`); - } else { - debug(`Skipping file since it hasn't changed: ${filePath}`); - results.push(cachedResult); - continue; - } - } - } - - // Do lint. - const result = verifyText({ - text: fs.readFileSync(filePath, "utf8"), - filePath, - config, - cwd, - fix, - allowInlineConfig, - reportUnusedDisableDirectives, - fileEnumerator, - linter - }); - - results.push(result); - - /* - * Store the lint result in the LintResultCache. - * NOTE: The LintResultCache will remove the file source and any - * other properties that are difficult to serialize, and will - * hydrate those properties back in on future lint runs. - */ - if (lintResultCache) { - lintResultCache.setCachedLintResults(filePath, config, result); - } - } - - // Persist the cache to disk. - if (lintResultCache) { - lintResultCache.reconcile(); - } - - debug(`Linting complete in: ${Date.now() - startTime}ms`); - let usedDeprecatedRules; - - return { - results, - ...calculateStatsPerRun(results), - - // Initialize it lazily because CLI and `ESLint` API don't use it. - get usedDeprecatedRules() { - if (!usedDeprecatedRules) { - usedDeprecatedRules = Array.from( - iterateRuleDeprecationWarnings(lastConfigArrays) - ); - } - return usedDeprecatedRules; - } - }; - } - - /** - * Executes the current configuration on text. - * @param {string} text A string of JavaScript code to lint. - * @param {string} [filename] An optional string representing the texts filename. - * @param {boolean} [warnIgnored] Always warn when a file is ignored - * @returns {LintReport} The results for the linting. - */ - executeOnText(text, filename, warnIgnored) { - const { - configArrayFactory, - fileEnumerator, - lastConfigArrays, - linter, - options: { - allowInlineConfig, - cwd, - fix, - reportUnusedDisableDirectives - } - } = internalSlotsMap.get(this); - const results = []; - const startTime = Date.now(); - const resolvedFilename = filename && path.resolve(cwd, filename); - - - // Clear the last used config arrays. - lastConfigArrays.length = 0; - if (resolvedFilename && this.isPathIgnored(resolvedFilename)) { - if (warnIgnored) { - results.push(createIgnoreResult(resolvedFilename, cwd)); - } - } else { - const config = configArrayFactory.getConfigArrayForFile( - resolvedFilename || "__placeholder__.js" - ); - - /* - * Store used configs for: - * - this method uses to collect used deprecated rules. - * - `getRules()` method uses to collect all loaded rules. - * - `--fix-type` option uses to get the loaded rule's meta data. - */ - lastConfigArrays.push(config); - - // Do lint. - results.push(verifyText({ - text, - filePath: resolvedFilename, - config, - cwd, - fix, - allowInlineConfig, - reportUnusedDisableDirectives, - fileEnumerator, - linter - })); - } - - debug(`Linting complete in: ${Date.now() - startTime}ms`); - let usedDeprecatedRules; - - return { - results, - ...calculateStatsPerRun(results), - - // Initialize it lazily because CLI and `ESLint` API don't use it. - get usedDeprecatedRules() { - if (!usedDeprecatedRules) { - usedDeprecatedRules = Array.from( - iterateRuleDeprecationWarnings(lastConfigArrays) - ); - } - return usedDeprecatedRules; - } - }; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file to retrieve a config object for. - * @throws {Error} If filepath a directory path. - * @returns {ConfigData} A configuration object for the file. - */ - getConfigForFile(filePath) { - const { configArrayFactory, options } = internalSlotsMap.get(this); - const absolutePath = path.resolve(options.cwd, filePath); - - if (directoryExists(absolutePath)) { - throw Object.assign( - new Error("'filePath' should not be a directory path."), - { messageTemplate: "print-config-with-directory-path" } - ); - } - - return configArrayFactory - .getConfigArrayForFile(absolutePath) - .extractConfig(absolutePath) - .toCompatibleObjectAsConfigFileContent(); - } - - /** - * Checks if a given path is ignored by ESLint. - * @param {string} filePath The path of the file to check. - * @returns {boolean} Whether or not the given path is ignored. - */ - isPathIgnored(filePath) { - const { - configArrayFactory, - defaultIgnores, - options: { cwd, ignore } - } = internalSlotsMap.get(this); - const absolutePath = path.resolve(cwd, filePath); - - if (ignore) { - const config = configArrayFactory - .getConfigArrayForFile(absolutePath) - .extractConfig(absolutePath); - const ignores = config.ignores || defaultIgnores; - - return ignores(absolutePath); - } - - return defaultIgnores(absolutePath); - } - - /** - * Returns the formatter representing the given format or null if the `format` is not a string. - * @param {string} [format] The name of the format to load or the path to a - * custom formatter. - * @throws {any} As may be thrown by requiring of formatter - * @returns {(FormatterFunction|null)} The formatter function or null if the `format` is not a string. - */ - getFormatter(format) { - - // default is stylish - const resolvedFormatName = format || "stylish"; - - // only strings are valid formatters - if (typeof resolvedFormatName === "string") { - - // replace \ with / for Windows compatibility - const normalizedFormatName = resolvedFormatName.replace(/\\/gu, "/"); - - const slots = internalSlotsMap.get(this); - const cwd = slots ? slots.options.cwd : process.cwd(); - const namespace = naming.getNamespaceFromTerm(normalizedFormatName); - - let formatterPath; - - // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) - if (!namespace && normalizedFormatName.includes("/")) { - formatterPath = path.resolve(cwd, normalizedFormatName); - } else { - try { - const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); - - formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js")); - } catch { - formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName); - } - } - - try { - return require(formatterPath); - } catch (ex) { - if (removedFormatters.has(format)) { - ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``; - } else { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; - } - throw ex; - } - - } else { - return null; - } - } + /** + * Creates a new instance of the core CLI engine. + * @param {CLIEngineOptions} providedOptions The options for this instance. + * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions. + * @param {Record|null} [additionalData.preloadedPlugins] Preloaded plugins. + */ + constructor(providedOptions, { preloadedPlugins } = {}) { + const options = Object.assign( + Object.create(null), + defaultOptions, + { cwd: process.cwd() }, + providedOptions, + ); + + if (options.fix === void 0) { + options.fix = false; + } + + const additionalPluginPool = new Map(); + + if (preloadedPlugins) { + for (const [id, plugin] of Object.entries(preloadedPlugins)) { + additionalPluginPool.set(id, plugin); + } + } + + const cacheFilePath = getCacheFile( + options.cacheLocation || options.cacheFile, + options.cwd, + ); + const configArrayFactory = new CascadingConfigArrayFactory({ + additionalPluginPool, + baseConfig: options.baseConfig || null, + cliConfig: createConfigDataFromOptions(options), + cwd: options.cwd, + ignorePath: options.ignorePath, + resolvePluginsRelativeTo: options.resolvePluginsRelativeTo, + rulePaths: options.rulePaths, + specificConfigPath: options.configFile, + useEslintrc: options.useEslintrc, + builtInRules, + loadRules, + getEslintRecommendedConfig: () => + require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all, + }); + const fileEnumerator = new FileEnumerator({ + configArrayFactory, + cwd: options.cwd, + extensions: options.extensions, + globInputPaths: options.globInputPaths, + errorOnUnmatchedPattern: options.errorOnUnmatchedPattern, + ignore: options.ignore, + }); + const lintResultCache = options.cache + ? new LintResultCache(cacheFilePath, options.cacheStrategy) + : null; + const linter = new Linter({ cwd: options.cwd, configType: "eslintrc" }); + + /** @type {ConfigArray[]} */ + const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()]; + + // Store private data. + internalSlotsMap.set(this, { + additionalPluginPool, + cacheFilePath, + configArrayFactory, + defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd), + fileEnumerator, + lastConfigArrays, + lintResultCache, + linter, + options, + }); + + // setup special filter for fixes + if (options.fix && options.fixTypes && options.fixTypes.length > 0) { + debug(`Using fix types ${options.fixTypes}`); + + // throw an error if any invalid fix types are found + validateFixTypes(options.fixTypes); + + // convert to Set for faster lookup + const fixTypes = new Set(options.fixTypes); + + // save original value of options.fix in case it's a function + const originalFix = + typeof options.fix === "function" ? options.fix : () => true; + + options.fix = message => + shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && + originalFix(message); + } + } + + getRules() { + const { lastConfigArrays } = internalSlotsMap.get(this); + + return new Map( + (function* () { + yield* builtInRules; + + for (const configArray of lastConfigArrays) { + yield* configArray.pluginRules; + } + })(), + ); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + const filteredSuppressedMessages = + result.suppressedMessages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push({ + ...result, + messages: filteredMessages, + suppressedMessages: filteredSuppressedMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0, + }); + } + }); + + return filtered; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintReport} report The report object created by CLIEngine. + * @returns {void} + */ + static outputFixes(report) { + report.results + .filter(result => Object.hasOwn(result, "output")) + .forEach(result => { + fs.writeFileSync(result.filePath, result.output); + }); + } + + /** + * Resolves the patterns passed into executeOnFiles() into glob-based patterns + * for easier handling. + * @param {string[]} patterns The file patterns passed on the command line. + * @returns {string[]} The equivalent glob patterns. + */ + resolveFileGlobPatterns(patterns) { + const { options } = internalSlotsMap.get(this); + + if (options.globInputPaths === false) { + return patterns.filter(Boolean); + } + + const extensions = (options.extensions || [".js"]).map(ext => + ext.replace(/^\./u, ""), + ); + const dirSuffix = `/**/*.{${extensions.join(",")}}`; + + return patterns.filter(Boolean).map(pathname => { + const resolvedPath = path.resolve(options.cwd, pathname); + const newPath = directoryExists(resolvedPath) + ? pathname.replace(/[/\\]$/u, "") + dirSuffix + : pathname; + + return path.normalize(newPath).replace(/\\/gu, "/"); + }); + } + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string[]} patterns An array of file and directory names. + * @throws {Error} As may be thrown by `fs.unlinkSync`. + * @returns {LintReport} The results for all files that were linted. + */ + executeOnFiles(patterns) { + const { + cacheFilePath, + fileEnumerator, + lastConfigArrays, + lintResultCache, + linter, + options: { + allowInlineConfig, + cache, + cwd, + fix, + reportUnusedDisableDirectives, + }, + } = internalSlotsMap.get(this); + const results = []; + const startTime = Date.now(); + + // Clear the last used config arrays. + lastConfigArrays.length = 0; + + // Delete cache file; should this do here? + if (!cache) { + try { + fs.unlinkSync(cacheFilePath); + } catch (error) { + const errorCode = error && error.code; + + // Ignore errors when no such file exists or file system is read only (and cache file does not exist) + if ( + errorCode !== "ENOENT" && + !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath)) + ) { + throw error; + } + } + } + + // Iterate source code files. + for (const { config, filePath, ignored } of fileEnumerator.iterateFiles( + patterns, + )) { + if (ignored) { + results.push(createIgnoreResult(filePath, cwd)); + continue; + } + + /* + * Store used configs for: + * - this method uses to collect used deprecated rules. + * - `getRules()` method uses to collect all loaded rules. + * - `--fix-type` option uses to get the loaded rule's meta data. + */ + if (!lastConfigArrays.includes(config)) { + lastConfigArrays.push(config); + } + + // Skip if there is cached result. + if (lintResultCache) { + const cachedResult = lintResultCache.getCachedLintResults( + filePath, + config, + ); + + if (cachedResult) { + const hadMessages = + cachedResult.messages && + cachedResult.messages.length > 0; + + if (hadMessages && fix) { + debug( + `Reprocessing cached file to allow autofix: ${filePath}`, + ); + } else { + debug( + `Skipping file since it hasn't changed: ${filePath}`, + ); + results.push(cachedResult); + continue; + } + } + } + + // Do lint. + const result = verifyText({ + text: fs.readFileSync(filePath, "utf8"), + filePath, + config, + cwd, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter, + }); + + results.push(result); + + /* + * Store the lint result in the LintResultCache. + * NOTE: The LintResultCache will remove the file source and any + * other properties that are difficult to serialize, and will + * hydrate those properties back in on future lint runs. + */ + if (lintResultCache) { + lintResultCache.setCachedLintResults(filePath, config, result); + } + } + + // Persist the cache to disk. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + let usedDeprecatedRules; + + return { + results, + ...calculateStatsPerRun(results), + + // Initialize it lazily because CLI and `ESLint` API don't use it. + get usedDeprecatedRules() { + if (!usedDeprecatedRules) { + usedDeprecatedRules = Array.from( + iterateRuleDeprecationWarnings(lastConfigArrays), + ); + } + return usedDeprecatedRules; + }, + }; + } + + /** + * Executes the current configuration on text. + * @param {string} text A string of JavaScript code to lint. + * @param {string} [filename] An optional string representing the texts filename. + * @param {boolean} [warnIgnored] Always warn when a file is ignored + * @returns {LintReport} The results for the linting. + */ + executeOnText(text, filename, warnIgnored) { + const { + configArrayFactory, + fileEnumerator, + lastConfigArrays, + linter, + options: { + allowInlineConfig, + cwd, + fix, + reportUnusedDisableDirectives, + }, + } = internalSlotsMap.get(this); + const results = []; + const startTime = Date.now(); + const resolvedFilename = filename && path.resolve(cwd, filename); + + // Clear the last used config arrays. + lastConfigArrays.length = 0; + if (resolvedFilename && this.isPathIgnored(resolvedFilename)) { + if (warnIgnored) { + results.push(createIgnoreResult(resolvedFilename, cwd)); + } + } else { + const config = configArrayFactory.getConfigArrayForFile( + resolvedFilename || "__placeholder__.js", + ); + + /* + * Store used configs for: + * - this method uses to collect used deprecated rules. + * - `getRules()` method uses to collect all loaded rules. + * - `--fix-type` option uses to get the loaded rule's meta data. + */ + lastConfigArrays.push(config); + + // Do lint. + results.push( + verifyText({ + text, + filePath: resolvedFilename, + config, + cwd, + fix, + allowInlineConfig, + reportUnusedDisableDirectives, + fileEnumerator, + linter, + }), + ); + } + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + let usedDeprecatedRules; + + return { + results, + ...calculateStatsPerRun(results), + + // Initialize it lazily because CLI and `ESLint` API don't use it. + get usedDeprecatedRules() { + if (!usedDeprecatedRules) { + usedDeprecatedRules = Array.from( + iterateRuleDeprecationWarnings(lastConfigArrays), + ); + } + return usedDeprecatedRules; + }, + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @throws {Error} If filepath a directory path. + * @returns {ConfigData} A configuration object for the file. + */ + getConfigForFile(filePath) { + const { configArrayFactory, options } = internalSlotsMap.get(this); + const absolutePath = path.resolve(options.cwd, filePath); + + if (directoryExists(absolutePath)) { + throw Object.assign( + new Error("'filePath' should not be a directory path."), + { messageTemplate: "print-config-with-directory-path" }, + ); + } + + return configArrayFactory + .getConfigArrayForFile(absolutePath) + .extractConfig(absolutePath) + .toCompatibleObjectAsConfigFileContent(); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {boolean} Whether or not the given path is ignored. + */ + isPathIgnored(filePath) { + const { + configArrayFactory, + defaultIgnores, + options: { cwd, ignore }, + } = internalSlotsMap.get(this); + const absolutePath = path.resolve(cwd, filePath); + + if (ignore) { + const config = configArrayFactory + .getConfigArrayForFile(absolutePath) + .extractConfig(absolutePath); + const ignores = config.ignores || defaultIgnores; + + return ignores(absolutePath); + } + + return defaultIgnores(absolutePath); + } + + /** + * Returns the formatter representing the given format or null if the `format` is not a string. + * @param {string} [format] The name of the format to load or the path to a + * custom formatter. + * @throws {any} As may be thrown by requiring of formatter + * @returns {(FormatterFunction|null)} The formatter function or null if the `format` is not a string. + */ + getFormatter(format) { + // default is stylish + const resolvedFormatName = format || "stylish"; + + // only strings are valid formatters + if (typeof resolvedFormatName === "string") { + // replace \ with / for Windows compatibility + const normalizedFormatName = resolvedFormatName.replace( + /\\/gu, + "/", + ); + + const slots = internalSlotsMap.get(this); + const cwd = slots ? slots.options.cwd : process.cwd(); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); + + let formatterPath; + + // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) + if (!namespace && normalizedFormatName.includes("/")) { + formatterPath = path.resolve(cwd, normalizedFormatName); + } else { + try { + const npmFormat = naming.normalizePackageName( + normalizedFormatName, + "eslint-formatter", + ); + + formatterPath = ModuleResolver.resolve( + npmFormat, + path.join(cwd, "__placeholder__.js"), + ); + } catch { + formatterPath = path.resolve( + __dirname, + "formatters", + normalizedFormatName, + ); + } + } + + try { + return require(formatterPath); + } catch (ex) { + if (removedFormatters.has(format)) { + ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``; + } else { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + } + throw ex; + } + } else { + return null; + } + } } CLIEngine.version = pkg.version; CLIEngine.getFormatter = CLIEngine.prototype.getFormatter; module.exports = { - CLIEngine, - - /** - * Get the internal slots of a given CLIEngine instance for tests. - * @param {CLIEngine} instance The CLIEngine instance to get. - * @returns {CLIEngineInternalSlots} The internal slots. - */ - getCLIEngineInternalSlots(instance) { - return internalSlotsMap.get(instance); - } + CLIEngine, + + /** + * Get the internal slots of a given CLIEngine instance for tests. + * @param {CLIEngine} instance The CLIEngine instance to get. + * @returns {CLIEngineInternalSlots} The internal slots. + */ + getCLIEngineInternalSlots(instance) { + return internalSlotsMap.get(instance); + }, }; diff --git a/lib/cli-engine/file-enumerator.js b/lib/cli-engine/file-enumerator.js index da5d1039da7f..a07886219739 100644 --- a/lib/cli-engine/file-enumerator.js +++ b/lib/cli-engine/file-enumerator.js @@ -42,10 +42,7 @@ const escapeRegExp = require("escape-string-regexp"); const { Minimatch } = require("minimatch"); const { - Legacy: { - IgnorePattern, - CascadingConfigArrayFactory - } + Legacy: { IgnorePattern, CascadingConfigArrayFactory }, } = require("@eslint/eslintrc"); const debug = require("debug")("eslint:file-enumerator"); @@ -108,7 +105,7 @@ const internalSlotsMap = new WeakMap(); * @returns {boolean} `true` if the string is a glob pattern. */ function isGlobPattern(pattern) { - return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern); + return isGlob(path.sep === "\\" ? pattern.replace(/\\/gu, "/") : pattern); } /** @@ -119,16 +116,15 @@ function isGlobPattern(pattern) { * @private */ function statSafeSync(filePath) { - try { - return fs.statSync(filePath); - } catch (error) { - - /* c8 ignore next */ - if (error.code !== "ENOENT") { - throw error; - } - return null; - } + try { + return fs.statSync(filePath); + } catch (error) { + /* c8 ignore next */ + if (error.code !== "ENOENT") { + throw error; + } + return null; + } } /** @@ -139,16 +135,15 @@ function statSafeSync(filePath) { * @private */ function readdirSafeSync(directoryPath) { - try { - return fs.readdirSync(directoryPath, { withFileTypes: true }); - } catch (error) { - - /* c8 ignore next */ - if (error.code !== "ENOENT") { - throw error; - } - return []; - } + try { + return fs.readdirSync(directoryPath, { withFileTypes: true }); + } catch (error) { + /* c8 ignore next */ + if (error.code !== "ENOENT") { + throw error; + } + return []; + } } /** @@ -157,50 +152,45 @@ function readdirSafeSync(directoryPath) { * @returns {RegExp | null} The created `RegExp` object or null. */ function createExtensionRegExp(extensions) { - if (extensions) { - const normalizedExts = extensions.map(ext => escapeRegExp( - ext.startsWith(".") - ? ext.slice(1) - : ext - )); - - return new RegExp( - `.\\.(?:${normalizedExts.join("|")})$`, - "u" - ); - } - return null; + if (extensions) { + const normalizedExts = extensions.map(ext => + escapeRegExp(ext.startsWith(".") ? ext.slice(1) : ext), + ); + + return new RegExp(`.\\.(?:${normalizedExts.join("|")})$`, "u"); + } + return null; } /** * The error type when no files match a glob. */ class NoFilesFoundError extends Error { - - /** - * @param {string} pattern The glob pattern which was not found. - * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled. - */ - constructor(pattern, globDisabled) { - super(`No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`); - this.messageTemplate = "file-not-found"; - this.messageData = { pattern, globDisabled }; - } + /** + * @param {string} pattern The glob pattern which was not found. + * @param {boolean} globDisabled If `true` then the pattern was a glob pattern, but glob was disabled. + */ + constructor(pattern, globDisabled) { + super( + `No files matching '${pattern}' were found${globDisabled ? " (glob was disabled)" : ""}.`, + ); + this.messageTemplate = "file-not-found"; + this.messageData = { pattern, globDisabled }; + } } /** * The error type when there are files matched by a glob, but all of them have been ignored. */ class AllFilesIgnoredError extends Error { - - /** - * @param {string} pattern The glob pattern which was not found. - */ - constructor(pattern) { - super(`All files matched by '${pattern}' are ignored.`); - this.messageTemplate = "all-files-ignored"; - this.messageData = { pattern }; - } + /** + * @param {string} pattern The glob pattern which was not found. + */ + constructor(pattern) { + super(`All files matched by '${pattern}' are ignored.`); + this.messageTemplate = "all-files-ignored"; + this.messageData = { pattern }; + } } /** @@ -208,336 +198,340 @@ class AllFilesIgnoredError extends Error { * matched by given glob patterns and that configuration. */ class FileEnumerator { - - /** - * Initialize this enumerator. - * @param {FileEnumeratorOptions} options The options. - */ - constructor({ - cwd = process.cwd(), - configArrayFactory = new CascadingConfigArrayFactory({ - cwd, - getEslintRecommendedConfig: () => require("@eslint/js").configs.recommended, - getEslintAllConfig: () => require("@eslint/js").configs.all - }), - extensions = null, - globInputPaths = true, - errorOnUnmatchedPattern = true, - ignore = true - } = {}) { - internalSlotsMap.set(this, { - configArrayFactory, - cwd, - defaultIgnores: IgnorePattern.createDefaultIgnore(cwd), - extensionRegExp: createExtensionRegExp(extensions), - globInputPaths, - errorOnUnmatchedPattern, - ignoreFlag: ignore - }); - } - - /** - * Check if a given file is target or not. - * @param {string} filePath The path to a candidate file. - * @param {ConfigArray} [providedConfig] Optional. The configuration for the file. - * @returns {boolean} `true` if the file is a target. - */ - isTargetPath(filePath, providedConfig) { - const { - configArrayFactory, - extensionRegExp - } = internalSlotsMap.get(this); - - // If `--ext` option is present, use it. - if (extensionRegExp) { - return extensionRegExp.test(filePath); - } - - // `.js` file is target by default. - if (filePath.endsWith(".js")) { - return true; - } - - // use `overrides[].files` to check additional targets. - const config = - providedConfig || - configArrayFactory.getConfigArrayForFile( - filePath, - { ignoreNotFoundError: true } - ); - - return config.isAdditionalTargetPath(filePath); - } - - /** - * Iterate files which are matched by given glob patterns. - * @param {string|string[]} patternOrPatterns The glob patterns to iterate files. - * @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern. - * @returns {IterableIterator} The found files. - */ - *iterateFiles(patternOrPatterns) { - const { globInputPaths, errorOnUnmatchedPattern } = internalSlotsMap.get(this); - const patterns = Array.isArray(patternOrPatterns) - ? patternOrPatterns - : [patternOrPatterns]; - - debug("Start to iterate files: %o", patterns); - - // The set of paths to remove duplicate. - const set = new Set(); - - for (const pattern of patterns) { - let foundRegardlessOfIgnored = false; - let found = false; - - // Skip empty string. - if (!pattern) { - continue; - } - - // Iterate files of this pattern. - for (const { config, filePath, flag } of this._iterateFiles(pattern)) { - foundRegardlessOfIgnored = true; - if (flag === IGNORED_SILENTLY) { - continue; - } - found = true; - - // Remove duplicate paths while yielding paths. - if (!set.has(filePath)) { - set.add(filePath); - yield { - config, - filePath, - ignored: flag === IGNORED - }; - } - } - - // Raise an error if any files were not found. - if (errorOnUnmatchedPattern) { - if (!foundRegardlessOfIgnored) { - throw new NoFilesFoundError( - pattern, - !globInputPaths && isGlob(pattern) - ); - } - if (!found) { - throw new AllFilesIgnoredError(pattern); - } - } - } - - debug(`Complete iterating files: ${JSON.stringify(patterns)}`); - } - - /** - * Iterate files which are matched by a given glob pattern. - * @param {string} pattern The glob pattern to iterate files. - * @returns {IterableIterator} The found files. - */ - _iterateFiles(pattern) { - const { cwd, globInputPaths } = internalSlotsMap.get(this); - const absolutePath = path.resolve(cwd, pattern); - const isDot = dotfilesPattern.test(pattern); - const stat = statSafeSync(absolutePath); - - if (stat && stat.isDirectory()) { - return this._iterateFilesWithDirectory(absolutePath, isDot); - } - if (stat && stat.isFile()) { - return this._iterateFilesWithFile(absolutePath); - } - if (globInputPaths && isGlobPattern(pattern)) { - return this._iterateFilesWithGlob(pattern, isDot); - } - - return []; - } - - /** - * Iterate a file which is matched by a given path. - * @param {string} filePath The path to the target file. - * @returns {IterableIterator} The found files. - * @private - */ - _iterateFilesWithFile(filePath) { - debug(`File: ${filePath}`); - - const { configArrayFactory } = internalSlotsMap.get(this); - const config = configArrayFactory.getConfigArrayForFile(filePath); - const ignored = this._isIgnoredFile(filePath, { config, direct: true }); - const flag = ignored ? IGNORED : NONE; - - return [{ config, filePath, flag }]; - } - - /** - * Iterate files in a given path. - * @param {string} directoryPath The path to the target directory. - * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. - * @returns {IterableIterator} The found files. - * @private - */ - _iterateFilesWithDirectory(directoryPath, dotfiles) { - debug(`Directory: ${directoryPath}`); - - return this._iterateFilesRecursive( - directoryPath, - { dotfiles, recursive: true, selector: null } - ); - } - - /** - * Iterate files which are matched by a given glob pattern. - * @param {string} pattern The glob pattern to iterate files. - * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. - * @returns {IterableIterator} The found files. - * @private - */ - _iterateFilesWithGlob(pattern, dotfiles) { - debug(`Glob: ${pattern}`); - - const { cwd } = internalSlotsMap.get(this); - const directoryPath = path.resolve(cwd, getGlobParent(pattern)); - const absolutePath = path.resolve(cwd, pattern); - const globPart = absolutePath.slice(directoryPath.length + 1); - - /* - * recursive if there are `**` or path separators in the glob part. - * Otherwise, patterns such as `src/*.js`, it doesn't need recursive. - */ - const recursive = /\*\*|\/|\\/u.test(globPart); - const selector = new Minimatch(absolutePath, minimatchOpts); - - debug(`recursive? ${recursive}`); - - return this._iterateFilesRecursive( - directoryPath, - { dotfiles, recursive, selector } - ); - } - - /** - * Iterate files in a given path. - * @param {string} directoryPath The path to the target directory. - * @param {Object} options The options to iterate files. - * @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default. - * @param {boolean} [options.recursive] If `true` then it dives into sub directories. - * @param {InstanceType} [options.selector] The matcher to choose files. - * @returns {IterableIterator} The found files. - * @private - */ - *_iterateFilesRecursive(directoryPath, options) { - debug(`Enter the directory: ${directoryPath}`); - const { configArrayFactory } = internalSlotsMap.get(this); - - /** @type {ConfigArray|null} */ - let config = null; - - // Enumerate the files of this directory. - for (const entry of readdirSafeSync(directoryPath)) { - const filePath = path.join(directoryPath, entry.name); - const fileInfo = entry.isSymbolicLink() ? statSafeSync(filePath) : entry; - - if (!fileInfo) { - continue; - } - - // Check if the file is matched. - if (fileInfo.isFile()) { - if (!config) { - config = configArrayFactory.getConfigArrayForFile( - filePath, - - /* - * We must ignore `ConfigurationNotFoundError` at this - * point because we don't know if target files exist in - * this directory. - */ - { ignoreNotFoundError: true } - ); - } - const matched = options.selector - - // Started with a glob pattern; choose by the pattern. - ? options.selector.match(filePath) - - // Started with a directory path; choose by file extensions. - : this.isTargetPath(filePath, config); - - if (matched) { - const ignored = this._isIgnoredFile(filePath, { ...options, config }); - const flag = ignored ? IGNORED_SILENTLY : NONE; - - debug(`Yield: ${entry.name}${ignored ? " but ignored" : ""}`); - yield { - config: configArrayFactory.getConfigArrayForFile(filePath), - filePath, - flag - }; - } else { - debug(`Didn't match: ${entry.name}`); - } - - // Dive into the sub directory. - } else if (options.recursive && fileInfo.isDirectory()) { - if (!config) { - config = configArrayFactory.getConfigArrayForFile( - filePath, - { ignoreNotFoundError: true } - ); - } - const ignored = this._isIgnoredFile( - filePath + path.sep, - { ...options, config } - ); - - if (!ignored) { - yield* this._iterateFilesRecursive(filePath, options); - } - } - } - - debug(`Leave the directory: ${directoryPath}`); - } - - /** - * Check if a given file should be ignored. - * @param {string} filePath The path to a file to check. - * @param {Object} options Options - * @param {ConfigArray} [options.config] The config for this file. - * @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default. - * @param {boolean} [options.direct] If `true` then this is a direct specified file. - * @returns {boolean} `true` if the file should be ignored. - * @private - */ - _isIgnoredFile(filePath, { - config: providedConfig, - dotfiles = false, - direct = false - }) { - const { - configArrayFactory, - defaultIgnores, - ignoreFlag - } = internalSlotsMap.get(this); - - if (ignoreFlag) { - const config = - providedConfig || - configArrayFactory.getConfigArrayForFile( - filePath, - { ignoreNotFoundError: true } - ); - const ignores = - config.extractConfig(filePath).ignores || defaultIgnores; - - return ignores(filePath, dotfiles); - } - - return !direct && defaultIgnores(filePath, dotfiles); - } + /** + * Initialize this enumerator. + * @param {FileEnumeratorOptions} options The options. + */ + constructor({ + cwd = process.cwd(), + configArrayFactory = new CascadingConfigArrayFactory({ + cwd, + getEslintRecommendedConfig: () => + require("@eslint/js").configs.recommended, + getEslintAllConfig: () => require("@eslint/js").configs.all, + }), + extensions = null, + globInputPaths = true, + errorOnUnmatchedPattern = true, + ignore = true, + } = {}) { + internalSlotsMap.set(this, { + configArrayFactory, + cwd, + defaultIgnores: IgnorePattern.createDefaultIgnore(cwd), + extensionRegExp: createExtensionRegExp(extensions), + globInputPaths, + errorOnUnmatchedPattern, + ignoreFlag: ignore, + }); + } + + /** + * Check if a given file is target or not. + * @param {string} filePath The path to a candidate file. + * @param {ConfigArray} [providedConfig] Optional. The configuration for the file. + * @returns {boolean} `true` if the file is a target. + */ + isTargetPath(filePath, providedConfig) { + const { configArrayFactory, extensionRegExp } = + internalSlotsMap.get(this); + + // If `--ext` option is present, use it. + if (extensionRegExp) { + return extensionRegExp.test(filePath); + } + + // `.js` file is target by default. + if (filePath.endsWith(".js")) { + return true; + } + + // use `overrides[].files` to check additional targets. + const config = + providedConfig || + configArrayFactory.getConfigArrayForFile(filePath, { + ignoreNotFoundError: true, + }); + + return config.isAdditionalTargetPath(filePath); + } + + /** + * Iterate files which are matched by given glob patterns. + * @param {string|string[]} patternOrPatterns The glob patterns to iterate files. + * @throws {NoFilesFoundError|AllFilesIgnoredError} On an unmatched pattern. + * @returns {IterableIterator} The found files. + */ + *iterateFiles(patternOrPatterns) { + const { globInputPaths, errorOnUnmatchedPattern } = + internalSlotsMap.get(this); + const patterns = Array.isArray(patternOrPatterns) + ? patternOrPatterns + : [patternOrPatterns]; + + debug("Start to iterate files: %o", patterns); + + // The set of paths to remove duplicate. + const set = new Set(); + + for (const pattern of patterns) { + let foundRegardlessOfIgnored = false; + let found = false; + + // Skip empty string. + if (!pattern) { + continue; + } + + // Iterate files of this pattern. + for (const { config, filePath, flag } of this._iterateFiles( + pattern, + )) { + foundRegardlessOfIgnored = true; + if (flag === IGNORED_SILENTLY) { + continue; + } + found = true; + + // Remove duplicate paths while yielding paths. + if (!set.has(filePath)) { + set.add(filePath); + yield { + config, + filePath, + ignored: flag === IGNORED, + }; + } + } + + // Raise an error if any files were not found. + if (errorOnUnmatchedPattern) { + if (!foundRegardlessOfIgnored) { + throw new NoFilesFoundError( + pattern, + !globInputPaths && isGlob(pattern), + ); + } + if (!found) { + throw new AllFilesIgnoredError(pattern); + } + } + } + + debug(`Complete iterating files: ${JSON.stringify(patterns)}`); + } + + /** + * Iterate files which are matched by a given glob pattern. + * @param {string} pattern The glob pattern to iterate files. + * @returns {IterableIterator} The found files. + */ + _iterateFiles(pattern) { + const { cwd, globInputPaths } = internalSlotsMap.get(this); + const absolutePath = path.resolve(cwd, pattern); + const isDot = dotfilesPattern.test(pattern); + const stat = statSafeSync(absolutePath); + + if (stat && stat.isDirectory()) { + return this._iterateFilesWithDirectory(absolutePath, isDot); + } + if (stat && stat.isFile()) { + return this._iterateFilesWithFile(absolutePath); + } + if (globInputPaths && isGlobPattern(pattern)) { + return this._iterateFilesWithGlob(pattern, isDot); + } + + return []; + } + + /** + * Iterate a file which is matched by a given path. + * @param {string} filePath The path to the target file. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithFile(filePath) { + debug(`File: ${filePath}`); + + const { configArrayFactory } = internalSlotsMap.get(this); + const config = configArrayFactory.getConfigArrayForFile(filePath); + const ignored = this._isIgnoredFile(filePath, { config, direct: true }); + const flag = ignored ? IGNORED : NONE; + + return [{ config, filePath, flag }]; + } + + /** + * Iterate files in a given path. + * @param {string} directoryPath The path to the target directory. + * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithDirectory(directoryPath, dotfiles) { + debug(`Directory: ${directoryPath}`); + + return this._iterateFilesRecursive(directoryPath, { + dotfiles, + recursive: true, + selector: null, + }); + } + + /** + * Iterate files which are matched by a given glob pattern. + * @param {string} pattern The glob pattern to iterate files. + * @param {boolean} dotfiles If `true` then it doesn't skip dot files by default. + * @returns {IterableIterator} The found files. + * @private + */ + _iterateFilesWithGlob(pattern, dotfiles) { + debug(`Glob: ${pattern}`); + + const { cwd } = internalSlotsMap.get(this); + const directoryPath = path.resolve(cwd, getGlobParent(pattern)); + const absolutePath = path.resolve(cwd, pattern); + const globPart = absolutePath.slice(directoryPath.length + 1); + + /* + * recursive if there are `**` or path separators in the glob part. + * Otherwise, patterns such as `src/*.js`, it doesn't need recursive. + */ + const recursive = /\*\*|\/|\\/u.test(globPart); + const selector = new Minimatch(absolutePath, minimatchOpts); + + debug(`recursive? ${recursive}`); + + return this._iterateFilesRecursive(directoryPath, { + dotfiles, + recursive, + selector, + }); + } + + /** + * Iterate files in a given path. + * @param {string} directoryPath The path to the target directory. + * @param {Object} options The options to iterate files. + * @param {boolean} [options.dotfiles] If `true` then it doesn't skip dot files by default. + * @param {boolean} [options.recursive] If `true` then it dives into sub directories. + * @param {InstanceType} [options.selector] The matcher to choose files. + * @returns {IterableIterator} The found files. + * @private + */ + *_iterateFilesRecursive(directoryPath, options) { + debug(`Enter the directory: ${directoryPath}`); + const { configArrayFactory } = internalSlotsMap.get(this); + + /** @type {ConfigArray|null} */ + let config = null; + + // Enumerate the files of this directory. + for (const entry of readdirSafeSync(directoryPath)) { + const filePath = path.join(directoryPath, entry.name); + const fileInfo = entry.isSymbolicLink() + ? statSafeSync(filePath) + : entry; + + if (!fileInfo) { + continue; + } + + // Check if the file is matched. + if (fileInfo.isFile()) { + if (!config) { + config = configArrayFactory.getConfigArrayForFile( + filePath, + + /* + * We must ignore `ConfigurationNotFoundError` at this + * point because we don't know if target files exist in + * this directory. + */ + { ignoreNotFoundError: true }, + ); + } + const matched = options.selector + ? // Started with a glob pattern; choose by the pattern. + options.selector.match(filePath) + : // Started with a directory path; choose by file extensions. + this.isTargetPath(filePath, config); + + if (matched) { + const ignored = this._isIgnoredFile(filePath, { + ...options, + config, + }); + const flag = ignored ? IGNORED_SILENTLY : NONE; + + debug( + `Yield: ${entry.name}${ignored ? " but ignored" : ""}`, + ); + yield { + config: configArrayFactory.getConfigArrayForFile( + filePath, + ), + filePath, + flag, + }; + } else { + debug(`Didn't match: ${entry.name}`); + } + + // Dive into the sub directory. + } else if (options.recursive && fileInfo.isDirectory()) { + if (!config) { + config = configArrayFactory.getConfigArrayForFile( + filePath, + { ignoreNotFoundError: true }, + ); + } + const ignored = this._isIgnoredFile(filePath + path.sep, { + ...options, + config, + }); + + if (!ignored) { + yield* this._iterateFilesRecursive(filePath, options); + } + } + } + + debug(`Leave the directory: ${directoryPath}`); + } + + /** + * Check if a given file should be ignored. + * @param {string} filePath The path to a file to check. + * @param {Object} options Options + * @param {ConfigArray} [options.config] The config for this file. + * @param {boolean} [options.dotfiles] If `true` then this is not ignore dot files by default. + * @param {boolean} [options.direct] If `true` then this is a direct specified file. + * @returns {boolean} `true` if the file should be ignored. + * @private + */ + _isIgnoredFile( + filePath, + { config: providedConfig, dotfiles = false, direct = false }, + ) { + const { configArrayFactory, defaultIgnores, ignoreFlag } = + internalSlotsMap.get(this); + + if (ignoreFlag) { + const config = + providedConfig || + configArrayFactory.getConfigArrayForFile(filePath, { + ignoreNotFoundError: true, + }); + const ignores = + config.extractConfig(filePath).ignores || defaultIgnores; + + return ignores(filePath, dotfiles); + } + + return !direct && defaultIgnores(filePath, dotfiles); + } } //------------------------------------------------------------------------------ diff --git a/lib/cli-engine/formatters/formatters-meta.json b/lib/cli-engine/formatters/formatters-meta.json index 4cc61ae3c62d..cf3205291249 100644 --- a/lib/cli-engine/formatters/formatters-meta.json +++ b/lib/cli-engine/formatters/formatters-meta.json @@ -1,18 +1,18 @@ [ - { - "name": "html", - "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser." - }, - { - "name": "json-with-metadata", - "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." - }, - { - "name": "json", - "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." - }, - { - "name": "stylish", - "description": "Human-readable output format. This is the default formatter." - } + { + "name": "html", + "description": "Outputs results to HTML. The `html` formatter is useful for visual presentation in the browser." + }, + { + "name": "json-with-metadata", + "description": "Outputs JSON-serialized results. The `json-with-metadata` provides the same linting results as the [`json`](#json) formatter with additional metadata about the rules applied. The linting results are included in the `results` property and the rules metadata is included in the `metadata` property.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "json", + "description": "Outputs JSON-serialized results. The `json` formatter is useful when you want to programmatically work with the CLI's linting results.\n\nAlternatively, you can use the [ESLint Node.js API](../../integrate/nodejs-api) to programmatically use ESLint." + }, + { + "name": "stylish", + "description": "Human-readable output format. This is the default formatter." + } ] diff --git a/lib/cli-engine/formatters/html.js b/lib/cli-engine/formatters/html.js index 1aa66fcefacc..c6ad8dd81908 100644 --- a/lib/cli-engine/formatters/html.js +++ b/lib/cli-engine/formatters/html.js @@ -8,22 +8,22 @@ // Helpers //------------------------------------------------------------------------------ -const encodeHTML = (function() { - const encodeHTMLRules = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'" - }; - const matchHTML = /[&<>"']/ug; - - return function(code) { - return code - ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m) - : ""; - }; -}()); +const encodeHTML = (function () { + const encodeHTMLRules = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }; + const matchHTML = /[&<>"']/gu; + + return function (code) { + return code + ? code.toString().replace(matchHTML, m => encodeHTMLRules[m] || m) + : ""; + }; +})(); /** * Get the final HTML document. @@ -31,9 +31,9 @@ const encodeHTML = (function() { * @returns {string} HTML document. */ function pageTemplate(it) { - const { reportColor, reportSummary, date, results } = it; + const { reportColor, reportSummary, date, results } = it; - return ` + return ` @@ -186,7 +186,7 @@ function pageTemplate(it) { * @returns {string} The original word with an s on the end if count is not one. */ function pluralize(word, count) { - return (count === 1 ? word : `${word}s`); + return count === 1 ? word : `${word}s`; } /** @@ -196,13 +196,13 @@ function pluralize(word, count) { * @returns {string} The formatted string, pluralized where necessary */ function renderSummary(totalErrors, totalWarnings) { - const totalProblems = totalErrors + totalWarnings; - let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`; + const totalProblems = totalErrors + totalWarnings; + let renderedText = `${totalProblems} ${pluralize("problem", totalProblems)}`; - if (totalProblems !== 0) { - renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`; - } - return renderedText; + if (totalProblems !== 0) { + renderedText += ` (${totalErrors} ${pluralize("error", totalErrors)}, ${totalWarnings} ${pluralize("warning", totalWarnings)})`; + } + return renderedText; } /** @@ -212,13 +212,13 @@ function renderSummary(totalErrors, totalWarnings) { * @returns {int} The color code (0 = green, 1 = yellow, 2 = red) */ function renderColor(totalErrors, totalWarnings) { - if (totalErrors !== 0) { - return 2; - } - if (totalWarnings !== 0) { - return 1; - } - return 0; + if (totalErrors !== 0) { + return 2; + } + if (totalWarnings !== 0) { + return 1; + } + return 0; } /** @@ -227,18 +227,18 @@ function renderColor(totalErrors, totalWarnings) { * @returns {string} HTML (table row) describing the message. */ function messageTemplate(it) { - const { - parentIndex, - lineNumber, - columnNumber, - severityNumber, - severityName, - message, - ruleUrl, - ruleId - } = it; - - return ` + const { + parentIndex, + lineNumber, + columnNumber, + severityNumber, + severityName, + message, + ruleUrl, + ruleId, + } = it; + + return ` @@ -258,36 +258,37 @@ function messageTemplate(it) { * @returns {string} HTML (table rows) describing the messages. */ function renderMessages(messages, parentIndex, rulesMeta) { - - /** - * Get HTML (table row) describing a message. - * @param {Object} message Message. - * @returns {string} HTML (table row) describing a message. - */ - return messages.map(message => { - const lineNumber = message.line || 0; - const columnNumber = message.column || 0; - let ruleUrl; - - if (rulesMeta) { - const meta = rulesMeta[message.ruleId]; - - if (meta && meta.docs && meta.docs.url) { - ruleUrl = meta.docs.url; - } - } - - return messageTemplate({ - parentIndex, - lineNumber, - columnNumber, - severityNumber: message.severity, - severityName: message.severity === 1 ? "Warning" : "Error", - message: message.message, - ruleId: message.ruleId, - ruleUrl - }); - }).join("\n"); + /** + * Get HTML (table row) describing a message. + * @param {Object} message Message. + * @returns {string} HTML (table row) describing a message. + */ + return messages + .map(message => { + const lineNumber = message.line || 0; + const columnNumber = message.column || 0; + let ruleUrl; + + if (rulesMeta) { + const meta = rulesMeta[message.ruleId]; + + if (meta && meta.docs && meta.docs.url) { + ruleUrl = meta.docs.url; + } + } + + return messageTemplate({ + parentIndex, + lineNumber, + columnNumber, + severityNumber: message.severity, + severityName: message.severity === 1 ? "Warning" : "Error", + message: message.message, + ruleId: message.ruleId, + ruleUrl, + }); + }) + .join("\n"); } /** @@ -296,9 +297,9 @@ function renderMessages(messages, parentIndex, rulesMeta) { * @returns {string} HTML (table row) describing the result for the file. */ function resultTemplate(it) { - const { color, index, filePath, summary } = it; + const { color, index, filePath, summary } = it; - return ` + return ` " : ""}`, + ) + .join("")}
[+] ${encodeHTML(filePath)} @@ -315,37 +316,44 @@ function resultTemplate(it) { * @returns {string} HTML string describing the results. */ function renderResults(results, rulesMeta) { - return results.map((result, index) => resultTemplate({ - index, - color: renderColor(result.errorCount, result.warningCount), - filePath: result.filePath, - summary: renderSummary(result.errorCount, result.warningCount) - }) + renderMessages(result.messages, index, rulesMeta)).join("\n"); + return results + .map( + (result, index) => + resultTemplate({ + index, + color: renderColor(result.errorCount, result.warningCount), + filePath: result.filePath, + summary: renderSummary( + result.errorCount, + result.warningCount, + ), + }) + renderMessages(result.messages, index, rulesMeta), + ) + .join("\n"); } //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ -module.exports = function(results, data) { - let totalErrors, - totalWarnings; +module.exports = function (results, data) { + let totalErrors, totalWarnings; - const metaData = data ? data.rulesMeta : {}; + const metaData = data ? data.rulesMeta : {}; - totalErrors = 0; - totalWarnings = 0; + totalErrors = 0; + totalWarnings = 0; - // Iterate over results to get totals - results.forEach(result => { - totalErrors += result.errorCount; - totalWarnings += result.warningCount; - }); + // Iterate over results to get totals + results.forEach(result => { + totalErrors += result.errorCount; + totalWarnings += result.warningCount; + }); - return pageTemplate({ - date: new Date(), - reportColor: renderColor(totalErrors, totalWarnings), - reportSummary: renderSummary(totalErrors, totalWarnings), - results: renderResults(results, metaData) - }); + return pageTemplate({ + date: new Date(), + reportColor: renderColor(totalErrors, totalWarnings), + reportSummary: renderSummary(totalErrors, totalWarnings), + results: renderResults(results, metaData), + }); }; diff --git a/lib/cli-engine/formatters/json-with-metadata.js b/lib/cli-engine/formatters/json-with-metadata.js index 6899471547a4..44c785c74175 100644 --- a/lib/cli-engine/formatters/json-with-metadata.js +++ b/lib/cli-engine/formatters/json-with-metadata.js @@ -8,9 +8,9 @@ // Public Interface //------------------------------------------------------------------------------ -module.exports = function(results, data) { - return JSON.stringify({ - results, - metadata: data - }); +module.exports = function (results, data) { + return JSON.stringify({ + results, + metadata: data, + }); }; diff --git a/lib/cli-engine/formatters/json.js b/lib/cli-engine/formatters/json.js index 82138af18748..230f5c5876de 100644 --- a/lib/cli-engine/formatters/json.js +++ b/lib/cli-engine/formatters/json.js @@ -8,6 +8,6 @@ // Public Interface //------------------------------------------------------------------------------ -module.exports = function(results) { - return JSON.stringify(results); +module.exports = function (results) { + return JSON.stringify(results); }; diff --git a/lib/cli-engine/formatters/stylish.js b/lib/cli-engine/formatters/stylish.js index a5cf39ba261d..da7554688b14 100644 --- a/lib/cli-engine/formatters/stylish.js +++ b/lib/cli-engine/formatters/stylish.js @@ -5,8 +5,8 @@ "use strict"; const chalk = require("chalk"), - util = require("node:util"), - table = require("../../shared/text-table"); + util = require("node:util"), + table = require("../../shared/text-table"); //------------------------------------------------------------------------------ // Helpers @@ -19,83 +19,104 @@ const chalk = require("chalk"), * @returns {string} The original word with an s on the end if count is not one. */ function pluralize(word, count) { - return (count === 1 ? word : `${word}s`); + return count === 1 ? word : `${word}s`; } //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ -module.exports = function(results) { - - let output = "\n", - errorCount = 0, - warningCount = 0, - fixableErrorCount = 0, - fixableWarningCount = 0, - summaryColor = "yellow"; - - results.forEach(result => { - const messages = result.messages; - - if (messages.length === 0) { - return; - } - - errorCount += result.errorCount; - warningCount += result.warningCount; - fixableErrorCount += result.fixableErrorCount; - fixableWarningCount += result.fixableWarningCount; - - output += `${chalk.underline(result.filePath)}\n`; - - output += `${table( - messages.map(message => { - let messageType; - - if (message.fatal || message.severity === 2) { - messageType = chalk.red("error"); - summaryColor = "red"; - } else { - messageType = chalk.yellow("warning"); - } - - return [ - "", - String(message.line || 0), - String(message.column || 0), - messageType, - message.message.replace(/([^ ])\.$/u, "$1"), - chalk.dim(message.ruleId || "") - ]; - }), - { - align: ["", "r", "l"], - stringLength(str) { - return util.stripVTControlCharacters(str).length; - } - } - ).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`; - }); - - const total = errorCount + warningCount; - - if (total > 0) { - output += chalk[summaryColor].bold([ - "\u2716 ", total, pluralize(" problem", total), - " (", errorCount, pluralize(" error", errorCount), ", ", - warningCount, pluralize(" warning", warningCount), ")\n" - ].join("")); - - if (fixableErrorCount > 0 || fixableWarningCount > 0) { - output += chalk[summaryColor].bold([ - " ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ", - fixableWarningCount, pluralize(" warning", fixableWarningCount), - " potentially fixable with the `--fix` option.\n" - ].join("")); - } - } - - // Resets output color, for prevent change on top level - return total > 0 ? chalk.reset(output) : ""; +module.exports = function (results) { + let output = "\n", + errorCount = 0, + warningCount = 0, + fixableErrorCount = 0, + fixableWarningCount = 0, + summaryColor = "yellow"; + + results.forEach(result => { + const messages = result.messages; + + if (messages.length === 0) { + return; + } + + errorCount += result.errorCount; + warningCount += result.warningCount; + fixableErrorCount += result.fixableErrorCount; + fixableWarningCount += result.fixableWarningCount; + + output += `${chalk.underline(result.filePath)}\n`; + + output += `${table( + messages.map(message => { + let messageType; + + if (message.fatal || message.severity === 2) { + messageType = chalk.red("error"); + summaryColor = "red"; + } else { + messageType = chalk.yellow("warning"); + } + + return [ + "", + String(message.line || 0), + String(message.column || 0), + messageType, + message.message.replace(/([^ ])\.$/u, "$1"), + chalk.dim(message.ruleId || ""), + ]; + }), + { + align: ["", "r", "l"], + stringLength(str) { + return util.stripVTControlCharacters(str).length; + }, + }, + ) + .split("\n") + .map(el => + el.replace(/(\d+)\s+(\d+)/u, (m, p1, p2) => + chalk.dim(`${p1}:${p2}`), + ), + ) + .join("\n")}\n\n`; + }); + + const total = errorCount + warningCount; + + if (total > 0) { + output += chalk[summaryColor].bold( + [ + "\u2716 ", + total, + pluralize(" problem", total), + " (", + errorCount, + pluralize(" error", errorCount), + ", ", + warningCount, + pluralize(" warning", warningCount), + ")\n", + ].join(""), + ); + + if (fixableErrorCount > 0 || fixableWarningCount > 0) { + output += chalk[summaryColor].bold( + [ + " ", + fixableErrorCount, + pluralize(" error", fixableErrorCount), + " and ", + fixableWarningCount, + pluralize(" warning", fixableWarningCount), + " potentially fixable with the `--fix` option.\n", + ].join(""), + ); + } + } + + // Resets output color, for prevent change on top level + return total > 0 ? chalk.reset(output) : ""; }; diff --git a/lib/cli-engine/hash.js b/lib/cli-engine/hash.js index 8e467734a023..4c306b9b4d6b 100644 --- a/lib/cli-engine/hash.js +++ b/lib/cli-engine/hash.js @@ -25,7 +25,7 @@ const murmur = require("imurmurhash"); * @returns {string} the hash */ function hash(str) { - return murmur(str).result().toString(36); + return murmur(str).result().toString(36); } //------------------------------------------------------------------------------ diff --git a/lib/cli-engine/index.js b/lib/cli-engine/index.js index 52e45a6d7910..bba3fb0b8ad7 100644 --- a/lib/cli-engine/index.js +++ b/lib/cli-engine/index.js @@ -3,5 +3,5 @@ const { CLIEngine } = require("./cli-engine"); module.exports = { - CLIEngine + CLIEngine, }; diff --git a/lib/cli-engine/lint-result-cache.js b/lib/cli-engine/lint-result-cache.js index 320362d4943c..1bca3e8a0ac0 100644 --- a/lib/cli-engine/lint-result-cache.js +++ b/lib/cli-engine/lint-result-cache.js @@ -26,8 +26,8 @@ const nodeVersion = process && process.version; const validCacheStrategies = ["metadata", "content"]; const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${validCacheStrategies - .map(strategy => `"${strategy}"`) - .join(", ")}`; + .map(strategy => `"${strategy}"`) + .join(", ")}`; /** * Tests whether a provided cacheStrategy is valid @@ -35,9 +35,7 @@ const invalidCacheStrategyErrorMessage = `Cache strategy must be one of: ${valid * @returns {boolean} true if `cacheStrategy` is one of `validCacheStrategies`; false otherwise */ function isValidCacheStrategy(cacheStrategy) { - return ( - validCacheStrategies.includes(cacheStrategy) - ); + return validCacheStrategies.includes(cacheStrategy); } /** @@ -46,11 +44,14 @@ function isValidCacheStrategy(cacheStrategy) { * @returns {string} The hash of the config */ function hashOfConfigFor(config) { - if (!configHashCache.has(config)) { - configHashCache.set(config, hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`)); - } - - return configHashCache.get(config); + if (!configHashCache.has(config)) { + configHashCache.set( + config, + hash(`${pkg.version}_${nodeVersion}_${stringify(config)}`), + ); + } + + return configHashCache.get(config); } //----------------------------------------------------------------------------- @@ -63,141 +64,139 @@ function hashOfConfigFor(config) { * serialize and adding them back in on retrieval. */ class LintResultCache { - - /** - * Creates a new LintResultCache instance. - * @param {string} cacheFileLocation The cache file location. - * @param {"metadata" | "content"} cacheStrategy The cache strategy to use. - */ - constructor(cacheFileLocation, cacheStrategy) { - assert(cacheFileLocation, "Cache file location is required"); - assert(cacheStrategy, "Cache strategy is required"); - assert( - isValidCacheStrategy(cacheStrategy), - invalidCacheStrategyErrorMessage - ); - - debug(`Caching results to ${cacheFileLocation}`); - - const useChecksum = cacheStrategy === "content"; - - debug( - `Using "${cacheStrategy}" strategy to detect changes` - ); - - this.fileEntryCache = fileEntryCache.create( - cacheFileLocation, - void 0, - useChecksum - ); - this.cacheFileLocation = cacheFileLocation; - } - - /** - * Retrieve cached lint results for a given file path, if present in the - * cache. If the file is present and has not been changed, rebuild any - * missing result information. - * @param {string} filePath The file for which to retrieve lint results. - * @param {ConfigArray} config The config of the file. - * @returns {Object|null} The rebuilt lint results, or null if the file is - * changed or not in the filesystem. - */ - getCachedLintResults(filePath, config) { - - /* - * Cached lint results are valid if and only if: - * 1. The file is present in the filesystem - * 2. The file has not changed since the time it was previously linted - * 3. The ESLint configuration has not changed since the time the file - * was previously linted - * If any of these are not true, we will not reuse the lint results. - */ - const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); - const hashOfConfig = hashOfConfigFor(config); - const changed = - fileDescriptor.changed || - fileDescriptor.meta.hashOfConfig !== hashOfConfig; - - if (fileDescriptor.notFound) { - debug(`File not found on the file system: ${filePath}`); - return null; - } - - if (changed) { - debug(`Cache entry not found or no longer valid: ${filePath}`); - return null; - } - - const cachedResults = fileDescriptor.meta.results; - - // Just in case, not sure if this can ever happen. - if (!cachedResults) { - return cachedResults; - } - - /* - * Shallow clone the object to ensure that any properties added or modified afterwards - * will not be accidentally stored in the cache file when `reconcile()` is called. - * https://github.com/eslint/eslint/issues/13507 - * All intentional changes to the cache file must be done through `setCachedLintResults()`. - */ - const results = { ...cachedResults }; - - // If source is present but null, need to reread the file from the filesystem. - if (results.source === null) { - debug(`Rereading cached result source from filesystem: ${filePath}`); - results.source = fs.readFileSync(filePath, "utf-8"); - } - - return results; - } - - /** - * Set the cached lint results for a given file path, after removing any - * information that will be both unnecessary and difficult to serialize. - * Avoids caching results with an "output" property (meaning fixes were - * applied), to prevent potentially incorrect results if fixes are not - * written to disk. - * @param {string} filePath The file for which to set lint results. - * @param {ConfigArray} config The config of the file. - * @param {Object} result The lint result to be set for the file. - * @returns {void} - */ - setCachedLintResults(filePath, config, result) { - if (result && Object.hasOwn(result, "output")) { - return; - } - - const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); - - if (fileDescriptor && !fileDescriptor.notFound) { - debug(`Updating cached result: ${filePath}`); - - // Serialize the result, except that we want to remove the file source if present. - const resultToSerialize = Object.assign({}, result); - - /* - * Set result.source to null. - * In `getCachedLintResults`, if source is explicitly null, we will - * read the file from the filesystem to set the value again. - */ - if (Object.hasOwn(resultToSerialize, "source")) { - resultToSerialize.source = null; - } - - fileDescriptor.meta.results = resultToSerialize; - fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config); - } - } - - /** - * Persists the in-memory cache to disk. - * @returns {void} - */ - reconcile() { - debug(`Persisting cached results: ${this.cacheFileLocation}`); - this.fileEntryCache.reconcile(); - } + /** + * Creates a new LintResultCache instance. + * @param {string} cacheFileLocation The cache file location. + * @param {"metadata" | "content"} cacheStrategy The cache strategy to use. + */ + constructor(cacheFileLocation, cacheStrategy) { + assert(cacheFileLocation, "Cache file location is required"); + assert(cacheStrategy, "Cache strategy is required"); + assert( + isValidCacheStrategy(cacheStrategy), + invalidCacheStrategyErrorMessage, + ); + + debug(`Caching results to ${cacheFileLocation}`); + + const useChecksum = cacheStrategy === "content"; + + debug(`Using "${cacheStrategy}" strategy to detect changes`); + + this.fileEntryCache = fileEntryCache.create( + cacheFileLocation, + void 0, + useChecksum, + ); + this.cacheFileLocation = cacheFileLocation; + } + + /** + * Retrieve cached lint results for a given file path, if present in the + * cache. If the file is present and has not been changed, rebuild any + * missing result information. + * @param {string} filePath The file for which to retrieve lint results. + * @param {ConfigArray} config The config of the file. + * @returns {Object|null} The rebuilt lint results, or null if the file is + * changed or not in the filesystem. + */ + getCachedLintResults(filePath, config) { + /* + * Cached lint results are valid if and only if: + * 1. The file is present in the filesystem + * 2. The file has not changed since the time it was previously linted + * 3. The ESLint configuration has not changed since the time the file + * was previously linted + * If any of these are not true, we will not reuse the lint results. + */ + const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); + const hashOfConfig = hashOfConfigFor(config); + const changed = + fileDescriptor.changed || + fileDescriptor.meta.hashOfConfig !== hashOfConfig; + + if (fileDescriptor.notFound) { + debug(`File not found on the file system: ${filePath}`); + return null; + } + + if (changed) { + debug(`Cache entry not found or no longer valid: ${filePath}`); + return null; + } + + const cachedResults = fileDescriptor.meta.results; + + // Just in case, not sure if this can ever happen. + if (!cachedResults) { + return cachedResults; + } + + /* + * Shallow clone the object to ensure that any properties added or modified afterwards + * will not be accidentally stored in the cache file when `reconcile()` is called. + * https://github.com/eslint/eslint/issues/13507 + * All intentional changes to the cache file must be done through `setCachedLintResults()`. + */ + const results = { ...cachedResults }; + + // If source is present but null, need to reread the file from the filesystem. + if (results.source === null) { + debug( + `Rereading cached result source from filesystem: ${filePath}`, + ); + results.source = fs.readFileSync(filePath, "utf-8"); + } + + return results; + } + + /** + * Set the cached lint results for a given file path, after removing any + * information that will be both unnecessary and difficult to serialize. + * Avoids caching results with an "output" property (meaning fixes were + * applied), to prevent potentially incorrect results if fixes are not + * written to disk. + * @param {string} filePath The file for which to set lint results. + * @param {ConfigArray} config The config of the file. + * @param {Object} result The lint result to be set for the file. + * @returns {void} + */ + setCachedLintResults(filePath, config, result) { + if (result && Object.hasOwn(result, "output")) { + return; + } + + const fileDescriptor = this.fileEntryCache.getFileDescriptor(filePath); + + if (fileDescriptor && !fileDescriptor.notFound) { + debug(`Updating cached result: ${filePath}`); + + // Serialize the result, except that we want to remove the file source if present. + const resultToSerialize = Object.assign({}, result); + + /* + * Set result.source to null. + * In `getCachedLintResults`, if source is explicitly null, we will + * read the file from the filesystem to set the value again. + */ + if (Object.hasOwn(resultToSerialize, "source")) { + resultToSerialize.source = null; + } + + fileDescriptor.meta.results = resultToSerialize; + fileDescriptor.meta.hashOfConfig = hashOfConfigFor(config); + } + } + + /** + * Persists the in-memory cache to disk. + * @returns {void} + */ + reconcile() { + debug(`Persisting cached results: ${this.cacheFileLocation}`); + this.fileEntryCache.reconcile(); + } } module.exports = LintResultCache; diff --git a/lib/cli-engine/load-rules.js b/lib/cli-engine/load-rules.js index a58f954e8684..5a7dc3498a7b 100644 --- a/lib/cli-engine/load-rules.js +++ b/lib/cli-engine/load-rules.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const fs = require("node:fs"), - path = require("node:path"); + path = require("node:path"); const rulesDirCache = {}; @@ -24,23 +24,23 @@ const rulesDirCache = {}; * @param {string} cwd Current working directory * @returns {Object} Loaded rule modules. */ -module.exports = function(relativeRulesDir, cwd) { - const rulesDir = path.resolve(cwd, relativeRulesDir); +module.exports = function (relativeRulesDir, cwd) { + const rulesDir = path.resolve(cwd, relativeRulesDir); - // cache will help performance as IO operation are expensive - if (rulesDirCache[rulesDir]) { - return rulesDirCache[rulesDir]; - } + // cache will help performance as IO operation are expensive + if (rulesDirCache[rulesDir]) { + return rulesDirCache[rulesDir]; + } - const rules = Object.create(null); + const rules = Object.create(null); - fs.readdirSync(rulesDir).forEach(file => { - if (path.extname(file) !== ".js") { - return; - } - rules[file.slice(0, -3)] = require(path.join(rulesDir, file)); - }); - rulesDirCache[rulesDir] = rules; + fs.readdirSync(rulesDir).forEach(file => { + if (path.extname(file) !== ".js") { + return; + } + rules[file.slice(0, -3)] = require(path.join(rulesDir, file)); + }); + rulesDirCache[rulesDir] = rules; - return rules; + return rules; }; diff --git a/lib/cli.js b/lib/cli.js index 1238efda7f5c..d66b76e7d112 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -16,15 +16,21 @@ //------------------------------------------------------------------------------ const fs = require("node:fs"), - path = require("node:path"), - { promisify } = require("node:util"), - { LegacyESLint } = require("./eslint"), - { ESLint, shouldUseFlatConfig, locateConfigFileToUse } = require("./eslint/eslint"), - createCLIOptions = require("./options"), - log = require("./shared/logging"), - RuntimeInfo = require("./shared/runtime-info"), - { normalizeSeverityToString } = require("./shared/severity"); -const { Legacy: { naming } } = require("@eslint/eslintrc"); + path = require("node:path"), + { promisify } = require("node:util"), + { LegacyESLint } = require("./eslint"), + { + ESLint, + shouldUseFlatConfig, + locateConfigFileToUse, + } = require("./eslint/eslint"), + createCLIOptions = require("./options"), + log = require("./shared/logging"), + RuntimeInfo = require("./shared/runtime-info"), + { normalizeSeverityToString } = require("./shared/severity"); +const { + Legacy: { naming }, +} = require("@eslint/eslintrc"); const { ModuleImporter } = require("@humanwhocodes/module-importer"); const debug = require("debug")("eslint:cli"); @@ -54,23 +60,32 @@ const writeFile = promisify(fs.writeFile); * @returns {Promise>} A mapping of plugin short names to implementations. */ async function loadPlugins(importer, pluginNames) { - const plugins = {}; - - await Promise.all(pluginNames.map(async pluginName => { - - const longName = naming.normalizePackageName(pluginName, "eslint-plugin"); - const module = await importer.import(longName); - - if (!("default" in module)) { - throw new Error(`"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`); - } - - const shortName = naming.getShorthandName(pluginName, "eslint-plugin"); - - plugins[shortName] = module.default; - })); - - return plugins; + const plugins = {}; + + await Promise.all( + pluginNames.map(async pluginName => { + const longName = naming.normalizePackageName( + pluginName, + "eslint-plugin", + ); + const module = await importer.import(longName); + + if (!("default" in module)) { + throw new Error( + `"${longName}" cannot be used with the \`--plugin\` option because its default module does not provide a \`default\` export`, + ); + } + + const shortName = naming.getShorthandName( + pluginName, + "eslint-plugin", + ); + + plugins[shortName] = module.default; + }), + ); + + return plugins; } /** @@ -81,7 +96,7 @@ async function loadPlugins(importer, pluginNames) { * autofixed), false otherwise. */ function quietFixPredicate(message) { - return message.severity === 2; + return message.severity === 2; } /** @@ -91,7 +106,7 @@ function quietFixPredicate(message) { * @returns {boolean} True if the lint rule should run, false otherwise. */ function quietRuleFilter(rule) { - return rule.severity === 2; + return rule.severity === 2; } /** @@ -102,165 +117,192 @@ function quietRuleFilter(rule) { * @returns {Promise} The options object for the ESLint constructor. * @private */ -async function translateOptions({ - cache, - cacheFile, - cacheLocation, - cacheStrategy, - config, - configLookup, - env, - errorOnUnmatchedPattern, - eslintrc, - ext, - fix, - fixDryRun, - fixType, - flag, - global, - ignore, - ignorePath, - ignorePattern, - inlineConfig, - parser, - parserOptions, - plugin, - quiet, - reportUnusedDisableDirectives, - reportUnusedDisableDirectivesSeverity, - reportUnusedInlineConfigs, - resolvePluginsRelativeTo, - rule, - rulesdir, - stats, - warnIgnored, - passOnNoPatterns, - maxWarnings -}, configType) { - - let overrideConfig, overrideConfigFile; - const importer = new ModuleImporter(); - - if (configType === "flat") { - overrideConfigFile = (typeof config === "string") ? config : !configLookup; - if (overrideConfigFile === false) { - overrideConfigFile = void 0; - } - - const languageOptions = {}; - - if (global) { - languageOptions.globals = global.reduce((obj, name) => { - if (name.endsWith(":true")) { - obj[name.slice(0, -5)] = "writable"; - } else { - obj[name] = "readonly"; - } - return obj; - }, {}); - } - - if (parserOptions) { - languageOptions.parserOptions = parserOptions; - } - - if (parser) { - languageOptions.parser = await importer.import(parser); - } - - overrideConfig = [{ - ...Object.keys(languageOptions).length > 0 ? { languageOptions } : {}, - rules: rule ? rule : {} - }]; - - if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) { - overrideConfig[0].linterOptions = { - reportUnusedDisableDirectives: reportUnusedDisableDirectives - ? "error" - : normalizeSeverityToString(reportUnusedDisableDirectivesSeverity) - }; - } - - if (reportUnusedInlineConfigs !== void 0) { - overrideConfig[0].linterOptions = { - ...overrideConfig[0].linterOptions, - reportUnusedInlineConfigs: normalizeSeverityToString(reportUnusedInlineConfigs) - }; - } - - if (plugin) { - overrideConfig[0].plugins = await loadPlugins(importer, plugin); - } - - if (ext) { - overrideConfig.push({ - files: ext.map(extension => `**/*${extension.startsWith(".") ? "" : "."}${extension}`) - }); - } - - } else { - overrideConfigFile = config; - - overrideConfig = { - env: env && env.reduce((obj, name) => { - obj[name] = true; - return obj; - }, {}), - globals: global && global.reduce((obj, name) => { - if (name.endsWith(":true")) { - obj[name.slice(0, -5)] = "writable"; - } else { - obj[name] = "readonly"; - } - return obj; - }, {}), - ignorePatterns: ignorePattern, - parser, - parserOptions, - plugins: plugin, - rules: rule - }; - } - - const options = { - allowInlineConfig: inlineConfig, - cache, - cacheLocation: cacheLocation || cacheFile, - cacheStrategy, - errorOnUnmatchedPattern, - fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true), - fixTypes: fixType, - ignore, - overrideConfig, - overrideConfigFile, - passOnNoPatterns - }; - - if (configType === "flat") { - options.ignorePatterns = ignorePattern; - options.stats = stats; - options.warnIgnored = warnIgnored; - options.flags = flag; - - /* - * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings - * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified. - */ - options.ruleFilter = quiet && maxWarnings === -1 ? quietRuleFilter : () => true; - } else { - options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; - options.rulePaths = rulesdir; - options.useEslintrc = eslintrc; - options.extensions = ext; - options.ignorePath = ignorePath; - if (reportUnusedDisableDirectives || reportUnusedDisableDirectivesSeverity !== void 0) { - options.reportUnusedDisableDirectives = reportUnusedDisableDirectives - ? "error" - : normalizeSeverityToString(reportUnusedDisableDirectivesSeverity); - } - } - - return options; +async function translateOptions( + { + cache, + cacheFile, + cacheLocation, + cacheStrategy, + config, + configLookup, + env, + errorOnUnmatchedPattern, + eslintrc, + ext, + fix, + fixDryRun, + fixType, + flag, + global, + ignore, + ignorePath, + ignorePattern, + inlineConfig, + parser, + parserOptions, + plugin, + quiet, + reportUnusedDisableDirectives, + reportUnusedDisableDirectivesSeverity, + reportUnusedInlineConfigs, + resolvePluginsRelativeTo, + rule, + rulesdir, + stats, + warnIgnored, + passOnNoPatterns, + maxWarnings, + }, + configType, +) { + let overrideConfig, overrideConfigFile; + const importer = new ModuleImporter(); + + if (configType === "flat") { + overrideConfigFile = + typeof config === "string" ? config : !configLookup; + if (overrideConfigFile === false) { + overrideConfigFile = void 0; + } + + const languageOptions = {}; + + if (global) { + languageOptions.globals = global.reduce((obj, name) => { + if (name.endsWith(":true")) { + obj[name.slice(0, -5)] = "writable"; + } else { + obj[name] = "readonly"; + } + return obj; + }, {}); + } + + if (parserOptions) { + languageOptions.parserOptions = parserOptions; + } + + if (parser) { + languageOptions.parser = await importer.import(parser); + } + + overrideConfig = [ + { + ...(Object.keys(languageOptions).length > 0 + ? { languageOptions } + : {}), + rules: rule ? rule : {}, + }, + ]; + + if ( + reportUnusedDisableDirectives || + reportUnusedDisableDirectivesSeverity !== void 0 + ) { + overrideConfig[0].linterOptions = { + reportUnusedDisableDirectives: reportUnusedDisableDirectives + ? "error" + : normalizeSeverityToString( + reportUnusedDisableDirectivesSeverity, + ), + }; + } + + if (reportUnusedInlineConfigs !== void 0) { + overrideConfig[0].linterOptions = { + ...overrideConfig[0].linterOptions, + reportUnusedInlineConfigs: normalizeSeverityToString( + reportUnusedInlineConfigs, + ), + }; + } + + if (plugin) { + overrideConfig[0].plugins = await loadPlugins(importer, plugin); + } + + if (ext) { + overrideConfig.push({ + files: ext.map( + extension => + `**/*${extension.startsWith(".") ? "" : "."}${extension}`, + ), + }); + } + } else { + overrideConfigFile = config; + + overrideConfig = { + env: + env && + env.reduce((obj, name) => { + obj[name] = true; + return obj; + }, {}), + globals: + global && + global.reduce((obj, name) => { + if (name.endsWith(":true")) { + obj[name.slice(0, -5)] = "writable"; + } else { + obj[name] = "readonly"; + } + return obj; + }, {}), + ignorePatterns: ignorePattern, + parser, + parserOptions, + plugins: plugin, + rules: rule, + }; + } + + const options = { + allowInlineConfig: inlineConfig, + cache, + cacheLocation: cacheLocation || cacheFile, + cacheStrategy, + errorOnUnmatchedPattern, + fix: (fix || fixDryRun) && (quiet ? quietFixPredicate : true), + fixTypes: fixType, + ignore, + overrideConfig, + overrideConfigFile, + passOnNoPatterns, + }; + + if (configType === "flat") { + options.ignorePatterns = ignorePattern; + options.stats = stats; + options.warnIgnored = warnIgnored; + options.flags = flag; + + /* + * For performance reasons rules not marked as 'error' are filtered out in quiet mode. As maxWarnings + * requires rules set to 'warn' to be run, we only filter out 'warn' rules if maxWarnings is not specified. + */ + options.ruleFilter = + quiet && maxWarnings === -1 ? quietRuleFilter : () => true; + } else { + options.resolvePluginsRelativeTo = resolvePluginsRelativeTo; + options.rulePaths = rulesdir; + options.useEslintrc = eslintrc; + options.extensions = ext; + options.ignorePath = ignorePath; + if ( + reportUnusedDisableDirectives || + reportUnusedDisableDirectivesSeverity !== void 0 + ) { + options.reportUnusedDisableDirectives = + reportUnusedDisableDirectives + ? "error" + : normalizeSeverityToString( + reportUnusedDisableDirectivesSeverity, + ); + } + } + + return options; } /** @@ -269,17 +311,17 @@ async function translateOptions({ * @returns {{errorCount:number;fatalErrorCount:number,warningCount:number}} The number of error messages. */ function countErrors(results) { - let errorCount = 0; - let fatalErrorCount = 0; - let warningCount = 0; + let errorCount = 0; + let fatalErrorCount = 0; + let warningCount = 0; - for (const result of results) { - errorCount += result.errorCount; - fatalErrorCount += result.fatalErrorCount; - warningCount += result.warningCount; - } + for (const result of results) { + errorCount += result.errorCount; + fatalErrorCount += result.fatalErrorCount; + warningCount += result.warningCount; + } - return { errorCount, fatalErrorCount, warningCount }; + return { errorCount, fatalErrorCount, warningCount }; } /** @@ -288,14 +330,14 @@ function countErrors(results) { * @returns {Promise} `true` if the given path is a directory. */ async function isDirectory(filePath) { - try { - return (await stat(filePath)).isDirectory(); - } catch (error) { - if (error.code === "ENOENT" || error.code === "ENOTDIR") { - return false; - } - throw error; - } + try { + return (await stat(filePath)).isDirectory(); + } catch (error) { + if (error.code === "ENOENT" || error.code === "ENOTDIR") { + return false; + } + throw error; + } } /** @@ -309,37 +351,40 @@ async function isDirectory(filePath) { * @private */ async function printResults(engine, results, format, outputFile, resultsMeta) { - let formatter; - - try { - formatter = await engine.loadFormatter(format); - } catch (e) { - log.error(e.message); - return false; - } - - const output = await formatter.format(results, resultsMeta); - - if (outputFile) { - const filePath = path.resolve(process.cwd(), outputFile); - - if (await isDirectory(filePath)) { - log.error("Cannot write to output file path, it is a directory: %s", outputFile); - return false; - } - - try { - await mkdir(path.dirname(filePath), { recursive: true }); - await writeFile(filePath, output); - } catch (ex) { - log.error("There was a problem writing the output file:\n%s", ex); - return false; - } - } else if (output) { - log.info(output); - } - - return true; + let formatter; + + try { + formatter = await engine.loadFormatter(format); + } catch (e) { + log.error(e.message); + return false; + } + + const output = await formatter.format(results, resultsMeta); + + if (outputFile) { + const filePath = path.resolve(process.cwd(), outputFile); + + if (await isDirectory(filePath)) { + log.error( + "Cannot write to output file path, it is a directory: %s", + outputFile, + ); + return false; + } + + try { + await mkdir(path.dirname(filePath), { recursive: true }); + await writeFile(filePath, output); + } catch (ex) { + log.error("There was a problem writing the output file:\n%s", ex); + return false; + } + } else if (output) { + log.info(output); + } + + return true; } //------------------------------------------------------------------------------ @@ -351,228 +396,267 @@ async function printResults(engine, results, format, outputFile, resultsMeta) { * for other Node.js programs to effectively run the CLI. */ const cli = { - - /** - * Calculates the command string for the --inspect-config operation. - * @param {string} configFile The path to the config file to inspect. - * @returns {Promise} The command string to execute. - */ - async calculateInspectConfigFlags(configFile) { - - // find the config file - const { - configFilePath, - basePath - } = await locateConfigFileToUse({ cwd: process.cwd(), configFile }); - - return ["--config", configFilePath, "--basePath", basePath]; - }, - - /** - * Executes the CLI based on an array of arguments that is passed in. - * @param {string|Array|Object} args The arguments to process. - * @param {string} [text] The text to lint (used for TTY). - * @param {boolean} [allowFlatConfig=true] Whether or not to allow flat config. - * @returns {Promise} The exit code for the operation. - */ - async execute(args, text, allowFlatConfig = true) { - if (Array.isArray(args)) { - debug("CLI args: %o", args.slice(2)); - } - - /* - * Before doing anything, we need to see if we are using a - * flat config file. If so, then we need to change the way command - * line args are parsed. This is temporary, and when we fully - * switch to flat config we can remove this logic. - */ - - const usingFlatConfig = allowFlatConfig && await shouldUseFlatConfig(); - - debug("Using flat config?", usingFlatConfig); - - if (allowFlatConfig && !usingFlatConfig) { - process.emitWarning("You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.", "ESLintRCWarning"); - } - - const CLIOptions = createCLIOptions(usingFlatConfig); - - /** @type {ParsedCLIOptions} */ - let options; - - try { - options = CLIOptions.parse(args); - } catch (error) { - debug("Error parsing CLI options:", error.message); - - let errorMessage = error.message; - - if (usingFlatConfig) { - errorMessage += "\nYou'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."; - } - - log.error(errorMessage); - return 2; - } - - const files = options._; - const useStdin = typeof text === "string"; - - if (options.help) { - log.info(CLIOptions.generateHelp()); - return 0; - } - if (options.version) { - log.info(RuntimeInfo.version()); - return 0; - } - if (options.envInfo) { - try { - log.info(RuntimeInfo.environment()); - return 0; - } catch (err) { - debug("Error retrieving environment info"); - log.error(err.message); - return 2; - } - } - - if (options.printConfig) { - if (files.length) { - log.error("The --print-config option must be used with exactly one file name."); - return 2; - } - if (useStdin) { - log.error("The --print-config option is not available for piped-in code."); - return 2; - } - - const engine = usingFlatConfig - ? new ESLint(await translateOptions(options, "flat")) - : new LegacyESLint(await translateOptions(options)); - const fileConfig = - await engine.calculateConfigForFile(options.printConfig); - - log.info(JSON.stringify(fileConfig, null, " ")); - return 0; - } - - if (options.inspectConfig) { - - log.info("You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file."); - - try { - const flatOptions = await translateOptions(options, "flat"); - const spawn = require("cross-spawn"); - const flags = await cli.calculateInspectConfigFlags(flatOptions.overrideConfigFile); - - spawn.sync("npx", ["@eslint/config-inspector@latest", ...flags], { encoding: "utf8", stdio: "inherit" }); - } catch (error) { - log.error(error); - return 2; - } - - return 0; - } - - debug(`Running on ${useStdin ? "text" : "files"}`); - - if (options.fix && options.fixDryRun) { - log.error("The --fix option and the --fix-dry-run option cannot be used together."); - return 2; - } - if (useStdin && options.fix) { - log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead."); - return 2; - } - if (options.fixType && !options.fix && !options.fixDryRun) { - log.error("The --fix-type option requires either --fix or --fix-dry-run."); - return 2; - } - - if (options.reportUnusedDisableDirectives && options.reportUnusedDisableDirectivesSeverity !== void 0) { - log.error("The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together."); - return 2; - } - - if (usingFlatConfig && options.ext) { - - // Passing `--ext ""` results in `options.ext` being an empty array. - if (options.ext.length === 0) { - log.error("The --ext option value cannot be empty."); - return 2; - } - - // Passing `--ext ,ts` results in an empty string at index 0. Passing `--ext ts,,tsx` results in an empty string at index 1. - const emptyStringIndex = options.ext.indexOf(""); - - if (emptyStringIndex >= 0) { - log.error(`The --ext option arguments cannot be empty strings. Found an empty string at index ${emptyStringIndex}.`); - return 2; - } - } - - const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint; - const eslintOptions = await translateOptions(options, usingFlatConfig ? "flat" : "eslintrc"); - const engine = new ActiveESLint(eslintOptions); - let results; - - if (useStdin) { - results = await engine.lintText(text, { - filePath: options.stdinFilename, - - // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility - warnIgnored: usingFlatConfig ? void 0 : true - }); - } else { - results = await engine.lintFiles(files); - } - - if (options.fix) { - debug("Fix mode enabled - applying fixes"); - await ActiveESLint.outputFixes(results); - } - - let resultsToPrint = results; - - if (options.quiet) { - debug("Quiet mode enabled - filtering out warnings"); - resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); - } - - const resultCounts = countErrors(results); - const tooManyWarnings = options.maxWarnings >= 0 && resultCounts.warningCount > options.maxWarnings; - const resultsMeta = tooManyWarnings - ? { - maxWarningsExceeded: { - maxWarnings: options.maxWarnings, - foundWarnings: resultCounts.warningCount - } - } - : {}; - - if (await printResults(engine, resultsToPrint, options.format, options.outputFile, resultsMeta)) { - - // Errors and warnings from the original unfiltered results should determine the exit code - const shouldExitForFatalErrors = - options.exitOnFatalError && resultCounts.fatalErrorCount > 0; - - if (!resultCounts.errorCount && tooManyWarnings) { - log.error( - "ESLint found too many warnings (maximum: %s).", - options.maxWarnings - ); - } - - if (shouldExitForFatalErrors) { - return 2; - } - - return (resultCounts.errorCount || tooManyWarnings) ? 1 : 0; - } - - return 2; - } + /** + * Calculates the command string for the --inspect-config operation. + * @param {string} configFile The path to the config file to inspect. + * @returns {Promise} The command string to execute. + */ + async calculateInspectConfigFlags(configFile) { + // find the config file + const { configFilePath, basePath } = await locateConfigFileToUse({ + cwd: process.cwd(), + configFile, + }); + + return ["--config", configFilePath, "--basePath", basePath]; + }, + + /** + * Executes the CLI based on an array of arguments that is passed in. + * @param {string|Array|Object} args The arguments to process. + * @param {string} [text] The text to lint (used for TTY). + * @param {boolean} [allowFlatConfig=true] Whether or not to allow flat config. + * @returns {Promise} The exit code for the operation. + */ + async execute(args, text, allowFlatConfig = true) { + if (Array.isArray(args)) { + debug("CLI args: %o", args.slice(2)); + } + + /* + * Before doing anything, we need to see if we are using a + * flat config file. If so, then we need to change the way command + * line args are parsed. This is temporary, and when we fully + * switch to flat config we can remove this logic. + */ + + const usingFlatConfig = + allowFlatConfig && (await shouldUseFlatConfig()); + + debug("Using flat config?", usingFlatConfig); + + if (allowFlatConfig && !usingFlatConfig) { + process.emitWarning( + "You are using an eslintrc configuration file, which is deprecated and support will be removed in v10.0.0. Please migrate to an eslint.config.js file. See https://eslint.org/docs/latest/use/configure/migration-guide for details. An eslintrc configuration file is used because you have the ESLINT_USE_FLAT_CONFIG environment variable set to false. If you want to use an eslint.config.js file, remove the environment variable. If you want to find the location of the eslintrc configuration file, use the --debug flag.", + "ESLintRCWarning", + ); + } + + const CLIOptions = createCLIOptions(usingFlatConfig); + + /** @type {ParsedCLIOptions} */ + let options; + + try { + options = CLIOptions.parse(args); + } catch (error) { + debug("Error parsing CLI options:", error.message); + + let errorMessage = error.message; + + if (usingFlatConfig) { + errorMessage += + "\nYou'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."; + } + + log.error(errorMessage); + return 2; + } + + const files = options._; + const useStdin = typeof text === "string"; + + if (options.help) { + log.info(CLIOptions.generateHelp()); + return 0; + } + if (options.version) { + log.info(RuntimeInfo.version()); + return 0; + } + if (options.envInfo) { + try { + log.info(RuntimeInfo.environment()); + return 0; + } catch (err) { + debug("Error retrieving environment info"); + log.error(err.message); + return 2; + } + } + + if (options.printConfig) { + if (files.length) { + log.error( + "The --print-config option must be used with exactly one file name.", + ); + return 2; + } + if (useStdin) { + log.error( + "The --print-config option is not available for piped-in code.", + ); + return 2; + } + + const engine = usingFlatConfig + ? new ESLint(await translateOptions(options, "flat")) + : new LegacyESLint(await translateOptions(options)); + const fileConfig = await engine.calculateConfigForFile( + options.printConfig, + ); + + log.info(JSON.stringify(fileConfig, null, " ")); + return 0; + } + + if (options.inspectConfig) { + log.info( + "You can also run this command directly using 'npx @eslint/config-inspector@latest' in the same directory as your configuration file.", + ); + + try { + const flatOptions = await translateOptions(options, "flat"); + const spawn = require("cross-spawn"); + const flags = await cli.calculateInspectConfigFlags( + flatOptions.overrideConfigFile, + ); + + spawn.sync( + "npx", + ["@eslint/config-inspector@latest", ...flags], + { encoding: "utf8", stdio: "inherit" }, + ); + } catch (error) { + log.error(error); + return 2; + } + + return 0; + } + + debug(`Running on ${useStdin ? "text" : "files"}`); + + if (options.fix && options.fixDryRun) { + log.error( + "The --fix option and the --fix-dry-run option cannot be used together.", + ); + return 2; + } + if (useStdin && options.fix) { + log.error( + "The --fix option is not available for piped-in code; use --fix-dry-run instead.", + ); + return 2; + } + if (options.fixType && !options.fix && !options.fixDryRun) { + log.error( + "The --fix-type option requires either --fix or --fix-dry-run.", + ); + return 2; + } + + if ( + options.reportUnusedDisableDirectives && + options.reportUnusedDisableDirectivesSeverity !== void 0 + ) { + log.error( + "The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together.", + ); + return 2; + } + + if (usingFlatConfig && options.ext) { + // Passing `--ext ""` results in `options.ext` being an empty array. + if (options.ext.length === 0) { + log.error("The --ext option value cannot be empty."); + return 2; + } + + // Passing `--ext ,ts` results in an empty string at index 0. Passing `--ext ts,,tsx` results in an empty string at index 1. + const emptyStringIndex = options.ext.indexOf(""); + + if (emptyStringIndex >= 0) { + log.error( + `The --ext option arguments cannot be empty strings. Found an empty string at index ${emptyStringIndex}.`, + ); + return 2; + } + } + + const ActiveESLint = usingFlatConfig ? ESLint : LegacyESLint; + const eslintOptions = await translateOptions( + options, + usingFlatConfig ? "flat" : "eslintrc", + ); + const engine = new ActiveESLint(eslintOptions); + let results; + + if (useStdin) { + results = await engine.lintText(text, { + filePath: options.stdinFilename, + + // flatConfig respects CLI flag and constructor warnIgnored, eslintrc forces true for backwards compatibility + warnIgnored: usingFlatConfig ? void 0 : true, + }); + } else { + results = await engine.lintFiles(files); + } + + if (options.fix) { + debug("Fix mode enabled - applying fixes"); + await ActiveESLint.outputFixes(results); + } + + let resultsToPrint = results; + + if (options.quiet) { + debug("Quiet mode enabled - filtering out warnings"); + resultsToPrint = ActiveESLint.getErrorResults(resultsToPrint); + } + + const resultCounts = countErrors(results); + const tooManyWarnings = + options.maxWarnings >= 0 && + resultCounts.warningCount > options.maxWarnings; + const resultsMeta = tooManyWarnings + ? { + maxWarningsExceeded: { + maxWarnings: options.maxWarnings, + foundWarnings: resultCounts.warningCount, + }, + } + : {}; + + if ( + await printResults( + engine, + resultsToPrint, + options.format, + options.outputFile, + resultsMeta, + ) + ) { + // Errors and warnings from the original unfiltered results should determine the exit code + const shouldExitForFatalErrors = + options.exitOnFatalError && resultCounts.fatalErrorCount > 0; + + if (!resultCounts.errorCount && tooManyWarnings) { + log.error( + "ESLint found too many warnings (maximum: %s).", + options.maxWarnings, + ); + } + + if (shouldExitForFatalErrors) { + return 2; + } + + return resultCounts.errorCount || tooManyWarnings ? 1 : 0; + } + + return 2; + }, }; module.exports = cli; diff --git a/lib/config-api.js b/lib/config-api.js index e3895d1d5074..5050a631cfb0 100644 --- a/lib/config-api.js +++ b/lib/config-api.js @@ -7,6 +7,6 @@ const { defineConfig, globalIgnores } = require("@eslint/config-helpers"); module.exports = { - defineConfig, - globalIgnores + defineConfig, + globalIgnores, }; diff --git a/lib/config/config-loader.js b/lib/config/config-loader.js index 0f4a3d9b144e..667c2d67d882 100644 --- a/lib/config/config-loader.js +++ b/lib/config/config-loader.js @@ -38,12 +38,12 @@ const { FlatConfigArray } = require("./flat-config-array"); //------------------------------------------------------------------------------ const FLAT_CONFIG_FILENAMES = [ - "eslint.config.js", - "eslint.config.mjs", - "eslint.config.cjs", - "eslint.config.ts", - "eslint.config.mts", - "eslint.config.cts" + "eslint.config.js", + "eslint.config.mjs", + "eslint.config.cjs", + "eslint.config.ts", + "eslint.config.mts", + "eslint.config.cts", ]; const importedConfigFileModificationTime = new Map(); @@ -55,9 +55,9 @@ const importedConfigFileModificationTime = new Map(); * @throws {Error} If `filePath` is not a non-empty string. */ function assertValidFilePath(filePath) { - if (!filePath || typeof filePath !== "string") { - throw new Error("'filePath' must be a non-empty string"); - } + if (!filePath || typeof filePath !== "string") { + throw new Error("'filePath' must be a non-empty string"); + } } /** @@ -71,18 +71,14 @@ function assertValidFilePath(filePath) { * @throws {Error} If no configuration exists. */ function assertConfigurationExists(configFilePath, loaderOptions) { + const { configFile: useConfigFile } = loaderOptions; - const { - configFile: useConfigFile - } = loaderOptions; - - if (!configFilePath && useConfigFile !== false) { - const error = new Error("Could not find config file."); - - error.messageTemplate = "config-file-missing"; - throw error; - } + if (!configFilePath && useConfigFile !== false) { + const error = new Error("Could not find config file."); + error.messageTemplate = "config-file-missing"; + throw error; + } } /** @@ -91,9 +87,9 @@ function assertConfigurationExists(configFilePath, loaderOptions) { * @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not. */ function isFileTS(filePath) { - const fileExtension = path.extname(filePath); + const fileExtension = path.extname(filePath); - return /^\.[mc]?ts$/u.test(fileExtension); + return /^\.[mc]?ts$/u.test(fileExtension); } /** @@ -101,7 +97,7 @@ function isFileTS(filePath) { * @returns {boolean} `true` if the ESLint is running Bun, `false` if it's not. */ function isRunningInBun() { - return !!globalThis.Bun; + return !!globalThis.Bun; } /** @@ -109,7 +105,7 @@ function isRunningInBun() { * @returns {boolean} `true` if the ESLint is running in Deno, `false` if it's not. */ function isRunningInDeno() { - return !!globalThis.Deno; + return !!globalThis.Deno; } /** @@ -118,87 +114,91 @@ function isRunningInDeno() { * @returns {Promise} The config loaded from the config file. */ async function loadConfigFile(filePath) { - - debug(`Loading config from ${filePath}`); - - const fileURL = pathToFileURL(filePath); - - debug(`Config file URL is ${fileURL}`); - - const mtime = (await fs.stat(filePath)).mtime.getTime(); - - /* - * Append a query with the config file's modification time (`mtime`) in order - * to import the current version of the config file. Without the query, `import()` would - * cache the config file module by the pathname only, and then always return - * the same version (the one that was actual when the module was imported for the first time). - * - * This ensures that the config file module is loaded and executed again - * if it has been changed since the last time it was imported. - * If it hasn't been changed, `import()` will just return the cached version. - * - * Note that we should not overuse queries (e.g., by appending the current time - * to always reload the config file module) as that could cause memory leaks - * because entries are never removed from the import cache. - */ - fileURL.searchParams.append("mtime", mtime); - - /* - * With queries, we can bypass the import cache. However, when import-ing a CJS module, - * Node.js uses the require infrastructure under the hood. That includes the require cache, - * which caches the config file module by its file path (queries have no effect). - * Therefore, we also need to clear the require cache before importing the config file module. - * In order to get the same behavior with ESM and CJS config files, in particular - to reload - * the config file only if it has been changed, we track file modification times and clear - * the require cache only if the file has been changed. - */ - if (importedConfigFileModificationTime.get(filePath) !== mtime) { - delete require.cache[filePath]; - } - - const isTS = isFileTS(filePath); - const isBun = isRunningInBun(); - const isDeno = isRunningInDeno(); - - /* - * If we are dealing with a TypeScript file, then we need to use `jiti` to load it - * in Node.js. Deno and Bun both allow native importing of TypeScript files. - * - * When Node.js supports native TypeScript imports, we can remove this check. - */ - if (isTS && !isDeno && !isBun) { - - // eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing - const { createJiti } = await ConfigLoader.loadJiti().catch(() => { - throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it."); - }); - - // `createJiti` was added in jiti v2. - if (typeof createJiti !== "function") { - throw new Error("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."); - } - - /* - * Disabling `moduleCache` allows us to reload a - * config file when the last modified timestamp changes. - */ - - const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false }); - const config = await jiti.import(fileURL.href); - - importedConfigFileModificationTime.set(filePath, mtime); - - return config?.default ?? config; - } - - - // fallback to normal runtime behavior - - const config = (await import(fileURL)).default; - - importedConfigFileModificationTime.set(filePath, mtime); - - return config; + debug(`Loading config from ${filePath}`); + + const fileURL = pathToFileURL(filePath); + + debug(`Config file URL is ${fileURL}`); + + const mtime = (await fs.stat(filePath)).mtime.getTime(); + + /* + * Append a query with the config file's modification time (`mtime`) in order + * to import the current version of the config file. Without the query, `import()` would + * cache the config file module by the pathname only, and then always return + * the same version (the one that was actual when the module was imported for the first time). + * + * This ensures that the config file module is loaded and executed again + * if it has been changed since the last time it was imported. + * If it hasn't been changed, `import()` will just return the cached version. + * + * Note that we should not overuse queries (e.g., by appending the current time + * to always reload the config file module) as that could cause memory leaks + * because entries are never removed from the import cache. + */ + fileURL.searchParams.append("mtime", mtime); + + /* + * With queries, we can bypass the import cache. However, when import-ing a CJS module, + * Node.js uses the require infrastructure under the hood. That includes the require cache, + * which caches the config file module by its file path (queries have no effect). + * Therefore, we also need to clear the require cache before importing the config file module. + * In order to get the same behavior with ESM and CJS config files, in particular - to reload + * the config file only if it has been changed, we track file modification times and clear + * the require cache only if the file has been changed. + */ + if (importedConfigFileModificationTime.get(filePath) !== mtime) { + delete require.cache[filePath]; + } + + const isTS = isFileTS(filePath); + const isBun = isRunningInBun(); + const isDeno = isRunningInDeno(); + + /* + * If we are dealing with a TypeScript file, then we need to use `jiti` to load it + * in Node.js. Deno and Bun both allow native importing of TypeScript files. + * + * When Node.js supports native TypeScript imports, we can remove this check. + */ + if (isTS && !isDeno && !isBun) { + // eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing + const { createJiti } = await ConfigLoader.loadJiti().catch(() => { + throw new Error( + "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.", + ); + }); + + // `createJiti` was added in jiti v2. + if (typeof createJiti !== "function") { + throw new Error( + "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.", + ); + } + + /* + * Disabling `moduleCache` allows us to reload a + * config file when the last modified timestamp changes. + */ + + const jiti = createJiti(__filename, { + moduleCache: false, + interopDefault: false, + }); + const config = await jiti.import(fileURL.href); + + importedConfigFileModificationTime.set(filePath, mtime); + + return config?.default ?? config; + } + + // fallback to normal runtime behavior + + const config = (await import(fileURL)).default; + + importedConfigFileModificationTime.set(filePath, mtime); + + return config; } //----------------------------------------------------------------------------- @@ -210,385 +210,411 @@ async function loadConfigFile(filePath) { * from the file being linted. */ class ConfigLoader { - - /** - * Map of config file paths to the config arrays for those directories. - * @type {Map>} - */ - #configArrays = new Map(); - - /** - * Map of absolute directory names to the config file paths for those directories. - * @type {Map>} - */ - #configFilePaths = new Map(); - - /** - * The options to use when loading configuration files. - * @type {ConfigLoaderOptions} - */ - #options; - - /** - * Creates a new instance. - * @param {ConfigLoaderOptions} options The options to use when loading configuration files. - */ - constructor(options) { - this.#options = options; - } - - /** - * Determines which config file to use. This is determined by seeing if an - * override config file was specified, and if so, using it; otherwise, as long - * as override config file is not explicitly set to `false`, it will search - * upwards from `fromDirectory` for a file named `eslint.config.js`. - * @param {string} fromDirectory The directory from which to start searching. - * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for - * the config file. - */ - async #locateConfigFileToUse(fromDirectory) { - - // check cache first - if (this.#configFilePaths.has(fromDirectory)) { - return this.#configFilePaths.get(fromDirectory); - } - - const resultPromise = ConfigLoader.locateConfigFileToUse({ - useConfigFile: this.#options.configFile, - cwd: this.#options.cwd, - fromDirectory - }); - - // ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory` - this.#configFilePaths.set(fromDirectory, resultPromise); - - // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. - const result = await resultPromise; - - this.#configFilePaths.set(fromDirectory, result); - - return result; - } - - /** - * Calculates the config array for this run based on inputs. - * @param {string} configFilePath The absolute path to the config file to use if not overridden. - * @param {string} basePath The base path to use for relative paths in the config file. - * @returns {Promise} The config array for `eslint`. - */ - async #calculateConfigArray(configFilePath, basePath) { - - // check for cached version first - if (this.#configArrays.has(configFilePath)) { - return this.#configArrays.get(configFilePath); - } - - const configsPromise = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options); - - // ensure `ConfigLoader.calculateConfigArray` is called only once for `configFilePath` - this.#configArrays.set(configFilePath, configsPromise); - - // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. - const configs = await configsPromise; - - this.#configArrays.set(configFilePath, configs); - - return configs; - } - - /** - * Returns the config file path for the given directory or file. This will either use - * the override config file that was specified in the constructor options or - * search for a config file from the directory. - * @param {string} fileOrDirPath The file or directory path to get the config file path for. - * @returns {Promise} The config file path or `undefined` if not found. - * @throws {Error} If `fileOrDirPath` is not a non-empty string. - * @throws {Error} If `fileOrDirPath` is not an absolute path. - */ - async findConfigFileForPath(fileOrDirPath) { - - assertValidFilePath(fileOrDirPath); - - const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(fileOrDirPath)); - const { configFilePath } = await this.#locateConfigFileToUse(absoluteDirPath); - - return configFilePath; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file or directory to retrieve config for. - * @returns {Promise} A configuration object for the file - * or `undefined` if there is no configuration data for the file. - * @throws {Error} If no configuration for `filePath` exists. - */ - async loadConfigArrayForFile(filePath) { - - assertValidFilePath(filePath); - - debug(`Calculating config for file ${filePath}`); - - const configFilePath = await this.findConfigFileForPath(filePath); - - assertConfigurationExists(configFilePath, this.#options); - - return this.loadConfigArrayForDirectory(filePath); - } - - /** - * Returns a configuration object for the given directory based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} dirPath The path of the directory to retrieve config for. - * @returns {Promise} A configuration object for the directory - * or `undefined` if there is no configuration data for the directory. - */ - async loadConfigArrayForDirectory(dirPath) { - - assertValidFilePath(dirPath); - - debug(`Calculating config for directory ${dirPath}`); - - const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(dirPath)); - const { configFilePath, basePath } = await this.#locateConfigFileToUse(absoluteDirPath); - - debug(`Using config file ${configFilePath} and base path ${basePath}`); - return this.#calculateConfigArray(configFilePath, basePath); - } - - /** - * Returns a configuration array for the given file based on the CLI options. - * This is a synchronous operation and does not read any files from disk. It's - * intended to be used in locations where we know the config file has already - * been loaded and we just need to get the configuration for a file. - * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {ConfigData|undefined} A configuration object for the file - * or `undefined` if there is no configuration data for the file. - * @throws {Error} If `filePath` is not a non-empty string. - * @throws {Error} If `filePath` is not an absolute path. - * @throws {Error} If the config file was not already loaded. - */ - getCachedConfigArrayForFile(filePath) { - assertValidFilePath(filePath); - - debug(`Looking up cached config for ${filePath}`); - - return this.getCachedConfigArrayForPath(path.dirname(filePath)); - } - - /** - * Returns a configuration array for the given directory based on the CLI options. - * This is a synchronous operation and does not read any files from disk. It's - * intended to be used in locations where we know the config file has already - * been loaded and we just need to get the configuration for a file. - * @param {string} fileOrDirPath The path of the directory to retrieve a config object for. - * @returns {ConfigData|undefined} A configuration object for the directory - * or `undefined` if there is no configuration data for the directory. - * @throws {Error} If `dirPath` is not a non-empty string. - * @throws {Error} If `dirPath` is not an absolute path. - * @throws {Error} If the config file was not already loaded. - */ - getCachedConfigArrayForPath(fileOrDirPath) { - assertValidFilePath(fileOrDirPath); - - debug(`Looking up cached config for ${fileOrDirPath}`); - - const absoluteDirPath = path.resolve(this.#options.cwd, fileOrDirPath); - - if (!this.#configFilePaths.has(absoluteDirPath)) { - throw new Error(`Could not find config file for ${fileOrDirPath}`); - } - - const configFilePathInfo = this.#configFilePaths.get(absoluteDirPath); - - if (typeof configFilePathInfo.then === "function") { - throw new Error(`Config file path for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`); - } - - const { configFilePath } = configFilePathInfo; - - const configArray = this.#configArrays.get(configFilePath); - - if (!configArray || typeof configArray.then === "function") { - throw new Error(`Config array for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`); - } - - return configArray; - } - - /** - * Used to import the jiti dependency. This method is exposed internally for testing purposes. - * @returns {Promise>} A promise that fulfills with a module object - * or rejects with an error if jiti is not found. - */ - static loadJiti() { - return import("jiti"); - } - - /** - * Determines which config file to use. This is determined by seeing if an - * override config file was specified, and if so, using it; otherwise, as long - * as override config file is not explicitly set to `false`, it will search - * upwards from `fromDirectory` for a file named `eslint.config.js`. - * This method is exposed internally for testing purposes. - * @param {Object} [options] the options object - * @param {string|false|undefined} options.useConfigFile The path to the config file to use. - * @param {string} options.cwd Path to a directory that should be considered as the current working directory. - * @param {string} [options.fromDirectory] The directory from which to start searching. Defaults to `cwd`. - * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for - * the config file. - */ - static async locateConfigFileToUse({ useConfigFile, cwd, fromDirectory = cwd }) { - - // determine where to load config file from - let configFilePath; - let basePath = cwd; - - if (typeof useConfigFile === "string") { - debug(`Override config file path is ${useConfigFile}`); - configFilePath = path.resolve(cwd, useConfigFile); - basePath = cwd; - } else if (useConfigFile !== false) { - debug("Searching for eslint.config.js"); - configFilePath = await findUp( - FLAT_CONFIG_FILENAMES, - { cwd: fromDirectory } - ); - - if (configFilePath) { - basePath = path.dirname(configFilePath); - } - - } - - return { - configFilePath, - basePath - }; - - } - - /** - * Calculates the config array for this run based on inputs. - * This method is exposed internally for testing purposes. - * @param {string} configFilePath The absolute path to the config file to use if not overridden. - * @param {string} basePath The base path to use for relative paths in the config file. - * @param {ConfigLoaderOptions} options The options to use when loading configuration files. - * @returns {Promise} The config array for `eslint`. - */ - static async calculateConfigArray(configFilePath, basePath, options) { - - const { - cwd, - baseConfig, - ignoreEnabled, - ignorePatterns, - overrideConfig, - defaultConfigs = [] - } = options; - - debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`); - - const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled }); - - // load config file - if (configFilePath) { - - debug(`Loading config file ${configFilePath}`); - const fileConfig = await loadConfigFile(configFilePath); - - /* - * It's possible that a config file could be empty or else - * have an empty object or array. In this case, we want to - * warn the user that they have an empty config. - * - * An empty CommonJS file exports an empty object while - * an empty ESM file exports undefined. - */ - - let emptyConfig = typeof fileConfig === "undefined"; - - debug(`Config file ${configFilePath} is ${emptyConfig ? "empty" : "not empty"}`); - - if (!emptyConfig) { - if (Array.isArray(fileConfig)) { - if (fileConfig.length === 0) { - debug(`Config file ${configFilePath} is an empty array`); - emptyConfig = true; - } else { - configs.push(...fileConfig); - } - } else { - if (typeof fileConfig === "object" && fileConfig !== null && Object.keys(fileConfig).length === 0) { - debug(`Config file ${configFilePath} is an empty object`); - emptyConfig = true; - } else { - configs.push(fileConfig); - } - } - } - - if (emptyConfig) { - globalThis.process?.emitWarning?.(`Running ESLint with an empty config (from ${configFilePath}). Please double-check that this is what you want. If you want to run ESLint with an empty config, export [{}] to remove this warning.`, "ESLintEmptyConfigWarning"); - } - - } - - // add in any configured defaults - configs.push(...defaultConfigs); - - // append command line ignore patterns - if (ignorePatterns && ignorePatterns.length > 0) { - - let relativeIgnorePatterns; - - /* - * If the config file basePath is different than the cwd, then - * the ignore patterns won't work correctly. Here, we adjust the - * ignore pattern to include the correct relative path. Patterns - * passed as `ignorePatterns` are relative to the cwd, whereas - * the config file basePath can be an ancestor of the cwd. - */ - if (basePath === cwd) { - relativeIgnorePatterns = ignorePatterns; - } else { - - // relative path must only have Unix-style separators - const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/"); - - relativeIgnorePatterns = ignorePatterns.map(pattern => { - const negated = pattern.startsWith("!"); - const basePattern = negated ? pattern.slice(1) : pattern; - - return (negated ? "!" : "") + - path.posix.join(relativeIgnorePath, basePattern); - }); - } - - /* - * Ignore patterns are added to the end of the config array - * so they can override default ignores. - */ - configs.push({ - ignores: relativeIgnorePatterns - }); - } - - if (overrideConfig) { - if (Array.isArray(overrideConfig)) { - configs.push(...overrideConfig); - } else { - configs.push(overrideConfig); - } - } - - await configs.normalize(); - - return configs; - } - + /** + * Map of config file paths to the config arrays for those directories. + * @type {Map>} + */ + #configArrays = new Map(); + + /** + * Map of absolute directory names to the config file paths for those directories. + * @type {Map>} + */ + #configFilePaths = new Map(); + + /** + * The options to use when loading configuration files. + * @type {ConfigLoaderOptions} + */ + #options; + + /** + * Creates a new instance. + * @param {ConfigLoaderOptions} options The options to use when loading configuration files. + */ + constructor(options) { + this.#options = options; + } + + /** + * Determines which config file to use. This is determined by seeing if an + * override config file was specified, and if so, using it; otherwise, as long + * as override config file is not explicitly set to `false`, it will search + * upwards from `fromDirectory` for a file named `eslint.config.js`. + * @param {string} fromDirectory The directory from which to start searching. + * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for + * the config file. + */ + async #locateConfigFileToUse(fromDirectory) { + // check cache first + if (this.#configFilePaths.has(fromDirectory)) { + return this.#configFilePaths.get(fromDirectory); + } + + const resultPromise = ConfigLoader.locateConfigFileToUse({ + useConfigFile: this.#options.configFile, + cwd: this.#options.cwd, + fromDirectory, + }); + + // ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory` + this.#configFilePaths.set(fromDirectory, resultPromise); + + // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. + const result = await resultPromise; + + this.#configFilePaths.set(fromDirectory, result); + + return result; + } + + /** + * Calculates the config array for this run based on inputs. + * @param {string} configFilePath The absolute path to the config file to use if not overridden. + * @param {string} basePath The base path to use for relative paths in the config file. + * @returns {Promise} The config array for `eslint`. + */ + async #calculateConfigArray(configFilePath, basePath) { + // check for cached version first + if (this.#configArrays.has(configFilePath)) { + return this.#configArrays.get(configFilePath); + } + + const configsPromise = ConfigLoader.calculateConfigArray( + configFilePath, + basePath, + this.#options, + ); + + // ensure `ConfigLoader.calculateConfigArray` is called only once for `configFilePath` + this.#configArrays.set(configFilePath, configsPromise); + + // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. + const configs = await configsPromise; + + this.#configArrays.set(configFilePath, configs); + + return configs; + } + + /** + * Returns the config file path for the given directory or file. This will either use + * the override config file that was specified in the constructor options or + * search for a config file from the directory. + * @param {string} fileOrDirPath The file or directory path to get the config file path for. + * @returns {Promise} The config file path or `undefined` if not found. + * @throws {Error} If `fileOrDirPath` is not a non-empty string. + * @throws {Error} If `fileOrDirPath` is not an absolute path. + */ + async findConfigFileForPath(fileOrDirPath) { + assertValidFilePath(fileOrDirPath); + + const absoluteDirPath = path.resolve( + this.#options.cwd, + path.dirname(fileOrDirPath), + ); + const { configFilePath } = + await this.#locateConfigFileToUse(absoluteDirPath); + + return configFilePath; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file or directory to retrieve config for. + * @returns {Promise} A configuration object for the file + * or `undefined` if there is no configuration data for the file. + * @throws {Error} If no configuration for `filePath` exists. + */ + async loadConfigArrayForFile(filePath) { + assertValidFilePath(filePath); + + debug(`Calculating config for file ${filePath}`); + + const configFilePath = await this.findConfigFileForPath(filePath); + + assertConfigurationExists(configFilePath, this.#options); + + return this.loadConfigArrayForDirectory(filePath); + } + + /** + * Returns a configuration object for the given directory based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} dirPath The path of the directory to retrieve config for. + * @returns {Promise} A configuration object for the directory + * or `undefined` if there is no configuration data for the directory. + */ + async loadConfigArrayForDirectory(dirPath) { + assertValidFilePath(dirPath); + + debug(`Calculating config for directory ${dirPath}`); + + const absoluteDirPath = path.resolve( + this.#options.cwd, + path.dirname(dirPath), + ); + const { configFilePath, basePath } = + await this.#locateConfigFileToUse(absoluteDirPath); + + debug(`Using config file ${configFilePath} and base path ${basePath}`); + return this.#calculateConfigArray(configFilePath, basePath); + } + + /** + * Returns a configuration array for the given file based on the CLI options. + * This is a synchronous operation and does not read any files from disk. It's + * intended to be used in locations where we know the config file has already + * been loaded and we just need to get the configuration for a file. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {ConfigData|undefined} A configuration object for the file + * or `undefined` if there is no configuration data for the file. + * @throws {Error} If `filePath` is not a non-empty string. + * @throws {Error} If `filePath` is not an absolute path. + * @throws {Error} If the config file was not already loaded. + */ + getCachedConfigArrayForFile(filePath) { + assertValidFilePath(filePath); + + debug(`Looking up cached config for ${filePath}`); + + return this.getCachedConfigArrayForPath(path.dirname(filePath)); + } + + /** + * Returns a configuration array for the given directory based on the CLI options. + * This is a synchronous operation and does not read any files from disk. It's + * intended to be used in locations where we know the config file has already + * been loaded and we just need to get the configuration for a file. + * @param {string} fileOrDirPath The path of the directory to retrieve a config object for. + * @returns {ConfigData|undefined} A configuration object for the directory + * or `undefined` if there is no configuration data for the directory. + * @throws {Error} If `dirPath` is not a non-empty string. + * @throws {Error} If `dirPath` is not an absolute path. + * @throws {Error} If the config file was not already loaded. + */ + getCachedConfigArrayForPath(fileOrDirPath) { + assertValidFilePath(fileOrDirPath); + + debug(`Looking up cached config for ${fileOrDirPath}`); + + const absoluteDirPath = path.resolve(this.#options.cwd, fileOrDirPath); + + if (!this.#configFilePaths.has(absoluteDirPath)) { + throw new Error(`Could not find config file for ${fileOrDirPath}`); + } + + const configFilePathInfo = this.#configFilePaths.get(absoluteDirPath); + + if (typeof configFilePathInfo.then === "function") { + throw new Error( + `Config file path for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`, + ); + } + + const { configFilePath } = configFilePathInfo; + + const configArray = this.#configArrays.get(configFilePath); + + if (!configArray || typeof configArray.then === "function") { + throw new Error( + `Config array for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`, + ); + } + + return configArray; + } + + /** + * Used to import the jiti dependency. This method is exposed internally for testing purposes. + * @returns {Promise>} A promise that fulfills with a module object + * or rejects with an error if jiti is not found. + */ + static loadJiti() { + return import("jiti"); + } + + /** + * Determines which config file to use. This is determined by seeing if an + * override config file was specified, and if so, using it; otherwise, as long + * as override config file is not explicitly set to `false`, it will search + * upwards from `fromDirectory` for a file named `eslint.config.js`. + * This method is exposed internally for testing purposes. + * @param {Object} [options] the options object + * @param {string|false|undefined} options.useConfigFile The path to the config file to use. + * @param {string} options.cwd Path to a directory that should be considered as the current working directory. + * @param {string} [options.fromDirectory] The directory from which to start searching. Defaults to `cwd`. + * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for + * the config file. + */ + static async locateConfigFileToUse({ + useConfigFile, + cwd, + fromDirectory = cwd, + }) { + // determine where to load config file from + let configFilePath; + let basePath = cwd; + + if (typeof useConfigFile === "string") { + debug(`Override config file path is ${useConfigFile}`); + configFilePath = path.resolve(cwd, useConfigFile); + basePath = cwd; + } else if (useConfigFile !== false) { + debug("Searching for eslint.config.js"); + configFilePath = await findUp(FLAT_CONFIG_FILENAMES, { + cwd: fromDirectory, + }); + + if (configFilePath) { + basePath = path.dirname(configFilePath); + } + } + + return { + configFilePath, + basePath, + }; + } + + /** + * Calculates the config array for this run based on inputs. + * This method is exposed internally for testing purposes. + * @param {string} configFilePath The absolute path to the config file to use if not overridden. + * @param {string} basePath The base path to use for relative paths in the config file. + * @param {ConfigLoaderOptions} options The options to use when loading configuration files. + * @returns {Promise} The config array for `eslint`. + */ + static async calculateConfigArray(configFilePath, basePath, options) { + const { + cwd, + baseConfig, + ignoreEnabled, + ignorePatterns, + overrideConfig, + defaultConfigs = [], + } = options; + + debug( + `Calculating config array from config file ${configFilePath} and base path ${basePath}`, + ); + + const configs = new FlatConfigArray(baseConfig || [], { + basePath, + shouldIgnore: ignoreEnabled, + }); + + // load config file + if (configFilePath) { + debug(`Loading config file ${configFilePath}`); + const fileConfig = await loadConfigFile(configFilePath); + + /* + * It's possible that a config file could be empty or else + * have an empty object or array. In this case, we want to + * warn the user that they have an empty config. + * + * An empty CommonJS file exports an empty object while + * an empty ESM file exports undefined. + */ + + let emptyConfig = typeof fileConfig === "undefined"; + + debug( + `Config file ${configFilePath} is ${emptyConfig ? "empty" : "not empty"}`, + ); + + if (!emptyConfig) { + if (Array.isArray(fileConfig)) { + if (fileConfig.length === 0) { + debug( + `Config file ${configFilePath} is an empty array`, + ); + emptyConfig = true; + } else { + configs.push(...fileConfig); + } + } else { + if ( + typeof fileConfig === "object" && + fileConfig !== null && + Object.keys(fileConfig).length === 0 + ) { + debug( + `Config file ${configFilePath} is an empty object`, + ); + emptyConfig = true; + } else { + configs.push(fileConfig); + } + } + } + + if (emptyConfig) { + globalThis.process?.emitWarning?.( + `Running ESLint with an empty config (from ${configFilePath}). Please double-check that this is what you want. If you want to run ESLint with an empty config, export [{}] to remove this warning.`, + "ESLintEmptyConfigWarning", + ); + } + } + + // add in any configured defaults + configs.push(...defaultConfigs); + + // append command line ignore patterns + if (ignorePatterns && ignorePatterns.length > 0) { + let relativeIgnorePatterns; + + /* + * If the config file basePath is different than the cwd, then + * the ignore patterns won't work correctly. Here, we adjust the + * ignore pattern to include the correct relative path. Patterns + * passed as `ignorePatterns` are relative to the cwd, whereas + * the config file basePath can be an ancestor of the cwd. + */ + if (basePath === cwd) { + relativeIgnorePatterns = ignorePatterns; + } else { + // relative path must only have Unix-style separators + const relativeIgnorePath = path + .relative(basePath, cwd) + .replace(/\\/gu, "/"); + + relativeIgnorePatterns = ignorePatterns.map(pattern => { + const negated = pattern.startsWith("!"); + const basePattern = negated ? pattern.slice(1) : pattern; + + return ( + (negated ? "!" : "") + + path.posix.join(relativeIgnorePath, basePattern) + ); + }); + } + + /* + * Ignore patterns are added to the end of the config array + * so they can override default ignores. + */ + configs.push({ + ignores: relativeIgnorePatterns, + }); + } + + if (overrideConfig) { + if (Array.isArray(overrideConfig)) { + configs.push(...overrideConfig); + } else { + configs.push(overrideConfig); + } + } + + await configs.normalize(); + + return configs; + } } /** @@ -596,141 +622,145 @@ class ConfigLoader { * from the current working directory. */ class LegacyConfigLoader extends ConfigLoader { - - /** - * The options to use when loading configuration files. - * @type {ConfigLoaderOptions} - */ - #options; - - /** - * The cached config file path for this instance. - * @type {Promise<{configFilePath:string,basePath:string}|undefined>} - */ - #configFilePath; - - /** - * The cached config array for this instance. - * @type {FlatConfigArray|Promise} - */ - #configArray; - - /** - * Creates a new instance. - * @param {ConfigLoaderOptions} options The options to use when loading configuration files. - */ - constructor(options) { - super(options); - this.#options = options; - } - - /** - * Determines which config file to use. This is determined by seeing if an - * override config file was specified, and if so, using it; otherwise, as long - * as override config file is not explicitly set to `false`, it will search - * upwards from the cwd for a file named `eslint.config.js`. - * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for - * the config file. - */ - #locateConfigFileToUse() { - if (!this.#configFilePath) { - this.#configFilePath = ConfigLoader.locateConfigFileToUse({ - useConfigFile: this.#options.configFile, - cwd: this.#options.cwd - }); - } - - return this.#configFilePath; - } - - /** - * Calculates the config array for this run based on inputs. - * @param {string} configFilePath The absolute path to the config file to use if not overridden. - * @param {string} basePath The base path to use for relative paths in the config file. - * @returns {Promise} The config array for `eslint`. - */ - async #calculateConfigArray(configFilePath, basePath) { - - // check for cached version first - if (this.#configArray) { - return this.#configArray; - } - - // ensure `ConfigLoader.calculateConfigArray` is called only once - this.#configArray = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options); - - // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. - this.#configArray = await this.#configArray; - - return this.#configArray; - } - - - /** - * Returns the config file path for the given directory. This will either use - * the override config file that was specified in the constructor options or - * search for a config file from the directory of the file being linted. - * @param {string} dirPath The directory path to get the config file path for. - * @returns {Promise} The config file path or `undefined` if not found. - * @throws {Error} If `fileOrDirPath` is not a non-empty string. - * @throws {Error} If `fileOrDirPath` is not an absolute path. - */ - async findConfigFileForPath(dirPath) { - - assertValidFilePath(dirPath); - - const { configFilePath } = await this.#locateConfigFileToUse(); - - return configFilePath; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} dirPath The path of the directory to retrieve config for. - * @returns {Promise} A configuration object for the file - * or `undefined` if there is no configuration data for the file. - */ - async loadConfigArrayForDirectory(dirPath) { - - assertValidFilePath(dirPath); - - debug(`[Legacy]: Calculating config for ${dirPath}`); - - const { configFilePath, basePath } = await this.#locateConfigFileToUse(); - - debug(`[Legacy]: Using config file ${configFilePath} and base path ${basePath}`); - return this.#calculateConfigArray(configFilePath, basePath); - } - - /** - * Returns a configuration array for the given directory based on the CLI options. - * This is a synchronous operation and does not read any files from disk. It's - * intended to be used in locations where we know the config file has already - * been loaded and we just need to get the configuration for a file. - * @param {string} dirPath The path of the directory to retrieve a config object for. - * @returns {ConfigData|undefined} A configuration object for the file - * or `undefined` if there is no configuration data for the file. - * @throws {Error} If `dirPath` is not a non-empty string. - * @throws {Error} If `dirPath` is not an absolute path. - * @throws {Error} If the config file was not already loaded. - */ - getCachedConfigArrayForPath(dirPath) { - assertValidFilePath(dirPath); - - debug(`[Legacy]: Looking up cached config for ${dirPath}`); - - if (!this.#configArray) { - throw new Error(`Could not find config file for ${dirPath}`); - } - - if (typeof this.#configArray.then === "function") { - throw new Error(`Config array for ${dirPath} has not yet been calculated or an error occurred during the calculation`); - } - - return this.#configArray; - } + /** + * The options to use when loading configuration files. + * @type {ConfigLoaderOptions} + */ + #options; + + /** + * The cached config file path for this instance. + * @type {Promise<{configFilePath:string,basePath:string}|undefined>} + */ + #configFilePath; + + /** + * The cached config array for this instance. + * @type {FlatConfigArray|Promise} + */ + #configArray; + + /** + * Creates a new instance. + * @param {ConfigLoaderOptions} options The options to use when loading configuration files. + */ + constructor(options) { + super(options); + this.#options = options; + } + + /** + * Determines which config file to use. This is determined by seeing if an + * override config file was specified, and if so, using it; otherwise, as long + * as override config file is not explicitly set to `false`, it will search + * upwards from the cwd for a file named `eslint.config.js`. + * @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for + * the config file. + */ + #locateConfigFileToUse() { + if (!this.#configFilePath) { + this.#configFilePath = ConfigLoader.locateConfigFileToUse({ + useConfigFile: this.#options.configFile, + cwd: this.#options.cwd, + }); + } + + return this.#configFilePath; + } + + /** + * Calculates the config array for this run based on inputs. + * @param {string} configFilePath The absolute path to the config file to use if not overridden. + * @param {string} basePath The base path to use for relative paths in the config file. + * @returns {Promise} The config array for `eslint`. + */ + async #calculateConfigArray(configFilePath, basePath) { + // check for cached version first + if (this.#configArray) { + return this.#configArray; + } + + // ensure `ConfigLoader.calculateConfigArray` is called only once + this.#configArray = ConfigLoader.calculateConfigArray( + configFilePath, + basePath, + this.#options, + ); + + // Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method. + this.#configArray = await this.#configArray; + + return this.#configArray; + } + + /** + * Returns the config file path for the given directory. This will either use + * the override config file that was specified in the constructor options or + * search for a config file from the directory of the file being linted. + * @param {string} dirPath The directory path to get the config file path for. + * @returns {Promise} The config file path or `undefined` if not found. + * @throws {Error} If `fileOrDirPath` is not a non-empty string. + * @throws {Error} If `fileOrDirPath` is not an absolute path. + */ + async findConfigFileForPath(dirPath) { + assertValidFilePath(dirPath); + + const { configFilePath } = await this.#locateConfigFileToUse(); + + return configFilePath; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} dirPath The path of the directory to retrieve config for. + * @returns {Promise} A configuration object for the file + * or `undefined` if there is no configuration data for the file. + */ + async loadConfigArrayForDirectory(dirPath) { + assertValidFilePath(dirPath); + + debug(`[Legacy]: Calculating config for ${dirPath}`); + + const { configFilePath, basePath } = + await this.#locateConfigFileToUse(); + + debug( + `[Legacy]: Using config file ${configFilePath} and base path ${basePath}`, + ); + return this.#calculateConfigArray(configFilePath, basePath); + } + + /** + * Returns a configuration array for the given directory based on the CLI options. + * This is a synchronous operation and does not read any files from disk. It's + * intended to be used in locations where we know the config file has already + * been loaded and we just need to get the configuration for a file. + * @param {string} dirPath The path of the directory to retrieve a config object for. + * @returns {ConfigData|undefined} A configuration object for the file + * or `undefined` if there is no configuration data for the file. + * @throws {Error} If `dirPath` is not a non-empty string. + * @throws {Error} If `dirPath` is not an absolute path. + * @throws {Error} If the config file was not already loaded. + */ + getCachedConfigArrayForPath(dirPath) { + assertValidFilePath(dirPath); + + debug(`[Legacy]: Looking up cached config for ${dirPath}`); + + if (!this.#configArray) { + throw new Error(`Could not find config file for ${dirPath}`); + } + + if (typeof this.#configArray.then === "function") { + throw new Error( + `Config array for ${dirPath} has not yet been calculated or an error occurred during the calculation`, + ); + } + + return this.#configArray; + } } module.exports = { ConfigLoader, LegacyConfigLoader }; diff --git a/lib/config/config.js b/lib/config/config.js index d75b6d6726b8..74ddaad3f5b5 100644 --- a/lib/config/config.js +++ b/lib/config/config.js @@ -22,12 +22,12 @@ const { ObjectSchema } = require("@eslint/config-array"); const ruleValidator = new RuleValidator(); const severities = new Map([ - [0, 0], - [1, 1], - [2, 2], - ["off", 0], - ["warn", 1], - ["error", 2] + [0, 0], + [1, 1], + [2, 2], + ["off", 0], + ["warn", 1], + ["error", 2], ]); /** @@ -37,12 +37,12 @@ const severities = new Map([ * name. */ function splitPluginIdentifier(identifier) { - const parts = identifier.split("/"); + const parts = identifier.split("/"); - return { - objectName: parts.pop(), - pluginName: parts.join("/") - }; + return { + objectName: parts.pop(), + pluginName: parts.join("/"), + }; } /** @@ -52,36 +52,34 @@ function splitPluginIdentifier(identifier) { * is no name. */ function getObjectId(object) { + // first check old-style name + let name = object.name; - // first check old-style name - let name = object.name; + if (!name) { + if (!object.meta) { + return null; + } - if (!name) { + name = object.meta.name; - if (!object.meta) { - return null; - } + if (!name) { + return null; + } + } - name = object.meta.name; + // now check for old-style version + let version = object.version; - if (!name) { - return null; - } - } + if (!version) { + version = object.meta && object.meta.version; + } - // now check for old-style version - let version = object.version; + // if there's a version then append that + if (version) { + return `${name}@${version}`; + } - if (!version) { - version = object.meta && object.meta.version; - } - - // if there's a version then append that - if (version) { - return `${name}@${version}`; - } - - return name; + return name; } /** @@ -93,40 +91,39 @@ function getObjectId(object) { * @throws {TypeError} If a function is found in the languageOptions. */ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") { + const result = {}; - const result = {}; - - for (const [key, value] of Object.entries(languageOptions)) { - if (value) { - if (typeof value === "object") { - const name = getObjectId(value); + for (const [key, value] of Object.entries(languageOptions)) { + if (value) { + if (typeof value === "object") { + const name = getObjectId(value); - if (name && hasMethod(value)) { - result[key] = name; - } else { - result[key] = languageOptionsToJSON(value, key); - } - continue; - } + if (name && hasMethod(value)) { + result[key] = name; + } else { + result[key] = languageOptionsToJSON(value, key); + } + continue; + } - if (typeof value === "function") { - const error = new TypeError(`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`); + if (typeof value === "function") { + const error = new TypeError( + `Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`, + ); - error.messageTemplate = "config-serialize-function"; - error.messageData = { key, objectKey }; + error.messageTemplate = "config-serialize-function"; + error.messageData = { key, objectKey }; - throw error; - } + throw error; + } + } - } + result[key] = value; + } - result[key] = value; - } - - return result; + return result; } - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- @@ -135,168 +132,197 @@ function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") { * Represents a normalized configuration object. */ class Config { - - /** - * The name to use for the language when serializing to JSON. - * @type {string|undefined} - */ - #languageName; - - /** - * The name to use for the processor when serializing to JSON. - * @type {string|undefined} - */ - #processorName; - - /** - * Creates a new instance. - * @param {Object} config The configuration object. - */ - constructor(config) { - - const { plugins, language, languageOptions, processor, ...otherKeys } = config; - - // Validate config object - const schema = new ObjectSchema(flatConfigSchema); - - schema.validate(config); - - // first, copy all the other keys over - Object.assign(this, otherKeys); - - // ensure that a language is specified - if (!language) { - throw new TypeError("Key 'language' is required."); - } - - // copy the rest over - this.plugins = plugins; - this.language = language; - - // Check language value - const { pluginName: languagePluginName, objectName: localLanguageName } = splitPluginIdentifier(language); - - this.#languageName = language; - - if (!plugins || !plugins[languagePluginName] || !plugins[languagePluginName].languages || !plugins[languagePluginName].languages[localLanguageName]) { - throw new TypeError(`Key "language": Could not find "${localLanguageName}" in plugin "${languagePluginName}".`); - } - - this.language = plugins[languagePluginName].languages[localLanguageName]; - - if (this.language.defaultLanguageOptions ?? languageOptions) { - this.languageOptions = flatConfigSchema.languageOptions.merge( - this.language.defaultLanguageOptions, - languageOptions - ); - } else { - this.languageOptions = {}; - } - - // Validate language options - try { - this.language.validateLanguageOptions(this.languageOptions); - } catch (error) { - throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error }); - } - - // Normalize language options if necessary - if (this.language.normalizeLanguageOptions) { - this.languageOptions = this.language.normalizeLanguageOptions(this.languageOptions); - } - - // Check processor value - if (processor) { - this.processor = processor; - - if (typeof processor === "string") { - const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor); - - this.#processorName = processor; - - if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) { - throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`); - } - - this.processor = plugins[pluginName].processors[localProcessorName]; - } else if (typeof processor === "object") { - this.#processorName = getObjectId(processor); - this.processor = processor; - } else { - throw new TypeError("Key 'processor' must be a string or an object."); - } - } - - // Process the rules - if (this.rules) { - this.#normalizeRulesConfig(); - ruleValidator.validate(this); - } - } - - /** - * Converts the configuration to a JSON representation. - * @returns {Record} The JSON representation of the configuration. - * @throws {Error} If the configuration cannot be serialized. - */ - toJSON() { - - if (this.processor && !this.#processorName) { - throw new Error("Could not serialize processor object (missing 'meta' object)."); - } - - if (!this.#languageName) { - throw new Error("Could not serialize language object (missing 'meta' object)."); - } - - return { - ...this, - plugins: Object.entries(this.plugins).map(([namespace, plugin]) => { - - const pluginId = getObjectId(plugin); - - if (!pluginId) { - return namespace; - } - - return `${namespace}:${pluginId}`; - }), - language: this.#languageName, - languageOptions: languageOptionsToJSON(this.languageOptions), - processor: this.#processorName - }; - } - - /** - * Normalizes the rules configuration. Ensures that each rule config is - * an array and that the severity is a number. Applies meta.defaultOptions. - * This function modifies `this.rules`. - * @returns {void} - */ - #normalizeRulesConfig() { - for (const [ruleId, originalConfig] of Object.entries(this.rules)) { - - // ensure rule config is an array - let ruleConfig = Array.isArray(originalConfig) - ? originalConfig - : [originalConfig]; - - // normalize severity - ruleConfig[0] = severities.get(ruleConfig[0]); - - const rule = getRuleFromConfig(ruleId, this); - - // apply meta.defaultOptions - const slicedOptions = ruleConfig.slice(1); - const mergedOptions = deepMergeArrays(rule?.meta?.defaultOptions, slicedOptions); - - if (mergedOptions.length) { - ruleConfig = [ruleConfig[0], ...mergedOptions]; - } - - this.rules[ruleId] = ruleConfig; - } - } + /** + * The name to use for the language when serializing to JSON. + * @type {string|undefined} + */ + #languageName; + + /** + * The name to use for the processor when serializing to JSON. + * @type {string|undefined} + */ + #processorName; + + /** + * Creates a new instance. + * @param {Object} config The configuration object. + */ + constructor(config) { + const { plugins, language, languageOptions, processor, ...otherKeys } = + config; + + // Validate config object + const schema = new ObjectSchema(flatConfigSchema); + + schema.validate(config); + + // first, copy all the other keys over + Object.assign(this, otherKeys); + + // ensure that a language is specified + if (!language) { + throw new TypeError("Key 'language' is required."); + } + + // copy the rest over + this.plugins = plugins; + this.language = language; + + // Check language value + const { + pluginName: languagePluginName, + objectName: localLanguageName, + } = splitPluginIdentifier(language); + + this.#languageName = language; + + if ( + !plugins || + !plugins[languagePluginName] || + !plugins[languagePluginName].languages || + !plugins[languagePluginName].languages[localLanguageName] + ) { + throw new TypeError( + `Key "language": Could not find "${localLanguageName}" in plugin "${languagePluginName}".`, + ); + } + + this.language = + plugins[languagePluginName].languages[localLanguageName]; + + if (this.language.defaultLanguageOptions ?? languageOptions) { + this.languageOptions = flatConfigSchema.languageOptions.merge( + this.language.defaultLanguageOptions, + languageOptions, + ); + } else { + this.languageOptions = {}; + } + + // Validate language options + try { + this.language.validateLanguageOptions(this.languageOptions); + } catch (error) { + throw new TypeError(`Key "languageOptions": ${error.message}`, { + cause: error, + }); + } + + // Normalize language options if necessary + if (this.language.normalizeLanguageOptions) { + this.languageOptions = this.language.normalizeLanguageOptions( + this.languageOptions, + ); + } + + // Check processor value + if (processor) { + this.processor = processor; + + if (typeof processor === "string") { + const { pluginName, objectName: localProcessorName } = + splitPluginIdentifier(processor); + + this.#processorName = processor; + + if ( + !plugins || + !plugins[pluginName] || + !plugins[pluginName].processors || + !plugins[pluginName].processors[localProcessorName] + ) { + throw new TypeError( + `Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`, + ); + } + + this.processor = + plugins[pluginName].processors[localProcessorName]; + } else if (typeof processor === "object") { + this.#processorName = getObjectId(processor); + this.processor = processor; + } else { + throw new TypeError( + "Key 'processor' must be a string or an object.", + ); + } + } + + // Process the rules + if (this.rules) { + this.#normalizeRulesConfig(); + ruleValidator.validate(this); + } + } + + /** + * Converts the configuration to a JSON representation. + * @returns {Record} The JSON representation of the configuration. + * @throws {Error} If the configuration cannot be serialized. + */ + toJSON() { + if (this.processor && !this.#processorName) { + throw new Error( + "Could not serialize processor object (missing 'meta' object).", + ); + } + + if (!this.#languageName) { + throw new Error( + "Could not serialize language object (missing 'meta' object).", + ); + } + + return { + ...this, + plugins: Object.entries(this.plugins).map(([namespace, plugin]) => { + const pluginId = getObjectId(plugin); + + if (!pluginId) { + return namespace; + } + + return `${namespace}:${pluginId}`; + }), + language: this.#languageName, + languageOptions: languageOptionsToJSON(this.languageOptions), + processor: this.#processorName, + }; + } + + /** + * Normalizes the rules configuration. Ensures that each rule config is + * an array and that the severity is a number. Applies meta.defaultOptions. + * This function modifies `this.rules`. + * @returns {void} + */ + #normalizeRulesConfig() { + for (const [ruleId, originalConfig] of Object.entries(this.rules)) { + // ensure rule config is an array + let ruleConfig = Array.isArray(originalConfig) + ? originalConfig + : [originalConfig]; + + // normalize severity + ruleConfig[0] = severities.get(ruleConfig[0]); + + const rule = getRuleFromConfig(ruleId, this); + + // apply meta.defaultOptions + const slicedOptions = ruleConfig.slice(1); + const mergedOptions = deepMergeArrays( + rule?.meta?.defaultOptions, + slicedOptions, + ); + + if (mergedOptions.length) { + ruleConfig = [ruleConfig[0], ...mergedOptions]; + } + + this.rules[ruleId] = ruleConfig; + } + } } module.exports = { Config }; diff --git a/lib/config/default-config.js b/lib/config/default-config.js index 1b4ec45c5e0a..aebf6e930685 100644 --- a/lib/config/default-config.js +++ b/lib/config/default-config.js @@ -15,55 +15,64 @@ const Rules = require("../rules"); // Helpers //----------------------------------------------------------------------------- +const sharedDefaultConfig = [ + // intentionally empty config to ensure these files are globbed by default + { + files: ["**/*.js", "**/*.mjs"], + }, + { + files: ["**/*.cjs"], + languageOptions: { + sourceType: "commonjs", + ecmaVersion: "latest", + }, + }, +]; + exports.defaultConfig = Object.freeze([ - { - plugins: { - "@": { + { + plugins: { + "@": { + languages: { + js: require("../languages/js"), + }, + + /* + * Because we try to delay loading rules until absolutely + * necessary, a proxy allows us to hook into the lazy-loading + * aspect of the rules map while still keeping all of the + * relevant configuration inside of the config array. + */ + rules: new Proxy( + {}, + { + get(target, property) { + return Rules.get(property); + }, - languages: { - js: require("../languages/js") - }, + has(target, property) { + return Rules.has(property); + }, + }, + ), + }, + }, + language: "@/js", + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + }, - /* - * Because we try to delay loading rules until absolutely - * necessary, a proxy allows us to hook into the lazy-loading - * aspect of the rules map while still keeping all of the - * relevant configuration inside of the config array. - */ - rules: new Proxy({}, { - get(target, property) { - return Rules.get(property); - }, + // default ignores are listed here + { + ignores: ["**/node_modules/", ".git/"], + }, - has(target, property) { - return Rules.has(property); - } - }) - } - }, - language: "@/js", - linterOptions: { - reportUnusedDisableDirectives: 1 - } - }, + ...sharedDefaultConfig, +]); - // default ignores are listed here - { - ignores: [ - "**/node_modules/", - ".git/" - ] - }, +exports.defaultRuleTesterConfig = Object.freeze([ + { files: ["**"] }, // Make sure the default config matches for all files - // intentionally empty config to ensure these files are globbed by default - { - files: ["**/*.js", "**/*.mjs"] - }, - { - files: ["**/*.cjs"], - languageOptions: { - sourceType: "commonjs", - ecmaVersion: "latest" - } - } + ...sharedDefaultConfig, ]); diff --git a/lib/config/flat-config-array.js b/lib/config/flat-config-array.js index 7c9110197418..3cc355ad5c5f 100644 --- a/lib/config/flat-config-array.js +++ b/lib/config/flat-config-array.js @@ -31,43 +31,41 @@ const META_FIELDS = new Set(["name"]); * @returns {TypeError} The new error with details. */ function wrapConfigErrorWithDetails(error, originalLength, baseLength) { - - let location = "user-defined"; - let configIndex = error.index; - - /* - * A config array is set up in this order: - * 1. Base config - * 2. Original configs - * 3. User-defined configs - * 4. CLI-defined configs - * - * So we need to adjust the index to account for the base config. - * - * - If the index is less than the base length, it's in the base config - * (as specified by `baseConfig` argument to `FlatConfigArray` constructor). - * - If the index is greater than the base length but less than the original - * length + base length, it's in the original config. The original config - * is passed to the `FlatConfigArray` constructor as the first argument. - * - Otherwise, it's in the user-defined config, which is loaded from the - * config file and merged with any command-line options. - */ - if (error.index < baseLength) { - location = "base"; - } else if (error.index < originalLength + baseLength) { - location = "original"; - configIndex = error.index - baseLength; - } else { - configIndex = error.index - originalLength - baseLength; - } - - return new TypeError( - `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`, - { cause: error } - ); + let location = "user-defined"; + let configIndex = error.index; + + /* + * A config array is set up in this order: + * 1. Base config + * 2. Original configs + * 3. User-defined configs + * 4. CLI-defined configs + * + * So we need to adjust the index to account for the base config. + * + * - If the index is less than the base length, it's in the base config + * (as specified by `baseConfig` argument to `FlatConfigArray` constructor). + * - If the index is greater than the base length but less than the original + * length + base length, it's in the original config. The original config + * is passed to the `FlatConfigArray` constructor as the first argument. + * - Otherwise, it's in the user-defined config, which is loaded from the + * config file and merged with any command-line options. + */ + if (error.index < baseLength) { + location = "base"; + } else if (error.index < originalLength + baseLength) { + location = "original"; + configIndex = error.index - baseLength; + } else { + configIndex = error.index - originalLength - baseLength; + } + + return new TypeError( + `${error.message.slice(0, -1)} at ${location} index ${configIndex}.`, + { cause: error }, + ); } - const originalBaseConfig = Symbol("originalBaseConfig"); const originalLength = Symbol("originalLength"); const baseLength = Symbol("baseLength"); @@ -80,143 +78,140 @@ const baseLength = Symbol("baseLength"); * Represents an array containing configuration information for ESLint. */ class FlatConfigArray extends ConfigArray { - - /** - * Creates a new instance. - * @param {*[]} configs An array of configuration information. - * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options - * to use for the config array instance. - */ - constructor(configs, { - basePath, - shouldIgnore = true, - baseConfig = defaultConfig - } = {}) { - super(configs, { - basePath, - schema: flatConfigSchema - }); - - /** - * The original length of the array before any modifications. - * @type {number} - */ - this[originalLength] = this.length; - - if (baseConfig[Symbol.iterator]) { - this.unshift(...baseConfig); - } else { - this.unshift(baseConfig); - } - - /** - * The length of the array after applying the base config. - * @type {number} - */ - this[baseLength] = this.length - this[originalLength]; - - /** - * The base config used to build the config array. - * @type {Array} - */ - this[originalBaseConfig] = baseConfig; - Object.defineProperty(this, originalBaseConfig, { writable: false }); - - /** - * Determines if `ignores` fields should be honored. - * If true, then all `ignores` fields are honored. - * if false, then only `ignores` fields in the baseConfig are honored. - * @type {boolean} - */ - this.shouldIgnore = shouldIgnore; - Object.defineProperty(this, "shouldIgnore", { writable: false }); - } - - /** - * Normalizes the array by calling the superclass method and catching/rethrowing - * any ConfigError exceptions with additional details. - * @param {any} [context] The context to use to normalize the array. - * @returns {Promise} A promise that resolves when the array is normalized. - */ - normalize(context) { - return super.normalize(context) - .catch(error => { - if (error.name === "ConfigError") { - throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]); - } - - throw error; - - }); - } - - /** - * Normalizes the array by calling the superclass method and catching/rethrowing - * any ConfigError exceptions with additional details. - * @param {any} [context] The context to use to normalize the array. - * @returns {FlatConfigArray} The current instance. - * @throws {TypeError} If the config is invalid. - */ - normalizeSync(context) { - - try { - - return super.normalizeSync(context); - - } catch (error) { - - if (error.name === "ConfigError") { - throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]); - } - - throw error; - - } - - } - - /* eslint-disable class-methods-use-this -- Desired as instance method */ - /** - * Replaces a config with another config to allow us to put strings - * in the config array that will be replaced by objects before - * normalization. - * @param {Object} config The config to preprocess. - * @returns {Object} The preprocessed config. - */ - [ConfigArraySymbol.preprocessConfig](config) { - - /* - * If a config object has `ignores` and no other non-meta fields, then it's an object - * for global ignores. If `shouldIgnore` is false, that object shouldn't apply, - * so we'll remove its `ignores`. - */ - if ( - !this.shouldIgnore && - !this[originalBaseConfig].includes(config) && - config.ignores && - Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1 - ) { - /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */ - const { ignores, ...otherKeys } = config; - - return otherKeys; - } - - return config; - } - - /** - * Finalizes the config by replacing plugin references with their objects - * and validating rule option schemas. - * @param {Object} config The config to finalize. - * @returns {Object} The finalized config. - * @throws {TypeError} If the config is invalid. - */ - [ConfigArraySymbol.finalizeConfig](config) { - return new Config(config); - } - /* eslint-enable class-methods-use-this -- Desired as instance method */ - + /** + * Creates a new instance. + * @param {*[]} configs An array of configuration information. + * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options + * to use for the config array instance. + */ + constructor( + configs, + { basePath, shouldIgnore = true, baseConfig = defaultConfig } = {}, + ) { + super(configs, { + basePath, + schema: flatConfigSchema, + }); + + /** + * The original length of the array before any modifications. + * @type {number} + */ + this[originalLength] = this.length; + + if (baseConfig[Symbol.iterator]) { + this.unshift(...baseConfig); + } else { + this.unshift(baseConfig); + } + + /** + * The length of the array after applying the base config. + * @type {number} + */ + this[baseLength] = this.length - this[originalLength]; + + /** + * The base config used to build the config array. + * @type {Array} + */ + this[originalBaseConfig] = baseConfig; + Object.defineProperty(this, originalBaseConfig, { writable: false }); + + /** + * Determines if `ignores` fields should be honored. + * If true, then all `ignores` fields are honored. + * if false, then only `ignores` fields in the baseConfig are honored. + * @type {boolean} + */ + this.shouldIgnore = shouldIgnore; + Object.defineProperty(this, "shouldIgnore", { writable: false }); + } + + /** + * Normalizes the array by calling the superclass method and catching/rethrowing + * any ConfigError exceptions with additional details. + * @param {any} [context] The context to use to normalize the array. + * @returns {Promise} A promise that resolves when the array is normalized. + */ + normalize(context) { + return super.normalize(context).catch(error => { + if (error.name === "ConfigError") { + throw wrapConfigErrorWithDetails( + error, + this[originalLength], + this[baseLength], + ); + } + + throw error; + }); + } + + /** + * Normalizes the array by calling the superclass method and catching/rethrowing + * any ConfigError exceptions with additional details. + * @param {any} [context] The context to use to normalize the array. + * @returns {FlatConfigArray} The current instance. + * @throws {TypeError} If the config is invalid. + */ + normalizeSync(context) { + try { + return super.normalizeSync(context); + } catch (error) { + if (error.name === "ConfigError") { + throw wrapConfigErrorWithDetails( + error, + this[originalLength], + this[baseLength], + ); + } + + throw error; + } + } + + /* eslint-disable class-methods-use-this -- Desired as instance method */ + /** + * Replaces a config with another config to allow us to put strings + * in the config array that will be replaced by objects before + * normalization. + * @param {Object} config The config to preprocess. + * @returns {Object} The preprocessed config. + */ + [ConfigArraySymbol.preprocessConfig](config) { + /* + * If a config object has `ignores` and no other non-meta fields, then it's an object + * for global ignores. If `shouldIgnore` is false, that object shouldn't apply, + * so we'll remove its `ignores`. + */ + if ( + !this.shouldIgnore && + !this[originalBaseConfig].includes(config) && + config.ignores && + Object.keys(config).filter(key => !META_FIELDS.has(key)).length === + 1 + ) { + /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */ + const { ignores, ...otherKeys } = config; + + return otherKeys; + } + + return config; + } + + /** + * Finalizes the config by replacing plugin references with their objects + * and validating rule option schemas. + * @param {Object} config The config to finalize. + * @returns {Object} The finalized config. + * @throws {TypeError} If the config is invalid. + */ + [ConfigArraySymbol.finalizeConfig](config) { + return new Config(config); + } + /* eslint-enable class-methods-use-this -- Desired as instance method */ } exports.FlatConfigArray = FlatConfigArray; diff --git a/lib/config/flat-config-helpers.js b/lib/config/flat-config-helpers.js index a904a0dd7c08..8f8c727a339c 100644 --- a/lib/config/flat-config-helpers.js +++ b/lib/config/flat-config-helpers.js @@ -17,9 +17,9 @@ // JSON schema that disallows passing any options const noOptionsSchema = Object.freeze({ - type: "array", - minItems: 0, - maxItems: 0 + type: "array", + minItems: 0, + maxItems: 0, }); //----------------------------------------------------------------------------- @@ -33,28 +33,27 @@ const noOptionsSchema = Object.freeze({ * parts of the ruleId; */ function parseRuleId(ruleId) { - let pluginName, ruleName; - - // distinguish between core rules and plugin rules - if (ruleId.includes("/")) { - - // mimic scoped npm packages - if (ruleId.startsWith("@")) { - pluginName = ruleId.slice(0, ruleId.lastIndexOf("/")); - } else { - pluginName = ruleId.slice(0, ruleId.indexOf("/")); - } - - ruleName = ruleId.slice(pluginName.length + 1); - } else { - pluginName = "@"; - ruleName = ruleId; - } - - return { - pluginName, - ruleName - }; + let pluginName, ruleName; + + // distinguish between core rules and plugin rules + if (ruleId.includes("/")) { + // mimic scoped npm packages + if (ruleId.startsWith("@")) { + pluginName = ruleId.slice(0, ruleId.lastIndexOf("/")); + } else { + pluginName = ruleId.slice(0, ruleId.indexOf("/")); + } + + ruleName = ruleId.slice(pluginName.length + 1); + } else { + pluginName = "@"; + ruleName = ruleId; + } + + return { + pluginName, + ruleName, + }; } /** @@ -65,9 +64,9 @@ function parseRuleId(ruleId) { * or undefined if not. */ function getRuleFromConfig(ruleId, config) { - const { pluginName, ruleName } = parseRuleId(ruleId); + const { pluginName, ruleName } = parseRuleId(ruleId); - return config.plugins?.[pluginName]?.rules?.[ruleName]; + return config.plugins?.[pluginName]?.rules?.[ruleName]; } /** @@ -77,52 +76,50 @@ function getRuleFromConfig(ruleId, config) { * @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`. */ function getRuleOptionsSchema(rule) { - - if (!rule.meta) { - return { ...noOptionsSchema }; // default if `meta.schema` is not specified - } - - const schema = rule.meta.schema; - - if (typeof schema === "undefined") { - return { ...noOptionsSchema }; // default if `meta.schema` is not specified - } - - // `schema:false` is an allowed explicit opt-out of options validation for the rule - if (schema === false) { - return null; - } - - if (typeof schema !== "object" || schema === null) { - throw new TypeError("Rule's `meta.schema` must be an array or object"); - } - - // ESLint-specific array form needs to be converted into a valid JSON Schema definition - if (Array.isArray(schema)) { - if (schema.length) { - return { - type: "array", - items: schema, - minItems: 0, - maxItems: schema.length - }; - } - - // `schema:[]` is an explicit way to specify that the rule does not accept any options - return { ...noOptionsSchema }; - } - - // `schema:` is assumed to be a valid JSON Schema definition - return schema; + if (!rule.meta) { + return { ...noOptionsSchema }; // default if `meta.schema` is not specified + } + + const schema = rule.meta.schema; + + if (typeof schema === "undefined") { + return { ...noOptionsSchema }; // default if `meta.schema` is not specified + } + + // `schema:false` is an allowed explicit opt-out of options validation for the rule + if (schema === false) { + return null; + } + + if (typeof schema !== "object" || schema === null) { + throw new TypeError("Rule's `meta.schema` must be an array or object"); + } + + // ESLint-specific array form needs to be converted into a valid JSON Schema definition + if (Array.isArray(schema)) { + if (schema.length) { + return { + type: "array", + items: schema, + minItems: 0, + maxItems: schema.length, + }; + } + + // `schema:[]` is an explicit way to specify that the rule does not accept any options + return { ...noOptionsSchema }; + } + + // `schema:` is assumed to be a valid JSON Schema definition + return schema; } - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { - parseRuleId, - getRuleFromConfig, - getRuleOptionsSchema + parseRuleId, + getRuleFromConfig, + getRuleOptionsSchema, }; diff --git a/lib/config/flat-config-schema.js b/lib/config/flat-config-schema.js index 0b6a1f982e89..ff977a7679e5 100644 --- a/lib/config/flat-config-schema.js +++ b/lib/config/flat-config-schema.js @@ -28,9 +28,12 @@ const { normalizeSeverityToNumber } = require("../shared/severity"); //----------------------------------------------------------------------------- const ruleSeverities = new Map([ - [0, 0], ["off", 0], - [1, 1], ["warn", 1], - [2, 2], ["error", 2] + [0, 0], + ["off", 0], + [1, 1], + ["warn", 1], + [2, 2], + ["error", 2], ]); /** @@ -39,7 +42,7 @@ const ruleSeverities = new Map([ * @returns {boolean} `true` if the value is a non-null object. */ function isNonNullObject(value) { - return typeof value === "object" && value !== null; + return typeof value === "object" && value !== null; } /** @@ -48,7 +51,7 @@ function isNonNullObject(value) { * @returns {boolean} `true` if the value is a non-null non-array object. */ function isNonArrayObject(value) { - return isNonNullObject(value) && !Array.isArray(value); + return isNonNullObject(value) && !Array.isArray(value); } /** @@ -57,7 +60,7 @@ function isNonArrayObject(value) { * @returns {boolean} `true` if the value is undefined. */ function isUndefined(value) { - return typeof value === "undefined"; + return typeof value === "undefined"; } /** @@ -68,57 +71,56 @@ function isUndefined(value) { * @returns {Object} An object with properties from both first and second. */ function deepMerge(first, second, mergeMap = new Map()) { - - let secondMergeMap = mergeMap.get(first); - - if (secondMergeMap) { - const result = secondMergeMap.get(second); - - if (result) { - - // If this combination of first and second arguments has been already visited, return the previously created result. - return result; - } - } else { - secondMergeMap = new Map(); - mergeMap.set(first, secondMergeMap); - } - - /* - * First create a result object where properties from the second object - * overwrite properties from the first. This sets up a baseline to use - * later rather than needing to inspect and change every property - * individually. - */ - const result = { - ...first, - ...second - }; - - delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__" - - // Store the pending result for this combination of first and second arguments. - secondMergeMap.set(second, result); - - for (const key of Object.keys(second)) { - - // avoid hairy edge case - if (key === "__proto__" || !Object.prototype.propertyIsEnumerable.call(first, key)) { - continue; - } - - const firstValue = first[key]; - const secondValue = second[key]; - - if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) { - result[key] = deepMerge(firstValue, secondValue, mergeMap); - } else if (isUndefined(secondValue)) { - result[key] = firstValue; - } - } - - return result; - + let secondMergeMap = mergeMap.get(first); + + if (secondMergeMap) { + const result = secondMergeMap.get(second); + + if (result) { + // If this combination of first and second arguments has been already visited, return the previously created result. + return result; + } + } else { + secondMergeMap = new Map(); + mergeMap.set(first, secondMergeMap); + } + + /* + * First create a result object where properties from the second object + * overwrite properties from the first. This sets up a baseline to use + * later rather than needing to inspect and change every property + * individually. + */ + const result = { + ...first, + ...second, + }; + + delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__" + + // Store the pending result for this combination of first and second arguments. + secondMergeMap.set(second, result); + + for (const key of Object.keys(second)) { + // avoid hairy edge case + if ( + key === "__proto__" || + !Object.prototype.propertyIsEnumerable.call(first, key) + ) { + continue; + } + + const firstValue = first[key]; + const secondValue = second[key]; + + if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) { + result[key] = deepMerge(firstValue, secondValue, mergeMap); + } else if (isUndefined(secondValue)) { + result[key] = firstValue; + } + } + + return result; } /** @@ -128,13 +130,12 @@ function deepMerge(first, second, mergeMap = new Map()) { * @returns {Array} An array of rule options. */ function normalizeRuleOptions(ruleOptions) { + const finalOptions = Array.isArray(ruleOptions) + ? ruleOptions.slice(0) + : [ruleOptions]; - const finalOptions = Array.isArray(ruleOptions) - ? ruleOptions.slice(0) - : [ruleOptions]; - - finalOptions[0] = ruleSeverities.get(finalOptions[0]); - return structuredClone(finalOptions); + finalOptions[0] = ruleSeverities.get(finalOptions[0]); + return structuredClone(finalOptions); } /** @@ -143,15 +144,13 @@ function normalizeRuleOptions(ruleOptions) { * @returns {boolean} `true` if the object has any methods. */ function hasMethod(object) { + for (const key of Object.keys(object)) { + if (typeof object[key] === "function") { + return true; + } + } - for (const key of Object.keys(object)) { - - if (typeof object[key] === "function") { - return true; - } - } - - return false; + return false; } //----------------------------------------------------------------------------- @@ -162,16 +161,17 @@ function hasMethod(object) { * The error type when a rule's options are configured with an invalid type. */ class InvalidRuleOptionsError extends Error { - - /** - * @param {string} ruleId Rule name being configured. - * @param {any} value The invalid value. - */ - constructor(ruleId, value) { - super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); - this.messageTemplate = "invalid-rule-options"; - this.messageData = { ruleId, value }; - } + /** + * @param {string} ruleId Rule name being configured. + * @param {any} value The invalid value. + */ + constructor(ruleId, value) { + super( + `Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`, + ); + this.messageTemplate = "invalid-rule-options"; + this.messageData = { ruleId, value }; + } } /** @@ -182,25 +182,30 @@ class InvalidRuleOptionsError extends Error { * @throws {InvalidRuleOptionsError} If the value isn't a valid rule options. */ function assertIsRuleOptions(ruleId, value) { - if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) { - throw new InvalidRuleOptionsError(ruleId, value); - } + if ( + typeof value !== "string" && + typeof value !== "number" && + !Array.isArray(value) + ) { + throw new InvalidRuleOptionsError(ruleId, value); + } } /** * The error type when a rule's severity is invalid. */ class InvalidRuleSeverityError extends Error { - - /** - * @param {string} ruleId Rule name being configured. - * @param {any} value The invalid value. - */ - constructor(ruleId, value) { - super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`); - this.messageTemplate = "invalid-rule-severity"; - this.messageData = { ruleId, value }; - } + /** + * @param {string} ruleId Rule name being configured. + * @param {any} value The invalid value. + */ + constructor(ruleId, value) { + super( + `Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`, + ); + this.messageTemplate = "invalid-rule-severity"; + this.messageData = { ruleId, value }; + } } /** @@ -211,11 +216,11 @@ class InvalidRuleSeverityError extends Error { * @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity. */ function assertIsRuleSeverity(ruleId, value) { - const severity = ruleSeverities.get(value); + const severity = ruleSeverities.get(value); - if (typeof severity === "undefined") { - throw new InvalidRuleSeverityError(ruleId, value); - } + if (typeof severity === "undefined") { + throw new InvalidRuleSeverityError(ruleId, value); + } } /** @@ -225,9 +230,11 @@ function assertIsRuleSeverity(ruleId, value) { * @throws {TypeError} If the string isn't in the correct format. */ function assertIsPluginMemberName(value) { - if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) { - throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`); - } + if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) { + throw new TypeError( + `Expected string in the form "pluginName/objectName" but found "${value}".`, + ); + } } /** @@ -237,294 +244,293 @@ function assertIsPluginMemberName(value) { * @throws {TypeError} If the value isn't an object. */ function assertIsObject(value) { - if (!isNonNullObject(value)) { - throw new TypeError("Expected an object."); - } + if (!isNonNullObject(value)) { + throw new TypeError("Expected an object."); + } } /** * The error type when there's an eslintrc-style options in a flat config. */ class IncompatibleKeyError extends Error { - - /** - * @param {string} key The invalid key. - */ - constructor(key) { - super("This appears to be in eslintrc format rather than flat config format."); - this.messageTemplate = "eslintrc-incompat"; - this.messageData = { key }; - } + /** + * @param {string} key The invalid key. + */ + constructor(key) { + super( + "This appears to be in eslintrc format rather than flat config format.", + ); + this.messageTemplate = "eslintrc-incompat"; + this.messageData = { key }; + } } /** * The error type when there's an eslintrc-style plugins array found. */ class IncompatiblePluginsError extends Error { - - /** - * Creates a new instance. - * @param {Array} plugins The plugins array. - */ - constructor(plugins) { - super("This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); - this.messageTemplate = "eslintrc-plugins"; - this.messageData = { plugins }; - } + /** + * Creates a new instance. + * @param {Array} plugins The plugins array. + */ + constructor(plugins) { + super( + "This appears to be in eslintrc format (array of strings) rather than flat config format (object).", + ); + this.messageTemplate = "eslintrc-plugins"; + this.messageData = { plugins }; + } } - //----------------------------------------------------------------------------- // Low-Level Schemas //----------------------------------------------------------------------------- /** @type {ObjectPropertySchema} */ const booleanSchema = { - merge: "replace", - validate: "boolean" + merge: "replace", + validate: "boolean", }; const ALLOWED_SEVERITIES = new Set(["error", "warn", "off", 2, 1, 0]); /** @type {ObjectPropertySchema} */ const disableDirectiveSeveritySchema = { - merge(first, second) { - const value = second === void 0 ? first : second; - - if (typeof value === "boolean") { - return value ? "warn" : "off"; - } - - return normalizeSeverityToNumber(value); - }, - validate(value) { - if (!(ALLOWED_SEVERITIES.has(value) || typeof value === "boolean")) { - throw new TypeError("Expected one of: \"error\", \"warn\", \"off\", 0, 1, 2, or a boolean."); - } - } + merge(first, second) { + const value = second === void 0 ? first : second; + + if (typeof value === "boolean") { + return value ? "warn" : "off"; + } + + return normalizeSeverityToNumber(value); + }, + validate(value) { + if (!(ALLOWED_SEVERITIES.has(value) || typeof value === "boolean")) { + throw new TypeError( + 'Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.', + ); + } + }, }; /** @type {ObjectPropertySchema} */ const unusedInlineConfigsSeveritySchema = { - merge(first, second) { - const value = second === void 0 ? first : second; - - return normalizeSeverityToNumber(value); - }, - validate(value) { - if (!ALLOWED_SEVERITIES.has(value)) { - throw new TypeError("Expected one of: \"error\", \"warn\", \"off\", 0, 1, or 2."); - } - } + merge(first, second) { + const value = second === void 0 ? first : second; + + return normalizeSeverityToNumber(value); + }, + validate(value) { + if (!ALLOWED_SEVERITIES.has(value)) { + throw new TypeError( + 'Expected one of: "error", "warn", "off", 0, 1, or 2.', + ); + } + }, }; /** @type {ObjectPropertySchema} */ const deepObjectAssignSchema = { - merge(first = {}, second = {}) { - return deepMerge(first, second); - }, - validate: "object" + merge(first = {}, second = {}) { + return deepMerge(first, second); + }, + validate: "object", }; - //----------------------------------------------------------------------------- // High-Level Schemas //----------------------------------------------------------------------------- /** @type {ObjectPropertySchema} */ const languageOptionsSchema = { - merge(first = {}, second = {}) { - - const result = deepMerge(first, second); - - for (const [key, value] of Object.entries(result)) { - - /* - * Special case: Because the `parser` property is an object, it should - * not be deep merged. Instead, it should be replaced if it exists in - * the second object. To make this more generic, we just check for - * objects with methods and replace them if they exist in the second - * object. - */ - if (isNonArrayObject(value)) { - if (hasMethod(value)) { - result[key] = second[key] ?? first[key]; - continue; - } - - // for other objects, make sure we aren't reusing the same object - result[key] = { ...result[key] }; - continue; - } - - } - - return result; - }, - validate: "object" + merge(first = {}, second = {}) { + const result = deepMerge(first, second); + + for (const [key, value] of Object.entries(result)) { + /* + * Special case: Because the `parser` property is an object, it should + * not be deep merged. Instead, it should be replaced if it exists in + * the second object. To make this more generic, we just check for + * objects with methods and replace them if they exist in the second + * object. + */ + if (isNonArrayObject(value)) { + if (hasMethod(value)) { + result[key] = second[key] ?? first[key]; + continue; + } + + // for other objects, make sure we aren't reusing the same object + result[key] = { ...result[key] }; + continue; + } + } + + return result; + }, + validate: "object", }; /** @type {ObjectPropertySchema} */ const languageSchema = { - merge: "replace", - validate: assertIsPluginMemberName + merge: "replace", + validate: assertIsPluginMemberName, }; /** @type {ObjectPropertySchema} */ const pluginsSchema = { - merge(first = {}, second = {}) { - const keys = new Set([...Object.keys(first), ...Object.keys(second)]); - const result = {}; - - // manually validate that plugins are not redefined - for (const key of keys) { - - // avoid hairy edge case - if (key === "__proto__") { - continue; - } - - if (key in first && key in second && first[key] !== second[key]) { - throw new TypeError(`Cannot redefine plugin "${key}".`); - } - - result[key] = second[key] || first[key]; - } - - return result; - }, - validate(value) { - - // first check the value to be sure it's an object - if (value === null || typeof value !== "object") { - throw new TypeError("Expected an object."); - } - - // make sure it's not an array, which would mean eslintrc-style is used - if (Array.isArray(value)) { - throw new IncompatiblePluginsError(value); - } - - // second check the keys to make sure they are objects - for (const key of Object.keys(value)) { - - // avoid hairy edge case - if (key === "__proto__") { - continue; - } - - if (value[key] === null || typeof value[key] !== "object") { - throw new TypeError(`Key "${key}": Expected an object.`); - } - } - } + merge(first = {}, second = {}) { + const keys = new Set([...Object.keys(first), ...Object.keys(second)]); + const result = {}; + + // manually validate that plugins are not redefined + for (const key of keys) { + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (key in first && key in second && first[key] !== second[key]) { + throw new TypeError(`Cannot redefine plugin "${key}".`); + } + + result[key] = second[key] || first[key]; + } + + return result; + }, + validate(value) { + // first check the value to be sure it's an object + if (value === null || typeof value !== "object") { + throw new TypeError("Expected an object."); + } + + // make sure it's not an array, which would mean eslintrc-style is used + if (Array.isArray(value)) { + throw new IncompatiblePluginsError(value); + } + + // second check the keys to make sure they are objects + for (const key of Object.keys(value)) { + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (value[key] === null || typeof value[key] !== "object") { + throw new TypeError(`Key "${key}": Expected an object.`); + } + } + }, }; /** @type {ObjectPropertySchema} */ const processorSchema = { - merge: "replace", - validate(value) { - if (typeof value === "string") { - assertIsPluginMemberName(value); - } else if (value && typeof value === "object") { - if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") { - throw new TypeError("Object must have a preprocess() and a postprocess() method."); - } - } else { - throw new TypeError("Expected an object or a string."); - } - } + merge: "replace", + validate(value) { + if (typeof value === "string") { + assertIsPluginMemberName(value); + } else if (value && typeof value === "object") { + if ( + typeof value.preprocess !== "function" || + typeof value.postprocess !== "function" + ) { + throw new TypeError( + "Object must have a preprocess() and a postprocess() method.", + ); + } + } else { + throw new TypeError("Expected an object or a string."); + } + }, }; /** @type {ObjectPropertySchema} */ const rulesSchema = { - merge(first = {}, second = {}) { - - const result = { - ...first, - ...second - }; - - - for (const ruleId of Object.keys(result)) { - - try { - - // avoid hairy edge case - if (ruleId === "__proto__") { - - /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */ - delete result.__proto__; - continue; - } - - result[ruleId] = normalizeRuleOptions(result[ruleId]); - - /* - * If either rule config is missing, then the correct - * config is already present and we just need to normalize - * the severity. - */ - if (!(ruleId in first) || !(ruleId in second)) { - continue; - } - - const firstRuleOptions = normalizeRuleOptions(first[ruleId]); - const secondRuleOptions = normalizeRuleOptions(second[ruleId]); - - /* - * If the second rule config only has a severity (length of 1), - * then use that severity and keep the rest of the options from - * the first rule config. - */ - if (secondRuleOptions.length === 1) { - result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)]; - continue; - } - - /* - * In any other situation, then the second rule config takes - * precedence. That means the value at `result[ruleId]` is - * already correct and no further work is necessary. - */ - } catch (ex) { - throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex }); - } - - } - - return result; - - - }, - - validate(value) { - assertIsObject(value); - - /* - * We are not checking the rule schema here because there is no - * guarantee that the rule definition is present at this point. Instead - * we wait and check the rule schema during the finalization step - * of calculating a config. - */ - for (const ruleId of Object.keys(value)) { - - // avoid hairy edge case - if (ruleId === "__proto__") { - continue; - } - - const ruleOptions = value[ruleId]; - - assertIsRuleOptions(ruleId, ruleOptions); - - if (Array.isArray(ruleOptions)) { - assertIsRuleSeverity(ruleId, ruleOptions[0]); - } else { - assertIsRuleSeverity(ruleId, ruleOptions); - } - } - } + merge(first = {}, second = {}) { + const result = { + ...first, + ...second, + }; + + for (const ruleId of Object.keys(result)) { + try { + // avoid hairy edge case + if (ruleId === "__proto__") { + /* eslint-disable-next-line no-proto -- Though deprecated, may still be present */ + delete result.__proto__; + continue; + } + + result[ruleId] = normalizeRuleOptions(result[ruleId]); + + /* + * If either rule config is missing, then the correct + * config is already present and we just need to normalize + * the severity. + */ + if (!(ruleId in first) || !(ruleId in second)) { + continue; + } + + const firstRuleOptions = normalizeRuleOptions(first[ruleId]); + const secondRuleOptions = normalizeRuleOptions(second[ruleId]); + + /* + * If the second rule config only has a severity (length of 1), + * then use that severity and keep the rest of the options from + * the first rule config. + */ + if (secondRuleOptions.length === 1) { + result[ruleId] = [ + secondRuleOptions[0], + ...firstRuleOptions.slice(1), + ]; + continue; + } + + /* + * In any other situation, then the second rule config takes + * precedence. That means the value at `result[ruleId]` is + * already correct and no further work is necessary. + */ + } catch (ex) { + throw new Error(`Key "${ruleId}": ${ex.message}`, { + cause: ex, + }); + } + } + + return result; + }, + + validate(value) { + assertIsObject(value); + + /* + * We are not checking the rule schema here because there is no + * guarantee that the rule definition is present at this point. Instead + * we wait and check the rule schema during the finalization step + * of calculating a config. + */ + for (const ruleId of Object.keys(value)) { + // avoid hairy edge case + if (ruleId === "__proto__") { + continue; + } + + const ruleOptions = value[ruleId]; + + assertIsRuleOptions(ruleId, ruleOptions); + + if (Array.isArray(ruleOptions)) { + assertIsRuleSeverity(ruleId, ruleOptions[0]); + } else { + assertIsRuleSeverity(ruleId, ruleOptions); + } + } + }, }; /** @@ -534,25 +540,25 @@ const rulesSchema = { * @returns {ObjectPropertySchema} The schema. */ function createEslintrcErrorSchema(key) { - return { - merge: "replace", - validate() { - throw new IncompatibleKeyError(key); - } - }; + return { + merge: "replace", + validate() { + throw new IncompatibleKeyError(key); + }, + }; } const eslintrcKeys = [ - "env", - "extends", - "globals", - "ignorePatterns", - "noInlineConfig", - "overrides", - "parser", - "parserOptions", - "reportUnusedDisableDirectives", - "root" + "env", + "extends", + "globals", + "ignorePatterns", + "noInlineConfig", + "overrides", + "parser", + "parserOptions", + "reportUnusedDisableDirectives", + "root", ]; //----------------------------------------------------------------------------- @@ -560,24 +566,25 @@ const eslintrcKeys = [ //----------------------------------------------------------------------------- const flatConfigSchema = { - - // eslintrc-style keys that should always error - ...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])), - - // flat config keys - settings: deepObjectAssignSchema, - linterOptions: { - schema: { - noInlineConfig: booleanSchema, - reportUnusedDisableDirectives: disableDirectiveSeveritySchema, - reportUnusedInlineConfigs: unusedInlineConfigsSeveritySchema - } - }, - language: languageSchema, - languageOptions: languageOptionsSchema, - processor: processorSchema, - plugins: pluginsSchema, - rules: rulesSchema + // eslintrc-style keys that should always error + ...Object.fromEntries( + eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)]), + ), + + // flat config keys + settings: deepObjectAssignSchema, + linterOptions: { + schema: { + noInlineConfig: booleanSchema, + reportUnusedDisableDirectives: disableDirectiveSeveritySchema, + reportUnusedInlineConfigs: unusedInlineConfigsSeveritySchema, + }, + }, + language: languageSchema, + languageOptions: languageOptionsSchema, + processor: processorSchema, + plugins: pluginsSchema, + rules: rulesSchema, }; //----------------------------------------------------------------------------- @@ -585,7 +592,7 @@ const flatConfigSchema = { //----------------------------------------------------------------------------- module.exports = { - flatConfigSchema, - hasMethod, - assertIsRuleSeverity + flatConfigSchema, + hasMethod, + assertIsRuleSeverity, }; diff --git a/lib/config/rule-validator.js b/lib/config/rule-validator.js index 3f8462d0a068..c4910a16572d 100644 --- a/lib/config/rule-validator.js +++ b/lib/config/rule-validator.js @@ -12,9 +12,9 @@ const ajvImport = require("../shared/ajv"); const ajv = ajvImport(); const { - parseRuleId, - getRuleFromConfig, - getRuleOptionsSchema + parseRuleId, + getRuleFromConfig, + getRuleOptionsSchema, } = require("./flat-config-helpers"); const ruleReplacements = require("../../conf/replacements.json"); @@ -32,67 +32,63 @@ const ruleReplacements = require("../../conf/replacements.json"); * @returns {void} */ function throwRuleNotFoundError({ pluginName, ruleName }, config) { + const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`; - const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`; + const errorMessageHeader = `Key "rules": Key "${ruleId}"`; - const errorMessageHeader = `Key "rules": Key "${ruleId}"`; + let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}" in configuration.`; - let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}" in configuration.`; + const missingPluginErrorMessage = errorMessage; - const missingPluginErrorMessage = errorMessage; + // if the plugin exists then we need to check if the rule exists + if (config.plugins && config.plugins[pluginName]) { + const replacementRuleName = ruleReplacements.rules[ruleName]; - // if the plugin exists then we need to check if the rule exists - if (config.plugins && config.plugins[pluginName]) { - const replacementRuleName = ruleReplacements.rules[ruleName]; + if (pluginName === "@" && replacementRuleName) { + errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`; + } else { + errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`; - if (pluginName === "@" && replacementRuleName) { + // otherwise, let's see if we can find the rule name elsewhere + for (const [otherPluginName, otherPlugin] of Object.entries( + config.plugins, + )) { + if (otherPlugin.rules && otherPlugin.rules[ruleName]) { + errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`; + break; + } + } + } - errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`; + // falls through to throw error + } - } else { + const error = new TypeError(errorMessage); - errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`; + if (errorMessage === missingPluginErrorMessage) { + error.messageTemplate = "config-plugin-missing"; + error.messageData = { pluginName, ruleId }; + } - // otherwise, let's see if we can find the rule name elsewhere - for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) { - if (otherPlugin.rules && otherPlugin.rules[ruleName]) { - errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`; - break; - } - } - - } - - // falls through to throw error - } - - const error = new TypeError(errorMessage); - - if (errorMessage === missingPluginErrorMessage) { - error.messageTemplate = "config-plugin-missing"; - error.messageData = { pluginName, ruleId }; - } - - throw error; + throw error; } /** * The error type when a rule has an invalid `meta.schema`. */ class InvalidRuleOptionsSchemaError extends Error { - - /** - * Creates a new instance. - * @param {string} ruleId Id of the rule that has an invalid `meta.schema`. - * @param {Error} processingError Error caught while processing the `meta.schema`. - */ - constructor(ruleId, processingError) { - super( - `Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`, - { cause: processingError } - ); - this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA"; - } + /** + * Creates a new instance. + * @param {string} ruleId Id of the rule that has an invalid `meta.schema`. + * @param {Error} processingError Error caught while processing the `meta.schema`. + */ + constructor(ruleId, processingError) { + super( + `Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`, + { cause: processingError }, + ); + this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA"; + } } //----------------------------------------------------------------------------- @@ -103,102 +99,101 @@ class InvalidRuleOptionsSchemaError extends Error { * Implements validation functionality for the rules portion of a config. */ class RuleValidator { - - /** - * Creates a new instance. - */ - constructor() { - - /** - * A collection of compiled validators for rules that have already - * been validated. - * @type {WeakMap} - */ - this.validators = new WeakMap(); - } - - /** - * Validates all of the rule configurations in a config against each - * rule's schema. - * @param {Object} config The full config to validate. This object must - * contain both the rules section and the plugins section. - * @returns {void} - * @throws {Error} If a rule's configuration does not match its schema. - */ - validate(config) { - - if (!config.rules) { - return; - } - - for (const [ruleId, ruleOptions] of Object.entries(config.rules)) { - - // check for edge case - if (ruleId === "__proto__") { - continue; - } - - /* - * If a rule is disabled, we don't do any validation. This allows - * users to safely set any value to 0 or "off" without worrying - * that it will cause a validation error. - * - * Note: ruleOptions is always an array at this point because - * this validation occurs after FlatConfigArray has merged and - * normalized values. - */ - if (ruleOptions[0] === 0) { - continue; - } - - const rule = getRuleFromConfig(ruleId, config); - - if (!rule) { - throwRuleNotFoundError(parseRuleId(ruleId), config); - } - - // Precompile and cache validator the first time - if (!this.validators.has(rule)) { - try { - const schema = getRuleOptionsSchema(rule); - - if (schema) { - this.validators.set(rule, ajv.compile(schema)); - } - } catch (err) { - throw new InvalidRuleOptionsSchemaError(ruleId, err); - } - } - - const validateRule = this.validators.get(rule); - - if (validateRule) { - - validateRule(ruleOptions.slice(1)); - - if (validateRule.errors) { - throw new Error(`Key "rules": Key "${ruleId}":\n${ - validateRule.errors.map( - error => { - if ( - error.keyword === "additionalProperties" && - error.schema === false && - typeof error.parentSchema?.properties === "object" && - typeof error.params?.additionalProperty === "string" - ) { - const expectedProperties = Object.keys(error.parentSchema.properties).map(property => `"${property}"`); - - return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`; - } - - return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`; - } - ).join("") - }`); - } - } - } - } + /** + * Creates a new instance. + */ + constructor() { + /** + * A collection of compiled validators for rules that have already + * been validated. + * @type {WeakMap} + */ + this.validators = new WeakMap(); + } + + /** + * Validates all of the rule configurations in a config against each + * rule's schema. + * @param {Object} config The full config to validate. This object must + * contain both the rules section and the plugins section. + * @returns {void} + * @throws {Error} If a rule's configuration does not match its schema. + */ + validate(config) { + if (!config.rules) { + return; + } + + for (const [ruleId, ruleOptions] of Object.entries(config.rules)) { + // check for edge case + if (ruleId === "__proto__") { + continue; + } + + /* + * If a rule is disabled, we don't do any validation. This allows + * users to safely set any value to 0 or "off" without worrying + * that it will cause a validation error. + * + * Note: ruleOptions is always an array at this point because + * this validation occurs after FlatConfigArray has merged and + * normalized values. + */ + if (ruleOptions[0] === 0) { + continue; + } + + const rule = getRuleFromConfig(ruleId, config); + + if (!rule) { + throwRuleNotFoundError(parseRuleId(ruleId), config); + } + + // Precompile and cache validator the first time + if (!this.validators.has(rule)) { + try { + const schema = getRuleOptionsSchema(rule); + + if (schema) { + this.validators.set(rule, ajv.compile(schema)); + } + } catch (err) { + throw new InvalidRuleOptionsSchemaError(ruleId, err); + } + } + + const validateRule = this.validators.get(rule); + + if (validateRule) { + validateRule(ruleOptions.slice(1)); + + if (validateRule.errors) { + throw new Error( + `Key "rules": Key "${ruleId}":\n${validateRule.errors + .map(error => { + if ( + error.keyword === "additionalProperties" && + error.schema === false && + typeof error.parentSchema?.properties === + "object" && + typeof error.params?.additionalProperty === + "string" + ) { + const expectedProperties = Object.keys( + error.parentSchema.properties, + ).map(property => `"${property}"`); + + return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`; + } + + return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`; + }) + .join("")}`, + ); + } + } + } + } } exports.RuleValidator = RuleValidator; diff --git a/lib/eslint/eslint-helpers.js b/lib/eslint/eslint-helpers.js index c84451004290..48bb4d0e99e3 100644 --- a/lib/eslint/eslint-helpers.js +++ b/lib/eslint/eslint-helpers.js @@ -43,58 +43,58 @@ const MINIMATCH_OPTIONS = { dot: true }; * The error type when no files match a glob. */ class NoFilesFoundError extends Error { - - /** - * @param {string} pattern The glob pattern which was not found. - * @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled. - */ - constructor(pattern, globEnabled) { - super(`No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`); - this.messageTemplate = "file-not-found"; - this.messageData = { pattern, globDisabled: !globEnabled }; - } + /** + * @param {string} pattern The glob pattern which was not found. + * @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled. + */ + constructor(pattern, globEnabled) { + super( + `No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`, + ); + this.messageTemplate = "file-not-found"; + this.messageData = { pattern, globDisabled: !globEnabled }; + } } /** * The error type when a search fails to match multiple patterns. */ class UnmatchedSearchPatternsError extends Error { - - /** - * @param {Object} options The options for the error. - * @param {string} options.basePath The directory that was searched. - * @param {Array} options.unmatchedPatterns The glob patterns - * which were not found. - * @param {Array} options.patterns The glob patterns that were - * searched. - * @param {Array} options.rawPatterns The raw glob patterns that - * were searched. - */ - constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) { - super(`No files matching '${rawPatterns}' in '${basePath}' were found.`); - this.basePath = basePath; - this.unmatchedPatterns = unmatchedPatterns; - this.patterns = patterns; - this.rawPatterns = rawPatterns; - } + /** + * @param {Object} options The options for the error. + * @param {string} options.basePath The directory that was searched. + * @param {Array} options.unmatchedPatterns The glob patterns + * which were not found. + * @param {Array} options.patterns The glob patterns that were + * searched. + * @param {Array} options.rawPatterns The raw glob patterns that + * were searched. + */ + constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) { + super( + `No files matching '${rawPatterns}' in '${basePath}' were found.`, + ); + this.basePath = basePath; + this.unmatchedPatterns = unmatchedPatterns; + this.patterns = patterns; + this.rawPatterns = rawPatterns; + } } /** * The error type when there are files matched by a glob, but all of them have been ignored. */ class AllFilesIgnoredError extends Error { - - /** - * @param {string} pattern The glob pattern which was not found. - */ - constructor(pattern) { - super(`All files matched by '${pattern}' are ignored.`); - this.messageTemplate = "all-matched-files-ignored"; - this.messageData = { pattern }; - } + /** + * @param {string} pattern The glob pattern which was not found. + */ + constructor(pattern) { + super(`All files matched by '${pattern}' are ignored.`); + this.messageTemplate = "all-matched-files-ignored"; + this.messageData = { pattern }; + } } - //----------------------------------------------------------------------------- // General Helpers //----------------------------------------------------------------------------- @@ -105,7 +105,7 @@ class AllFilesIgnoredError extends Error { * @returns {boolean} `true` if `value` is a non-empty string. */ function isNonEmptyString(value) { - return typeof value === "string" && value.trim() !== ""; + return typeof value === "string" && value.trim() !== ""; } /** @@ -114,7 +114,9 @@ function isNonEmptyString(value) { * @returns {boolean} `true` if `value` is an array of non-empty strings. */ function isArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.length && value.every(isNonEmptyString); + return ( + Array.isArray(value) && value.length && value.every(isNonEmptyString) + ); } /** @@ -124,7 +126,7 @@ function isArrayOfNonEmptyString(value) { * strings. */ function isEmptyArrayOrArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.every(isNonEmptyString); + return Array.isArray(value) && value.every(isNonEmptyString); } //----------------------------------------------------------------------------- @@ -137,7 +139,7 @@ function isEmptyArrayOrArrayOfNonEmptyString(value) { * @returns {string} The pattern with slashes normalized. */ function normalizeToPosix(pattern) { - return pattern.replace(/\\/gu, "/"); + return pattern.replace(/\\/gu, "/"); } /** @@ -146,10 +148,9 @@ function normalizeToPosix(pattern) { * @returns {boolean} `true` if the string is a glob pattern. */ function isGlobPattern(pattern) { - return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern); + return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern); } - /** * Determines if a given glob pattern will return any results. * Used primarily to help with useful error messages. @@ -159,38 +160,39 @@ function isGlobPattern(pattern) { * @returns {Promise} True if there is a glob match, false if not. */ async function globMatch({ basePath, pattern }) { - - let found = false; - const { hfs } = await import("@humanfs/node"); - const patternToUse = normalizeToPosix(path.relative(basePath, pattern)); - - const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS); - - const walkSettings = { - - directoryFilter(entry) { - return !found && matcher.match(entry.path, true); - }, - - entryFilter(entry) { - if (found || entry.isDirectory) { - return false; - } - - if (matcher.match(entry.path)) { - found = true; - return true; - } - - return false; - } - }; - - if (await hfs.isDirectory(basePath)) { - return hfs.walk(basePath, walkSettings).next().then(() => found); - } - - return found; + let found = false; + const { hfs } = await import("@humanfs/node"); + const patternToUse = normalizeToPosix(path.relative(basePath, pattern)); + + const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS); + + const walkSettings = { + directoryFilter(entry) { + return !found && matcher.match(entry.path, true); + }, + + entryFilter(entry) { + if (found || entry.isDirectory) { + return false; + } + + if (matcher.match(entry.path)) { + found = true; + return true; + } + + return false; + }, + }; + + if (await hfs.isDirectory(basePath)) { + return hfs + .walk(basePath, walkSettings) + .next() + .then(() => found); + } + + return found; } /** @@ -214,130 +216,128 @@ async function globMatch({ basePath, pattern }) { * match any files. */ async function globSearch({ - basePath, - patterns, - rawPatterns, - configLoader, - errorOnUnmatchedPattern + basePath, + patterns, + rawPatterns, + configLoader, + errorOnUnmatchedPattern, }) { - - if (patterns.length === 0) { - return []; - } - - /* - * In this section we are converting the patterns into Minimatch - * instances for performance reasons. Because we are doing the same - * matches repeatedly, it's best to compile those patterns once and - * reuse them multiple times. - * - * To do that, we convert any patterns with an absolute path into a - * relative path and normalize it to Posix-style slashes. We also keep - * track of the relative patterns to map them back to the original - * patterns, which we need in order to throw an error if there are any - * unmatched patterns. - */ - const relativeToPatterns = new Map(); - const matchers = patterns.map((pattern, i) => { - const patternToUse = normalizeToPosix(path.relative(basePath, pattern)); - - relativeToPatterns.set(patternToUse, patterns[i]); - - return new Minimatch(patternToUse, MINIMATCH_OPTIONS); - }); - - /* - * We track unmatched patterns because we may want to throw an error when - * they occur. To start, this set is initialized with all of the patterns. - * Every time a match occurs, the pattern is removed from the set, making - * it easy to tell if we have any unmatched patterns left at the end of - * search. - */ - const unmatchedPatterns = new Set([...relativeToPatterns.keys()]); - const { hfs } = await import("@humanfs/node"); - - const walk = hfs.walk( - basePath, - { - async directoryFilter(entry) { - - if (!matchers.some(matcher => matcher.match(entry.path, true))) { - return false; - } - - const absolutePath = path.resolve(basePath, entry.path); - const configs = await configLoader.loadConfigArrayForDirectory(absolutePath); - - return !configs.isDirectoryIgnored(absolutePath); - }, - async entryFilter(entry) { - const absolutePath = path.resolve(basePath, entry.path); - - // entries may be directories or files so filter out directories - if (entry.isDirectory) { - return false; - } - - const configs = await configLoader.loadConfigArrayForFile(absolutePath); - const config = configs.getConfig(absolutePath); - - /* - * Optimization: We need to track when patterns are left unmatched - * and so we use `unmatchedPatterns` to do that. There is a bit of - * complexity here because the same file can be matched by more than - * one pattern. So, when we start, we actually need to test every - * pattern against every file. Once we know there are no remaining - * unmatched patterns, then we can switch to just looking for the - * first matching pattern for improved speed. - */ - const matchesPattern = unmatchedPatterns.size > 0 - ? matchers.reduce((previousValue, matcher) => { - const pathMatches = matcher.match(entry.path); - - /* - * We updated the unmatched patterns set only if the path - * matches and the file has a config. If the file has no - * config, that means there wasn't a match for the - * pattern so it should not be removed. - * - * Performance note: `getConfig()` aggressively caches - * results so there is no performance penalty for calling - * it multiple times with the same argument. - */ - if (pathMatches && config) { - unmatchedPatterns.delete(matcher.pattern); - } - - return pathMatches || previousValue; - }, false) - : matchers.some(matcher => matcher.match(entry.path)); - - return matchesPattern && config !== void 0; - } - } - ); - - const filePaths = []; - - if (await hfs.isDirectory(basePath)) { - for await (const entry of walk) { - filePaths.push(path.resolve(basePath, entry.path)); - } - } - - // now check to see if we have any unmatched patterns - if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) { - throw new UnmatchedSearchPatternsError({ - basePath, - unmatchedPatterns: [...unmatchedPatterns].map( - pattern => relativeToPatterns.get(pattern) - ), - patterns, - rawPatterns - }); - } - - return filePaths; + if (patterns.length === 0) { + return []; + } + + /* + * In this section we are converting the patterns into Minimatch + * instances for performance reasons. Because we are doing the same + * matches repeatedly, it's best to compile those patterns once and + * reuse them multiple times. + * + * To do that, we convert any patterns with an absolute path into a + * relative path and normalize it to Posix-style slashes. We also keep + * track of the relative patterns to map them back to the original + * patterns, which we need in order to throw an error if there are any + * unmatched patterns. + */ + const relativeToPatterns = new Map(); + const matchers = patterns.map((pattern, i) => { + const patternToUse = normalizeToPosix(path.relative(basePath, pattern)); + + relativeToPatterns.set(patternToUse, patterns[i]); + + return new Minimatch(patternToUse, MINIMATCH_OPTIONS); + }); + + /* + * We track unmatched patterns because we may want to throw an error when + * they occur. To start, this set is initialized with all of the patterns. + * Every time a match occurs, the pattern is removed from the set, making + * it easy to tell if we have any unmatched patterns left at the end of + * search. + */ + const unmatchedPatterns = new Set([...relativeToPatterns.keys()]); + const { hfs } = await import("@humanfs/node"); + + const walk = hfs.walk(basePath, { + async directoryFilter(entry) { + if (!matchers.some(matcher => matcher.match(entry.path, true))) { + return false; + } + + const absolutePath = path.resolve(basePath, entry.path); + const configs = + await configLoader.loadConfigArrayForDirectory(absolutePath); + + return !configs.isDirectoryIgnored(absolutePath); + }, + async entryFilter(entry) { + const absolutePath = path.resolve(basePath, entry.path); + + // entries may be directories or files so filter out directories + if (entry.isDirectory) { + return false; + } + + const configs = + await configLoader.loadConfigArrayForFile(absolutePath); + const config = configs.getConfig(absolutePath); + + /* + * Optimization: We need to track when patterns are left unmatched + * and so we use `unmatchedPatterns` to do that. There is a bit of + * complexity here because the same file can be matched by more than + * one pattern. So, when we start, we actually need to test every + * pattern against every file. Once we know there are no remaining + * unmatched patterns, then we can switch to just looking for the + * first matching pattern for improved speed. + */ + const matchesPattern = + unmatchedPatterns.size > 0 + ? matchers.reduce((previousValue, matcher) => { + const pathMatches = matcher.match(entry.path); + + /* + * We updated the unmatched patterns set only if the path + * matches and the file has a config. If the file has no + * config, that means there wasn't a match for the + * pattern so it should not be removed. + * + * Performance note: `getConfig()` aggressively caches + * results so there is no performance penalty for calling + * it multiple times with the same argument. + */ + if (pathMatches && config) { + unmatchedPatterns.delete(matcher.pattern); + } + + return pathMatches || previousValue; + }, false) + : matchers.some(matcher => matcher.match(entry.path)); + + return matchesPattern && config !== void 0; + }, + }); + + const filePaths = []; + + if (await hfs.isDirectory(basePath)) { + for await (const entry of walk) { + filePaths.push(path.resolve(basePath, entry.path)); + } + } + + // now check to see if we have any unmatched patterns + if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) { + throw new UnmatchedSearchPatternsError({ + basePath, + unmatchedPatterns: [...unmatchedPatterns].map(pattern => + relativeToPatterns.get(pattern), + ), + patterns, + rawPatterns, + }); + } + + return filePaths; } /** @@ -358,26 +358,25 @@ async function globSearch({ * matches some files when there are no ignores. */ async function throwErrorForUnmatchedPatterns({ - basePath, - patterns, - rawPatterns, - unmatchedPatterns + basePath, + patterns, + rawPatterns, + unmatchedPatterns, }) { + const pattern = unmatchedPatterns[0]; + const rawPattern = rawPatterns[patterns.indexOf(pattern)]; - const pattern = unmatchedPatterns[0]; - const rawPattern = rawPatterns[patterns.indexOf(pattern)]; + const patternHasMatch = await globMatch({ + basePath, + pattern, + }); - const patternHasMatch = await globMatch({ - basePath, - pattern - }); + if (patternHasMatch) { + throw new AllFilesIgnoredError(rawPattern); + } - if (patternHasMatch) { - throw new AllFilesIgnoredError(rawPattern); - } - - // if we get here there are truly no matches - throw new NoFilesFoundError(rawPattern, true); + // if we get here there are truly no matches + throw new NoFilesFoundError(rawPattern, true); } /** @@ -392,69 +391,71 @@ async function throwErrorForUnmatchedPatterns({ * @returns {Promise>} An array of matching file paths * or an empty array if there are no matches. */ -async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern }) { - - /* - * For convenience, we normalized the search map into an array of objects. - * Next, we filter out all searches that have no patterns. This happens - * primarily for the cwd, which is prepopulated in the searches map as an - * optimization. However, if it has no patterns, it means all patterns - * occur outside of the cwd and we can safely filter out that search. - */ - const normalizedSearches = [...searches].map( - ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns }) - ).filter(({ patterns }) => patterns.length > 0); - - const results = await Promise.allSettled( - normalizedSearches.map( - ({ basePath, patterns, rawPatterns }) => globSearch({ - basePath, - patterns, - rawPatterns, - configLoader, - errorOnUnmatchedPattern - }) - ) - ); - - /* - * The first loop handles errors from the glob searches. Since we can't - * use `await` inside `flatMap`, we process errors separately in this loop. - * This results in two iterations over `results`, but since the length is - * less than or equal to the number of globs and directories passed on the - * command line, the performance impact should be minimal. - */ - for (let i = 0; i < results.length; i++) { - - const result = results[i]; - const currentSearch = normalizedSearches[i]; - - if (result.status === "fulfilled") { - continue; - } - - // if we make it here then there was an error - const error = result.reason; - - // unexpected errors should be re-thrown - if (!error.basePath) { - throw error; - } - - if (errorOnUnmatchedPattern) { - - await throwErrorForUnmatchedPatterns({ - ...currentSearch, - unmatchedPatterns: error.unmatchedPatterns - }); - - } - - } - - // second loop for `fulfulled` results - return results.flatMap(result => result.value); - +async function globMultiSearch({ + searches, + configLoader, + errorOnUnmatchedPattern, +}) { + /* + * For convenience, we normalized the search map into an array of objects. + * Next, we filter out all searches that have no patterns. This happens + * primarily for the cwd, which is prepopulated in the searches map as an + * optimization. However, if it has no patterns, it means all patterns + * occur outside of the cwd and we can safely filter out that search. + */ + const normalizedSearches = [...searches] + .map(([basePath, { patterns, rawPatterns }]) => ({ + basePath, + patterns, + rawPatterns, + })) + .filter(({ patterns }) => patterns.length > 0); + + const results = await Promise.allSettled( + normalizedSearches.map(({ basePath, patterns, rawPatterns }) => + globSearch({ + basePath, + patterns, + rawPatterns, + configLoader, + errorOnUnmatchedPattern, + }), + ), + ); + + /* + * The first loop handles errors from the glob searches. Since we can't + * use `await` inside `flatMap`, we process errors separately in this loop. + * This results in two iterations over `results`, but since the length is + * less than or equal to the number of globs and directories passed on the + * command line, the performance impact should be minimal. + */ + for (let i = 0; i < results.length; i++) { + const result = results[i]; + const currentSearch = normalizedSearches[i]; + + if (result.status === "fulfilled") { + continue; + } + + // if we make it here then there was an error + const error = result.reason; + + // unexpected errors should be re-thrown + if (!error.basePath) { + throw error; + } + + if (errorOnUnmatchedPattern) { + await throwErrorForUnmatchedPatterns({ + ...currentSearch, + unmatchedPatterns: error.unmatchedPatterns, + }); + } + } + + // second loop for `fulfulled` results + return results.flatMap(result => result.value); } /** @@ -472,130 +473,122 @@ async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern * @throws {NoFilesFoundError} If no files matched the given patterns. */ async function findFiles({ - patterns, - globInputPaths, - cwd, - configLoader, - errorOnUnmatchedPattern + patterns, + globInputPaths, + cwd, + configLoader, + errorOnUnmatchedPattern, }) { - - const results = []; - const missingPatterns = []; - let globbyPatterns = []; - let rawPatterns = []; - const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]); - - /* - * This part is a bit involved because we need to account for - * the different ways that the patterns can match directories. - * For each different way, we need to decide if we should look - * for a config file or just use the default config. (Directories - * without a config file always use the default config.) - * - * Here are the cases: - * - * 1. A directory is passed directly (e.g., "subdir"). In this case, we - * can assume that the user intends to lint this directory and we should - * not look for a config file in the parent directory, because the only - * reason to do that would be to ignore this directory (which we already - * know we don't want to do). Instead, we use the default config until we - * get to the directory that was passed, at which point we start looking - * for config files again. - * - * 2. A dot (".") or star ("*"). In this case, we want to read - * the config file in the current directory because the user is - * explicitly asking to lint the current directory. Note that "." - * will traverse into subdirectories while "*" will not. - * - * 3. A directory is passed in the form of "subdir/subsubdir". - * In this case, we don't want to look for a config file in the - * parent directory ("subdir"). We can skip looking for a config - * file until `entry.depth` is greater than 1 because there's no - * way that the pattern can match `entry.path` yet. - * - * 4. A directory glob pattern is passed (e.g., "subd*"). We want - * this case to act like case 2 because it's unclear whether or not - * any particular directory is meant to be traversed. - * - * 5. A recursive glob pattern is passed (e.g., "**"). We want this - * case to act like case 2. - */ - - // check to see if we have explicit files and directories - const filePaths = patterns.map(filePath => path.resolve(cwd, filePath)); - const stats = await Promise.all( - filePaths.map( - filePath => fsp.stat(filePath).catch(() => { }) - ) - ); - - stats.forEach((stat, index) => { - - const filePath = filePaths[index]; - const pattern = normalizeToPosix(patterns[index]); - - if (stat) { - - // files are added directly to the list - if (stat.isFile()) { - results.push(filePath); - } - - // directories need extensions attached - if (stat.isDirectory()) { - - if (!searches.has(filePath)) { - searches.set(filePath, { patterns: [], rawPatterns: [] }); - } - ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath)); - - globbyPatterns.push(`${normalizeToPosix(filePath)}/**`); - rawPatterns.push(pattern); - } - - return; - } - - // save patterns for later use based on whether globs are enabled - if (globInputPaths && isGlobPattern(pattern)) { - - /* - * We are grouping patterns by their glob parent. This is done to - * make it easier to determine when a config file should be loaded. - */ - - const basePath = path.resolve(cwd, globParent(pattern)); - - if (!searches.has(basePath)) { - searches.set(basePath, { patterns: [], rawPatterns: [] }); - } - ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath)); - - globbyPatterns.push(filePath); - rawPatterns.push(pattern); - } else { - missingPatterns.push(pattern); - } - }); - - // there were patterns that didn't match anything, tell the user - if (errorOnUnmatchedPattern && missingPatterns.length) { - throw new NoFilesFoundError(missingPatterns[0], globInputPaths); - } - - // now we are safe to do the search - const globbyResults = await globMultiSearch({ - searches, - configLoader, - errorOnUnmatchedPattern - }); - - return [ - ...new Set([ - ...results, - ...globbyResults - ]) - ]; + const results = []; + const missingPatterns = []; + let globbyPatterns = []; + let rawPatterns = []; + const searches = new Map([ + [cwd, { patterns: globbyPatterns, rawPatterns: [] }], + ]); + + /* + * This part is a bit involved because we need to account for + * the different ways that the patterns can match directories. + * For each different way, we need to decide if we should look + * for a config file or just use the default config. (Directories + * without a config file always use the default config.) + * + * Here are the cases: + * + * 1. A directory is passed directly (e.g., "subdir"). In this case, we + * can assume that the user intends to lint this directory and we should + * not look for a config file in the parent directory, because the only + * reason to do that would be to ignore this directory (which we already + * know we don't want to do). Instead, we use the default config until we + * get to the directory that was passed, at which point we start looking + * for config files again. + * + * 2. A dot (".") or star ("*"). In this case, we want to read + * the config file in the current directory because the user is + * explicitly asking to lint the current directory. Note that "." + * will traverse into subdirectories while "*" will not. + * + * 3. A directory is passed in the form of "subdir/subsubdir". + * In this case, we don't want to look for a config file in the + * parent directory ("subdir"). We can skip looking for a config + * file until `entry.depth` is greater than 1 because there's no + * way that the pattern can match `entry.path` yet. + * + * 4. A directory glob pattern is passed (e.g., "subd*"). We want + * this case to act like case 2 because it's unclear whether or not + * any particular directory is meant to be traversed. + * + * 5. A recursive glob pattern is passed (e.g., "**"). We want this + * case to act like case 2. + */ + + // check to see if we have explicit files and directories + const filePaths = patterns.map(filePath => path.resolve(cwd, filePath)); + const stats = await Promise.all( + filePaths.map(filePath => fsp.stat(filePath).catch(() => {})), + ); + + stats.forEach((stat, index) => { + const filePath = filePaths[index]; + const pattern = normalizeToPosix(patterns[index]); + + if (stat) { + // files are added directly to the list + if (stat.isFile()) { + results.push(filePath); + } + + // directories need extensions attached + if (stat.isDirectory()) { + if (!searches.has(filePath)) { + searches.set(filePath, { patterns: [], rawPatterns: [] }); + } + ({ patterns: globbyPatterns, rawPatterns } = + searches.get(filePath)); + + globbyPatterns.push(`${normalizeToPosix(filePath)}/**`); + rawPatterns.push(pattern); + } + + return; + } + + // save patterns for later use based on whether globs are enabled + if (globInputPaths && isGlobPattern(pattern)) { + /* + * We are grouping patterns by their glob parent. This is done to + * make it easier to determine when a config file should be loaded. + */ + + const basePath = path.resolve(cwd, globParent(pattern)); + + if (!searches.has(basePath)) { + searches.set(basePath, { patterns: [], rawPatterns: [] }); + } + ({ patterns: globbyPatterns, rawPatterns } = + searches.get(basePath)); + + globbyPatterns.push(filePath); + rawPatterns.push(pattern); + } else { + missingPatterns.push(pattern); + } + }); + + // there were patterns that didn't match anything, tell the user + if (errorOnUnmatchedPattern && missingPatterns.length) { + throw new NoFilesFoundError(missingPatterns[0], globInputPaths); + } + + // now we are safe to do the search + const globbyResults = await globMultiSearch({ + searches, + configLoader, + errorOnUnmatchedPattern, + }); + + return [...new Set([...results, ...globbyResults])]; } //----------------------------------------------------------------------------- @@ -609,7 +602,7 @@ async function findFiles({ * @private */ function isErrorMessage(message) { - return message.severity === 2; + return message.severity === 2; } /** @@ -621,60 +614,72 @@ function isErrorMessage(message) { * @private */ function createIgnoreResult(filePath, baseDir, configStatus) { - let message; - - switch (configStatus) { - case "external": - message = "File ignored because outside of base path."; - break; - case "unconfigured": - message = "File ignored because no matching configuration was supplied."; - break; - default: - { - const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules"); - - if (isInNodeModules) { - 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."; - } else { - 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."; - } - } - break; - } - - return { - filePath, - messages: [ - { - ruleId: null, - fatal: false, - severity: 1, - message, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 0, - warningCount: 1, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; + let message; + + switch (configStatus) { + case "external": + message = "File ignored because outside of base path."; + break; + case "unconfigured": + message = + "File ignored because no matching configuration was supplied."; + break; + default: + { + const isInNodeModules = + baseDir && + path + .dirname(path.relative(baseDir, filePath)) + .split(path.sep) + .includes("node_modules"); + + if (isInNodeModules) { + 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.'; + } else { + 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.'; + } + } + break; + } + + return { + filePath, + messages: [ + { + ruleId: null, + fatal: false, + severity: 1, + message, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 0, + warningCount: 1, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }; } //----------------------------------------------------------------------------- // Options-related Helpers //----------------------------------------------------------------------------- - /** * Check if a given value is a valid fix type or not. * @param {any} x The value to check. * @returns {boolean} `true` if `x` is valid fix type. */ function isFixType(x) { - return x === "directive" || x === "problem" || x === "suggestion" || x === "layout"; + return ( + x === "directive" || + x === "problem" || + x === "suggestion" || + x === "layout" + ); } /** @@ -683,18 +688,18 @@ function isFixType(x) { * @returns {boolean} `true` if `x` is an array of fix types. */ function isFixTypeArray(x) { - return Array.isArray(x) && x.every(isFixType); + return Array.isArray(x) && x.every(isFixType); } /** * The error for invalid options. */ class ESLintInvalidOptionsError extends Error { - constructor(messages) { - super(`Invalid Options:\n- ${messages.join("\n- ")}`); - this.code = "ESLINT_INVALID_OPTIONS"; - Error.captureStackTrace(this, ESLintInvalidOptionsError); - } + constructor(messages) { + super(`Invalid Options:\n- ${messages.join("\n- ")}`); + this.code = "ESLINT_INVALID_OPTIONS"; + Error.captureStackTrace(this, ESLintInvalidOptionsError); + } } /** @@ -704,171 +709,200 @@ class ESLintInvalidOptionsError extends Error { * @returns {ESLintOptions} The normalized options. */ function processOptions({ - allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. - baseConfig = null, - cache = false, - cacheLocation = ".eslintcache", - cacheStrategy = "metadata", - cwd = process.cwd(), - errorOnUnmatchedPattern = true, - fix = false, - fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. - flags = [], - globInputPaths = true, - ignore = true, - ignorePatterns = null, - overrideConfig = null, - overrideConfigFile = null, - plugins = {}, - stats = false, - warnIgnored = true, - passOnNoPatterns = false, - ruleFilter = () => true, - ...unknownOptions + allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. + baseConfig = null, + cache = false, + cacheLocation = ".eslintcache", + cacheStrategy = "metadata", + cwd = process.cwd(), + errorOnUnmatchedPattern = true, + fix = false, + fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. + flags = [], + globInputPaths = true, + ignore = true, + ignorePatterns = null, + overrideConfig = null, + overrideConfigFile = null, + plugins = {}, + stats = false, + warnIgnored = true, + passOnNoPatterns = false, + ruleFilter = () => true, + ...unknownOptions }) { - const errors = []; - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length >= 1) { - errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); - if (unknownOptionKeys.includes("cacheFile")) { - errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); - } - if (unknownOptionKeys.includes("configFile")) { - errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); - } - if (unknownOptionKeys.includes("envs")) { - errors.push("'envs' has been removed."); - } - if (unknownOptionKeys.includes("extensions")) { - errors.push("'extensions' has been removed."); - } - if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) { - errors.push("'resolvePluginsRelativeTo' has been removed."); - } - if (unknownOptionKeys.includes("globals")) { - errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead."); - } - if (unknownOptionKeys.includes("ignorePath")) { - errors.push("'ignorePath' has been removed."); - } - if (unknownOptionKeys.includes("ignorePattern")) { - errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); - } - if (unknownOptionKeys.includes("parser")) { - errors.push("'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead."); - } - if (unknownOptionKeys.includes("parserOptions")) { - errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead."); - } - if (unknownOptionKeys.includes("rules")) { - errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); - } - if (unknownOptionKeys.includes("rulePaths")) { - errors.push("'rulePaths' has been removed. Please define your rules using plugins."); - } - if (unknownOptionKeys.includes("reportUnusedDisableDirectives")) { - errors.push("'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead."); - } - } - if (typeof allowInlineConfig !== "boolean") { - errors.push("'allowInlineConfig' must be a boolean."); - } - if (typeof baseConfig !== "object") { - errors.push("'baseConfig' must be an object or null."); - } - if (typeof cache !== "boolean") { - errors.push("'cache' must be a boolean."); - } - if (!isNonEmptyString(cacheLocation)) { - errors.push("'cacheLocation' must be a non-empty string."); - } - if ( - cacheStrategy !== "metadata" && - cacheStrategy !== "content" - ) { - errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); - } - if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { - errors.push("'cwd' must be an absolute path."); - } - if (typeof errorOnUnmatchedPattern !== "boolean") { - errors.push("'errorOnUnmatchedPattern' must be a boolean."); - } - if (typeof fix !== "boolean" && typeof fix !== "function") { - errors.push("'fix' must be a boolean or a function."); - } - if (fixTypes !== null && !isFixTypeArray(fixTypes)) { - errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(flags)) { - errors.push("'flags' must be an array of non-empty strings."); - } - if (typeof globInputPaths !== "boolean") { - errors.push("'globInputPaths' must be a boolean."); - } - if (typeof ignore !== "boolean") { - errors.push("'ignore' must be a boolean."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) { - errors.push("'ignorePatterns' must be an array of non-empty strings or null."); - } - if (typeof overrideConfig !== "object") { - errors.push("'overrideConfig' must be an object or null."); - } - if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) { - errors.push("'overrideConfigFile' must be a non-empty string, null, or true."); - } - if (typeof passOnNoPatterns !== "boolean") { - errors.push("'passOnNoPatterns' must be a boolean."); - } - if (typeof plugins !== "object") { - errors.push("'plugins' must be an object or null."); - } else if (plugins !== null && Object.keys(plugins).includes("")) { - errors.push("'plugins' must not include an empty string."); - } - if (Array.isArray(plugins)) { - errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); - } - if (typeof stats !== "boolean") { - errors.push("'stats' must be a boolean."); - } - if (typeof warnIgnored !== "boolean") { - errors.push("'warnIgnored' must be a boolean."); - } - if (typeof ruleFilter !== "function") { - errors.push("'ruleFilter' must be a function."); - } - if (errors.length > 0) { - throw new ESLintInvalidOptionsError(errors); - } - - return { - allowInlineConfig, - baseConfig, - cache, - cacheLocation, - cacheStrategy, - - // when overrideConfigFile is true that means don't do config file lookup - configFile: overrideConfigFile === true ? false : overrideConfigFile, - overrideConfig, - cwd: path.normalize(cwd), - errorOnUnmatchedPattern, - fix, - fixTypes, - flags: [...flags], - globInputPaths, - ignore, - ignorePatterns, - stats, - passOnNoPatterns, - warnIgnored, - ruleFilter - }; + const errors = []; + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length >= 1) { + errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); + if (unknownOptionKeys.includes("cacheFile")) { + errors.push( + "'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", + ); + } + if (unknownOptionKeys.includes("configFile")) { + errors.push( + "'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", + ); + } + if (unknownOptionKeys.includes("envs")) { + errors.push("'envs' has been removed."); + } + if (unknownOptionKeys.includes("extensions")) { + errors.push("'extensions' has been removed."); + } + if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) { + errors.push("'resolvePluginsRelativeTo' has been removed."); + } + if (unknownOptionKeys.includes("globals")) { + errors.push( + "'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.", + ); + } + if (unknownOptionKeys.includes("ignorePath")) { + errors.push("'ignorePath' has been removed."); + } + if (unknownOptionKeys.includes("ignorePattern")) { + errors.push( + "'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", + ); + } + if (unknownOptionKeys.includes("parser")) { + errors.push( + "'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead.", + ); + } + if (unknownOptionKeys.includes("parserOptions")) { + errors.push( + "'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead.", + ); + } + if (unknownOptionKeys.includes("rules")) { + errors.push( + "'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", + ); + } + if (unknownOptionKeys.includes("rulePaths")) { + errors.push( + "'rulePaths' has been removed. Please define your rules using plugins.", + ); + } + if (unknownOptionKeys.includes("reportUnusedDisableDirectives")) { + errors.push( + "'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead.", + ); + } + } + if (typeof allowInlineConfig !== "boolean") { + errors.push("'allowInlineConfig' must be a boolean."); + } + if (typeof baseConfig !== "object") { + errors.push("'baseConfig' must be an object or null."); + } + if (typeof cache !== "boolean") { + errors.push("'cache' must be a boolean."); + } + if (!isNonEmptyString(cacheLocation)) { + errors.push("'cacheLocation' must be a non-empty string."); + } + if (cacheStrategy !== "metadata" && cacheStrategy !== "content") { + errors.push('\'cacheStrategy\' must be any of "metadata", "content".'); + } + if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { + errors.push("'cwd' must be an absolute path."); + } + if (typeof errorOnUnmatchedPattern !== "boolean") { + errors.push("'errorOnUnmatchedPattern' must be a boolean."); + } + if (typeof fix !== "boolean" && typeof fix !== "function") { + errors.push("'fix' must be a boolean or a function."); + } + if (fixTypes !== null && !isFixTypeArray(fixTypes)) { + errors.push( + '\'fixTypes\' must be an array of any of "directive", "problem", "suggestion", and "layout".', + ); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(flags)) { + errors.push("'flags' must be an array of non-empty strings."); + } + if (typeof globInputPaths !== "boolean") { + errors.push("'globInputPaths' must be a boolean."); + } + if (typeof ignore !== "boolean") { + errors.push("'ignore' must be a boolean."); + } + if ( + !isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && + ignorePatterns !== null + ) { + errors.push( + "'ignorePatterns' must be an array of non-empty strings or null.", + ); + } + if (typeof overrideConfig !== "object") { + errors.push("'overrideConfig' must be an object or null."); + } + if ( + !isNonEmptyString(overrideConfigFile) && + overrideConfigFile !== null && + overrideConfigFile !== true + ) { + errors.push( + "'overrideConfigFile' must be a non-empty string, null, or true.", + ); + } + if (typeof passOnNoPatterns !== "boolean") { + errors.push("'passOnNoPatterns' must be a boolean."); + } + if (typeof plugins !== "object") { + errors.push("'plugins' must be an object or null."); + } else if (plugins !== null && Object.keys(plugins).includes("")) { + errors.push("'plugins' must not include an empty string."); + } + if (Array.isArray(plugins)) { + errors.push( + "'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.", + ); + } + if (typeof stats !== "boolean") { + errors.push("'stats' must be a boolean."); + } + if (typeof warnIgnored !== "boolean") { + errors.push("'warnIgnored' must be a boolean."); + } + if (typeof ruleFilter !== "function") { + errors.push("'ruleFilter' must be a function."); + } + if (errors.length > 0) { + throw new ESLintInvalidOptionsError(errors); + } + + return { + allowInlineConfig, + baseConfig, + cache, + cacheLocation, + cacheStrategy, + + // when overrideConfigFile is true that means don't do config file lookup + configFile: overrideConfigFile === true ? false : overrideConfigFile, + overrideConfig, + cwd: path.normalize(cwd), + errorOnUnmatchedPattern, + fix, + fixTypes, + flags: [...flags], + globInputPaths, + ignore, + ignorePatterns, + stats, + passOnNoPatterns, + warnIgnored, + ruleFilter, + }; } - //----------------------------------------------------------------------------- // Cache-related helpers //----------------------------------------------------------------------------- @@ -884,82 +918,78 @@ function processOptions({ * @returns {string} the resolved path to the cache file */ function getCacheFile(cacheFile, cwd) { - - /* - * make sure the path separators are normalized for the environment/os - * keeping the trailing path separator if present - */ - const normalizedCacheFile = path.normalize(cacheFile); - - const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); - const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; - - /** - * return the name for the cache file in case the provided parameter is a directory - * @returns {string} the resolved path to the cacheFile - */ - function getCacheFileForDirectory() { - return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`); - } - - let fileStats; - - try { - fileStats = fs.lstatSync(resolvedCacheFile); - } catch { - fileStats = null; - } - - - /* - * in case the file exists we need to verify if the provided path - * is a directory or a file. If it is a directory we want to create a file - * inside that directory - */ - if (fileStats) { - - /* - * is a directory or is a file, but the original file the user provided - * looks like a directory but `path.resolve` removed the `last path.sep` - * so we need to still treat this like a directory - */ - if (fileStats.isDirectory() || looksLikeADirectory) { - return getCacheFileForDirectory(); - } - - // is file so just use that file - return resolvedCacheFile; - } - - /* - * here we known the file or directory doesn't exist, - * so we will try to infer if its a directory if it looks like a directory - * for the current operating system. - */ - - // if the last character passed is a path separator we assume is a directory - if (looksLikeADirectory) { - return getCacheFileForDirectory(); - } - - return resolvedCacheFile; + /* + * make sure the path separators are normalized for the environment/os + * keeping the trailing path separator if present + */ + const normalizedCacheFile = path.normalize(cacheFile); + + const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile); + const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep; + + /** + * return the name for the cache file in case the provided parameter is a directory + * @returns {string} the resolved path to the cacheFile + */ + function getCacheFileForDirectory() { + return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`); + } + + let fileStats; + + try { + fileStats = fs.lstatSync(resolvedCacheFile); + } catch { + fileStats = null; + } + + /* + * in case the file exists we need to verify if the provided path + * is a directory or a file. If it is a directory we want to create a file + * inside that directory + */ + if (fileStats) { + /* + * is a directory or is a file, but the original file the user provided + * looks like a directory but `path.resolve` removed the `last path.sep` + * so we need to still treat this like a directory + */ + if (fileStats.isDirectory() || looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + // is file so just use that file + return resolvedCacheFile; + } + + /* + * here we known the file or directory doesn't exist, + * so we will try to infer if its a directory if it looks like a directory + * for the current operating system. + */ + + // if the last character passed is a path separator we assume is a directory + if (looksLikeADirectory) { + return getCacheFileForDirectory(); + } + + return resolvedCacheFile; } - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { - findFiles, + findFiles, - isNonEmptyString, - isArrayOfNonEmptyString, + isNonEmptyString, + isArrayOfNonEmptyString, - createIgnoreResult, - isErrorMessage, + createIgnoreResult, + isErrorMessage, - processOptions, + processOptions, - getCacheFile + getCacheFile, }; diff --git a/lib/eslint/eslint.js b/lib/eslint/eslint.js index 7661faf44158..eb9f0a5085e8 100644 --- a/lib/eslint/eslint.js +++ b/lib/eslint/eslint.js @@ -17,26 +17,24 @@ const { Linter } = require("../linter"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { defaultConfig } = require("../config/default-config"); const { - Legacy: { - ConfigOps: { - getRuleSeverity - }, - ModuleResolver, - naming - } + Legacy: { + ConfigOps: { getRuleSeverity }, + ModuleResolver, + naming, + }, } = require("@eslint/eslintrc"); const { - findFiles, - getCacheFile, + findFiles, + getCacheFile, - isNonEmptyString, - isArrayOfNonEmptyString, + isNonEmptyString, + isArrayOfNonEmptyString, - createIgnoreResult, - isErrorMessage, + createIgnoreResult, + isErrorMessage, - processOptions + processOptions, } = require("./eslint-helpers"); const { pathToFileURL } = require("node:url"); const LintResultCache = require("../cli-engine/lint-result-cache"); @@ -101,15 +99,15 @@ const { ConfigLoader, LegacyConfigLoader } = require("../config/config-loader"); const debug = require("debug")("eslint:eslint"); const privateMembers = new WeakMap(); const removedFormatters = new Set([ - "checkstyle", - "codeframe", - "compact", - "jslint-xml", - "junit", - "table", - "tap", - "unix", - "visualstudio" + "checkstyle", + "codeframe", + "compact", + "jslint-xml", + "junit", + "table", + "tap", + "unix", + "visualstudio", ]); /** @@ -119,33 +117,33 @@ const removedFormatters = new Set([ * @private */ function calculateStatsPerFile(messages) { - const stat = { - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - }; - - for (let i = 0; i < messages.length; i++) { - const message = messages[i]; - - if (message.fatal || message.severity === 2) { - stat.errorCount++; - if (message.fatal) { - stat.fatalErrorCount++; - } - if (message.fix) { - stat.fixableErrorCount++; - } - } else { - stat.warningCount++; - if (message.fix) { - stat.fixableWarningCount++; - } - } - } - return stat; + const stat = { + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }; + + for (let i = 0; i < messages.length; i++) { + const message = messages[i]; + + if (message.fatal || message.severity === 2) { + stat.errorCount++; + if (message.fatal) { + stat.fatalErrorCount++; + } + if (message.fix) { + stat.fixableErrorCount++; + } + } else { + stat.warningCount++; + if (message.fix) { + stat.fixableWarningCount++; + } + } + } + return stat; } /** @@ -154,10 +152,10 @@ function calculateStatsPerFile(messages) { * @returns {Object} metadata for all enabled rules. */ function createRulesMeta(rules) { - return Array.from(rules).reduce((retVal, [id, rule]) => { - retVal[id] = rule.meta; - return retVal; - }, {}); + return Array.from(rules).reduce((retVal, [id, rule]) => { + retVal[id] = rule.meta; + return retVal; + }, {}); } /** @@ -167,7 +165,7 @@ function createRulesMeta(rules) { * @returns {string} The absolute path of a file named `"__placeholder__.js"` in the given directory. */ function getPlaceholderPath(cwd) { - return path.join(cwd, "__placeholder__.js"); + return path.join(cwd, "__placeholder__.js"); } /** @type {WeakMap} */ @@ -180,45 +178,49 @@ const usedDeprecatedRulesCache = new WeakMap(); * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. */ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { - const { - options: { cwd }, - configLoader - } = privateMembers.get(eslint); - const filePath = path.isAbsolute(maybeFilePath) - ? maybeFilePath - : getPlaceholderPath(cwd); - const configs = configLoader.getCachedConfigArrayForFile(filePath); - const config = configs.getConfig(filePath); - - // Most files use the same config, so cache it. - if (config && !usedDeprecatedRulesCache.has(config)) { - const retv = []; - - if (config.rules) { - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = getRuleFromConfig(ruleId, config); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - const usesNewFormat = typeof meta.deprecated === "object"; - - retv.push({ - ruleId, - replacedBy: usesNewFormat ? meta.deprecated.replacedBy?.map(replacement => `${replacement.plugin?.name !== void 0 ? `${naming.getShorthandName(replacement.plugin.name, "eslint-plugin")}/` : ""}${replacement.rule?.name ?? ""}`) ?? [] : meta.replacedBy || [], - info: usesNewFormat ? meta.deprecated : void 0 - }); - } - } - } - - - usedDeprecatedRulesCache.set(config, Object.freeze(retv)); - } - - return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); + const { + options: { cwd }, + configLoader, + } = privateMembers.get(eslint); + const filePath = path.isAbsolute(maybeFilePath) + ? maybeFilePath + : getPlaceholderPath(cwd); + const configs = configLoader.getCachedConfigArrayForFile(filePath); + const config = configs.getConfig(filePath); + + // Most files use the same config, so cache it. + if (config && !usedDeprecatedRulesCache.has(config)) { + const retv = []; + + if (config.rules) { + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = getRuleFromConfig(ruleId, config); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + const usesNewFormat = typeof meta.deprecated === "object"; + + retv.push({ + ruleId, + replacedBy: usesNewFormat + ? (meta.deprecated.replacedBy?.map( + replacement => + `${replacement.plugin?.name !== void 0 ? `${naming.getShorthandName(replacement.plugin.name, "eslint-plugin")}/` : ""}${replacement.rule?.name ?? ""}`, + ) ?? []) + : meta.replacedBy || [], + info: usesNewFormat ? meta.deprecated : void 0, + }); + } + } + } + + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); + } + + return config ? usedDeprecatedRulesCache.get(config) : Object.freeze([]); } /** @@ -229,19 +231,19 @@ function getOrFindUsedDeprecatedRules(eslint, maybeFilePath) { * @returns {LintResult[]} The processed linting results. */ function processLintReport(eslint, { results }) { - const descriptor = { - configurable: true, - enumerable: true, - get() { - return getOrFindUsedDeprecatedRules(eslint, this.filePath); - } - }; - - for (const result of results) { - Object.defineProperty(result, "usedDeprecatedRules", descriptor); - } - - return results; + const descriptor = { + configurable: true, + enumerable: true, + get() { + return getOrFindUsedDeprecatedRules(eslint, this.filePath); + }, + }; + + for (const result of results) { + Object.defineProperty(result, "usedDeprecatedRules", descriptor); + } + + return results; } /** @@ -251,18 +253,17 @@ function processLintReport(eslint, { results }) { * @returns {number} An integer representing the order in which the two results should occur. */ function compareResultsByFilePath(a, b) { - if (a.filePath < b.filePath) { - return -1; - } + if (a.filePath < b.filePath) { + return -1; + } - if (a.filePath > b.filePath) { - return 1; - } + if (a.filePath > b.filePath) { + return 1; + } - return 0; + return 0; } - /** * Determines which config file to use. This is determined by seeing if an * override config file was passed, and if so, using it; otherwise, as long @@ -276,22 +277,23 @@ function compareResultsByFilePath(a, b) { * the config file. */ async function locateConfigFileToUse({ configFile, cwd }) { - - const configLoader = new ConfigLoader({ - cwd, - configFile - }); - - const configFilePath = await configLoader.findConfigFileForPath(path.join(cwd, "__placeholder__.js")); - - if (!configFilePath) { - throw new Error("No ESLint configuration file was found."); - } - - return { - configFilePath, - basePath: configFile ? cwd : path.dirname(configFilePath) - }; + const configLoader = new ConfigLoader({ + cwd, + configFile, + }); + + const configFilePath = await configLoader.findConfigFileForPath( + path.join(cwd, "__placeholder__.js"), + ); + + if (!configFilePath) { + throw new Error("No ESLint configuration file was found."); + } + + return { + configFilePath, + basePath: configFile ? cwd : path.dirname(configFilePath), + }; } /** @@ -310,74 +312,71 @@ async function locateConfigFileToUse({ configFile, cwd }) { * @private */ function verifyText({ - text, - cwd, - filePath: providedFilePath, - configs, - fix, - allowInlineConfig, - ruleFilter, - stats, - linter + text, + cwd, + filePath: providedFilePath, + configs, + fix, + allowInlineConfig, + ruleFilter, + stats, + linter, }) { - const filePath = providedFilePath || ""; - - debug(`Lint ${filePath}`); - - /* - * Verify. - * `config.extractConfig(filePath)` requires an absolute path, but `linter` - * doesn't know CWD, so it gives `linter` an absolute path always. - */ - const filePathToVerify = filePath === "" ? getPlaceholderPath(cwd) : filePath; - const { fixed, messages, output } = linter.verifyAndFix( - text, - configs, - { - allowInlineConfig, - filename: filePathToVerify, - fix, - ruleFilter, - stats, - - /** - * Check if the linter should adopt a given code block or not. - * @param {string} blockFilename The virtual filename of a code block. - * @returns {boolean} `true` if the linter should adopt the code block. - */ - filterCodeBlock(blockFilename) { - return configs.getConfig(blockFilename) !== void 0; - } - } - ); - - // Tweak and return. - const result = { - filePath: filePath === "" ? filePath : path.resolve(filePath), - messages, - suppressedMessages: linter.getSuppressedMessages(), - ...calculateStatsPerFile(messages) - }; - - if (fixed) { - result.output = output; - } - - if ( - result.errorCount + result.warningCount > 0 && - typeof result.output === "undefined" - ) { - result.source = text; - } - - if (stats) { - result.stats = { - times: linter.getTimes(), - fixPasses: linter.getFixPassCount() - }; - } - - return result; + const filePath = providedFilePath || ""; + + debug(`Lint ${filePath}`); + + /* + * Verify. + * `config.extractConfig(filePath)` requires an absolute path, but `linter` + * doesn't know CWD, so it gives `linter` an absolute path always. + */ + const filePathToVerify = + filePath === "" ? getPlaceholderPath(cwd) : filePath; + const { fixed, messages, output } = linter.verifyAndFix(text, configs, { + allowInlineConfig, + filename: filePathToVerify, + fix, + ruleFilter, + stats, + + /** + * Check if the linter should adopt a given code block or not. + * @param {string} blockFilename The virtual filename of a code block. + * @returns {boolean} `true` if the linter should adopt the code block. + */ + filterCodeBlock(blockFilename) { + return configs.getConfig(blockFilename) !== void 0; + }, + }); + + // Tweak and return. + const result = { + filePath: filePath === "" ? filePath : path.resolve(filePath), + messages, + suppressedMessages: linter.getSuppressedMessages(), + ...calculateStatsPerFile(messages), + }; + + if (fixed) { + result.output = output; + } + + if ( + result.errorCount + result.warningCount > 0 && + typeof result.output === "undefined" + ) { + result.source = text; + } + + if (stats) { + result.stats = { + times: linter.getTimes(), + fixPasses: linter.getFixPassCount(), + }; + } + + return result; } /** @@ -388,13 +387,13 @@ function verifyText({ * @returns {boolean} Whether the message should be fixed. */ function shouldMessageBeFixed(message, config, fixTypes) { - if (!message.ruleId) { - return fixTypes.has("directive"); - } + if (!message.ruleId) { + return fixTypes.has("directive"); + } - const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); + const rule = message.ruleId && getRuleFromConfig(message.ruleId, config); - return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); + return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type)); } /** @@ -402,7 +401,9 @@ function shouldMessageBeFixed(message, config, fixTypes) { * @returns {TypeError} An error object. */ function createExtraneousResultsError() { - return new TypeError("Results object was not created from this ESLint instance."); + return new TypeError( + "Results object was not created from this ESLint instance.", + ); } /** @@ -413,13 +414,15 @@ function createExtraneousResultsError() { * @returns {Function|boolean} The fixer function or the original fix value. */ function getFixerForFixTypes(fix, fixTypesSet, config) { - if (!fix || !fixTypesSet) { - return fix; - } + if (!fix || !fixTypesSet) { + return fix; + } - const originalFix = (typeof fix === "function") ? fix : () => true; + const originalFix = typeof fix === "function" ? fix : () => true; - return message => shouldMessageBeFixed(message, config, fixTypesSet) && originalFix(message); + return message => + shouldMessageBeFixed(message, config, fixTypesSet) && + originalFix(message); } //----------------------------------------------------------------------------- @@ -430,679 +433,734 @@ function getFixerForFixTypes(fix, fixTypesSet, config) { * Primary Node.js API for ESLint. */ class ESLint { - - /** - * The type of configuration used by this class. - * @type {string} - */ - static configType = "flat"; - - /** - * The loader to use for finding config files. - * @type {ConfigLoader|LegacyConfigLoader} - */ - #configLoader; - - /** - * Creates a new instance of the main ESLint API. - * @param {ESLintOptions} options The options for this instance. - */ - constructor(options = {}) { - - const defaultConfigs = []; - const processedOptions = processOptions(options); - const linter = new Linter({ - cwd: processedOptions.cwd, - configType: "flat", - flags: processedOptions.flags - }); - - const cacheFilePath = getCacheFile( - processedOptions.cacheLocation, - processedOptions.cwd - ); - - const lintResultCache = processedOptions.cache - ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) - : null; - - const configLoaderOptions = { - cwd: processedOptions.cwd, - baseConfig: processedOptions.baseConfig, - overrideConfig: processedOptions.overrideConfig, - configFile: processedOptions.configFile, - ignoreEnabled: processedOptions.ignore, - ignorePatterns: processedOptions.ignorePatterns, - defaultConfigs - }; - - this.#configLoader = linter.hasFlag("unstable_config_lookup_from_file") - ? new ConfigLoader(configLoaderOptions) - : new LegacyConfigLoader(configLoaderOptions); - - debug(`Using config loader ${this.#configLoader.constructor.name}`); - - privateMembers.set(this, { - options: processedOptions, - linter, - cacheFilePath, - lintResultCache, - defaultConfigs, - configs: null, - configLoader: this.#configLoader - }); - - - /** - * If additional plugins are passed in, add that to the default - * configs for this instance. - */ - if (options.plugins) { - - const plugins = {}; - - for (const [pluginName, plugin] of Object.entries(options.plugins)) { - plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = plugin; - } - - defaultConfigs.push({ - plugins - }); - } - - // Check for the .eslintignore file, and warn if it's present. - if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) { - process.emitWarning( - "The \".eslintignore\" file is no longer supported. Switch to using the \"ignores\" property in \"eslint.config.js\": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files", - "ESLintIgnoreWarning" - ); - } - } - - /** - * The version text. - * @type {string} - */ - static get version() { - return version; - } - - /** - * 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. - * @type {ConfigArray} - */ - static get defaultConfig() { - return defaultConfig; - } - - /** - * Outputs fixes from the given results to files. - * @param {LintResult[]} results The lint results. - * @returns {Promise} Returns a promise that is used to track side effects. - */ - static async outputFixes(results) { - if (!Array.isArray(results)) { - throw new Error("'results' must be an array"); - } - - await Promise.all( - results - .filter(result => { - if (typeof result !== "object" || result === null) { - throw new Error("'results' must include only objects"); - } - return ( - typeof result.output === "string" && - path.isAbsolute(result.filePath) - ); - }) - .map(r => fs.writeFile(r.filePath, r.output)) - ); - } - - /** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ - static getErrorResults(results) { - const filtered = []; - - results.forEach(result => { - const filteredMessages = result.messages.filter(isErrorMessage); - const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage); - - if (filteredMessages.length > 0) { - filtered.push({ - ...result, - messages: filteredMessages, - suppressedMessages: filteredSuppressedMessages, - errorCount: filteredMessages.length, - warningCount: 0, - fixableErrorCount: result.fixableErrorCount, - fixableWarningCount: 0 - }); - } - }); - - return filtered; - } - - /** - * Returns meta objects for each rule represented in the lint results. - * @param {LintResult[]} results The results to fetch rules meta for. - * @returns {Object} A mapping of ruleIds to rule meta objects. - * @throws {TypeError} When the results object wasn't created from this ESLint instance. - * @throws {TypeError} When a plugin or rule is missing. - */ - getRulesMetaForResults(results) { - - // short-circuit simple case - if (results.length === 0) { - return {}; - } - - const resultRules = new Map(); - const { - configLoader, - options: { cwd } - } = privateMembers.get(this); - - for (const result of results) { - - /* - * Normalize filename for . - */ - const filePath = result.filePath === "" - ? getPlaceholderPath(cwd) : result.filePath; - const allMessages = result.messages.concat(result.suppressedMessages); - - for (const { ruleId } of allMessages) { - if (!ruleId) { - continue; - } - - /* - * All of the plugin and rule information is contained within the - * calculated config for the given file. - */ - let configs; - - try { - configs = configLoader.getCachedConfigArrayForFile(filePath); - } catch { - throw createExtraneousResultsError(); - } - - const config = configs.getConfig(filePath); - - if (!config) { - throw createExtraneousResultsError(); - } - const rule = getRuleFromConfig(ruleId, config); - - // ignore unknown rules - if (rule) { - resultRules.set(ruleId, rule); - } - } - } - - return createRulesMeta(resultRules); - } - - /** - * Indicates if the given feature flag is enabled for this instance. - * @param {string} flag The feature flag to check. - * @returns {boolean} `true` if the feature flag is enabled, `false` if not. - */ - hasFlag(flag) { - - // note: Linter does validation of the flags - return privateMembers.get(this).linter.hasFlag(flag); - } - - /** - * Executes the current configuration on an array of file and directory names. - * @param {string|string[]} patterns An array of file and directory names. - * @returns {Promise} The results of linting the file patterns given. - */ - async lintFiles(patterns) { - - let normalizedPatterns = patterns; - const { - cacheFilePath, - lintResultCache, - linter, - options: eslintOptions - } = privateMembers.get(this); - - /* - * Special cases: - * 1. `patterns` is an empty string - * 2. `patterns` is an empty array - * - * In both cases, we use the cwd as the directory to lint. - */ - if (patterns === "" || Array.isArray(patterns) && patterns.length === 0) { - - /* - * Special case: If `passOnNoPatterns` is true, then we just exit - * without doing any work. - */ - if (eslintOptions.passOnNoPatterns) { - return []; - } - - normalizedPatterns = ["."]; - } else { - - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); - } - - if (typeof patterns === "string") { - normalizedPatterns = [patterns]; - } - } - - debug(`Using file patterns: ${normalizedPatterns}`); - - const { - allowInlineConfig, - cache, - cwd, - fix, - fixTypes, - ruleFilter, - stats, - globInputPaths, - errorOnUnmatchedPattern, - warnIgnored - } = eslintOptions; - const startTime = Date.now(); - const fixTypesSet = fixTypes ? new Set(fixTypes) : null; - - // Delete cache file; should this be done here? - if (!cache && cacheFilePath) { - debug(`Deleting cache file at ${cacheFilePath}`); - - try { - await fs.unlink(cacheFilePath); - } catch (error) { - const errorCode = error && error.code; - - // Ignore errors when no such file exists or file system is read only (and cache file does not exist) - if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !existsSync(cacheFilePath))) { - throw error; - } - } - } - - const filePaths = await findFiles({ - patterns: normalizedPatterns, - cwd, - globInputPaths, - configLoader: this.#configLoader, - errorOnUnmatchedPattern - }); - const controller = new AbortController(); - const retryCodes = new Set(["ENFILE", "EMFILE"]); - const retrier = new Retrier(error => retryCodes.has(error.code), { concurrency: 100 }); - - debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`); - - /* - * Because we need to process multiple files, including reading from disk, - * it is most efficient to start by reading each file via promises so that - * they can be done in parallel. Then, we can lint the returned text. This - * ensures we are waiting the minimum amount of time in between lints. - */ - const results = await Promise.all( - - filePaths.map(async filePath => { - - const configs = await this.#configLoader.loadConfigArrayForFile(filePath); - const config = configs.getConfig(filePath); - - /* - * If a filename was entered that cannot be matched - * to a config, then notify the user. - */ - if (!config) { - if (warnIgnored) { - const configStatus = configs.getConfigStatus(filePath); - - return createIgnoreResult(filePath, cwd, configStatus); - } - - return void 0; - } - - // Skip if there is cached result. - if (lintResultCache) { - const cachedResult = - lintResultCache.getCachedLintResults(filePath, config); - - if (cachedResult) { - const hadMessages = - cachedResult.messages && - cachedResult.messages.length > 0; - - if (hadMessages && fix) { - debug(`Reprocessing cached file to allow autofix: ${filePath}`); - } else { - debug(`Skipping file since it hasn't changed: ${filePath}`); - return cachedResult; - } - } - } - - - // set up fixer for fixTypes if necessary - const fixer = getFixerForFixTypes(fix, fixTypesSet, config); - - return retrier.retry(() => fs.readFile(filePath, { encoding: "utf8", signal: controller.signal }) - .then(text => { - - // fail immediately if an error occurred in another file - controller.signal.throwIfAborted(); - - // do the linting - const result = verifyText({ - text, - filePath, - configs, - cwd, - fix: fixer, - allowInlineConfig, - ruleFilter, - stats, - linter - }); - - /* - * Store the lint result in the LintResultCache. - * NOTE: The LintResultCache will remove the file source and any - * other properties that are difficult to serialize, and will - * hydrate those properties back in on future lint runs. - */ - if (lintResultCache) { - lintResultCache.setCachedLintResults(filePath, config, result); - } - - return result; - }), { signal: controller.signal }) - .catch(error => { - controller.abort(error); - throw error; - }); - }) - ); - - // Persist the cache to disk. - if (lintResultCache) { - lintResultCache.reconcile(); - } - - const finalResults = results.filter(result => !!result); - - return processLintReport(this, { - results: finalResults - }); - } - - /** - * Executes the current configuration on text. - * @param {string} code A string of JavaScript code to lint. - * @param {Object} [options] The options. - * @param {string} [options.filePath] The path to the file of the source code. - * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. - * @returns {Promise} The results of linting the string of code given. - */ - async lintText(code, options = {}) { - - // Parameter validation - - if (typeof code !== "string") { - throw new Error("'code' must be a string"); - } - - if (typeof options !== "object") { - throw new Error("'options' must be an object, null, or undefined"); - } - - // Options validation - - const { - filePath, - warnIgnored, - ...unknownOptions - } = options || {}; - - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length > 0) { - throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); - } - - if (filePath !== void 0 && !isNonEmptyString(filePath)) { - throw new Error("'options.filePath' must be a non-empty string or undefined"); - } - - if (typeof warnIgnored !== "boolean" && typeof warnIgnored !== "undefined") { - throw new Error("'options.warnIgnored' must be a boolean or undefined"); - } - - // Now we can get down to linting - - const { - linter, - options: eslintOptions - } = privateMembers.get(this); - const { - allowInlineConfig, - cwd, - fix, - fixTypes, - warnIgnored: constructorWarnIgnored, - ruleFilter, - stats - } = eslintOptions; - const results = []; - const startTime = Date.now(); - const fixTypesSet = fixTypes ? new Set(fixTypes) : null; - const resolvedFilename = path.resolve(cwd, filePath || "__placeholder__.js"); - const configs = await this.#configLoader.loadConfigArrayForFile(resolvedFilename); - const configStatus = configs?.getConfigStatus(resolvedFilename) ?? "unconfigured"; - - // Clear the last used config arrays. - if (resolvedFilename && configStatus !== "matched") { - const shouldWarnIgnored = typeof warnIgnored === "boolean" ? warnIgnored : constructorWarnIgnored; - - if (shouldWarnIgnored) { - results.push(createIgnoreResult(resolvedFilename, cwd, configStatus)); - } - } else { - - const config = configs.getConfig(resolvedFilename); - const fixer = getFixerForFixTypes(fix, fixTypesSet, config); - - // Do lint. - results.push(verifyText({ - text: code, - filePath: resolvedFilename.endsWith("__placeholder__.js") ? "" : resolvedFilename, - configs, - cwd, - fix: fixer, - allowInlineConfig, - ruleFilter, - stats, - linter - })); - } - - debug(`Linting complete in: ${Date.now() - startTime}ms`); - - return processLintReport(this, { - results - }); - - } - - /** - * Returns the formatter representing the given formatter name. - * @param {string} [name] The name of the formatter to load. - * The following values are allowed: - * - `undefined` ... Load `stylish` builtin formatter. - * - A builtin formatter name ... Load the builtin formatter. - * - A third-party formatter name: - * - `foo` → `eslint-formatter-foo` - * - `@foo` → `@foo/eslint-formatter` - * - `@foo/bar` → `@foo/eslint-formatter-bar` - * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. - * This promise will be rejected if the given formatter was not found or not - * a function. - */ - async loadFormatter(name = "stylish") { - if (typeof name !== "string") { - throw new Error("'name' must be a string"); - } - - // replace \ with / for Windows compatibility - const normalizedFormatName = name.replace(/\\/gu, "/"); - const namespace = naming.getNamespaceFromTerm(normalizedFormatName); - - // grab our options - const { cwd } = privateMembers.get(this).options; - - - let formatterPath; - - // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) - if (!namespace && normalizedFormatName.includes("/")) { - formatterPath = path.resolve(cwd, normalizedFormatName); - } else { - try { - const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter"); - - // TODO: This is pretty dirty...would be nice to clean up at some point. - formatterPath = ModuleResolver.resolve(npmFormat, getPlaceholderPath(cwd)); - } catch { - formatterPath = path.resolve(__dirname, "../", "cli-engine", "formatters", `${normalizedFormatName}.js`); - } - } - - let formatter; - - try { - formatter = (await import(pathToFileURL(formatterPath))).default; - } catch (ex) { - - // check for formatters that have been removed - if (removedFormatters.has(name)) { - ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; - } else { - ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; - } - - throw ex; - } - - - if (typeof formatter !== "function") { - throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`); - } - - const eslint = this; - - return { - - /** - * The main formatter method. - * @param {LintResult[]} results The lint results to format. - * @param {ResultsMeta} resultsMeta Warning count and max threshold. - * @returns {string} The formatted lint results. - */ - format(results, resultsMeta) { - let rulesMeta = null; - - results.sort(compareResultsByFilePath); - - return formatter(results, { - ...resultsMeta, - cwd, - get rulesMeta() { - if (!rulesMeta) { - rulesMeta = eslint.getRulesMetaForResults(results); - } - - return rulesMeta; - } - }); - } - }; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file - * or `undefined` if there is no configuration data for the object. - */ - async calculateConfigForFile(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const options = privateMembers.get(this).options; - const absolutePath = path.resolve(options.cwd, filePath); - const configs = await this.#configLoader.loadConfigArrayForFile(absolutePath); - - if (!configs) { - const error = new Error("Could not find config file."); - - error.messageTemplate = "config-file-missing"; - throw error; - } - - return configs.getConfig(absolutePath); - } - - /** - * Finds the config file being used by this instance based on the options - * passed to the constructor. - * @param {string} [filePath] The path of the file to find the config file for. - * @returns {Promise} The path to the config file being used or - * `undefined` if no config file is being used. - */ - findConfigFile(filePath) { - const options = privateMembers.get(this).options; - - /* - * Because the new config lookup scheme skips the current directory - * and looks into the parent directories, we need to use a placeholder - * directory to ensure the file in cwd is checked. - */ - const fakeCwd = path.join(options.cwd, "__placeholder__"); - - return this.#configLoader.findConfigFileForPath(filePath ?? fakeCwd) - .catch(() => void 0); - } - - /** - * Checks if a given path is ignored by ESLint. - * @param {string} filePath The path of the file to check. - * @returns {Promise} Whether or not the given path is ignored. - */ - async isPathIgnored(filePath) { - const config = await this.calculateConfigForFile(filePath); - - return config === void 0; - } + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "flat"; + + /** + * The loader to use for finding config files. + * @type {ConfigLoader|LegacyConfigLoader} + */ + #configLoader; + + /** + * Creates a new instance of the main ESLint API. + * @param {ESLintOptions} options The options for this instance. + */ + constructor(options = {}) { + const defaultConfigs = []; + const processedOptions = processOptions(options); + const linter = new Linter({ + cwd: processedOptions.cwd, + configType: "flat", + flags: processedOptions.flags, + }); + + const cacheFilePath = getCacheFile( + processedOptions.cacheLocation, + processedOptions.cwd, + ); + + const lintResultCache = processedOptions.cache + ? new LintResultCache(cacheFilePath, processedOptions.cacheStrategy) + : null; + + const configLoaderOptions = { + cwd: processedOptions.cwd, + baseConfig: processedOptions.baseConfig, + overrideConfig: processedOptions.overrideConfig, + configFile: processedOptions.configFile, + ignoreEnabled: processedOptions.ignore, + ignorePatterns: processedOptions.ignorePatterns, + defaultConfigs, + }; + + this.#configLoader = linter.hasFlag("unstable_config_lookup_from_file") + ? new ConfigLoader(configLoaderOptions) + : new LegacyConfigLoader(configLoaderOptions); + + debug(`Using config loader ${this.#configLoader.constructor.name}`); + + privateMembers.set(this, { + options: processedOptions, + linter, + cacheFilePath, + lintResultCache, + defaultConfigs, + configs: null, + configLoader: this.#configLoader, + }); + + /** + * If additional plugins are passed in, add that to the default + * configs for this instance. + */ + if (options.plugins) { + const plugins = {}; + + for (const [pluginName, plugin] of Object.entries( + options.plugins, + )) { + plugins[naming.getShorthandName(pluginName, "eslint-plugin")] = + plugin; + } + + defaultConfigs.push({ + plugins, + }); + } + + // Check for the .eslintignore file, and warn if it's present. + if (existsSync(path.resolve(processedOptions.cwd, ".eslintignore"))) { + process.emitWarning( + 'The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files', + "ESLintIgnoreWarning", + ); + } + } + + /** + * The version text. + * @type {string} + */ + static get version() { + return version; + } + + /** + * 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. + * @type {ConfigArray} + */ + static get defaultConfig() { + return defaultConfig; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintResult[]} results The lint results. + * @returns {Promise} Returns a promise that is used to track side effects. + */ + static async outputFixes(results) { + if (!Array.isArray(results)) { + throw new Error("'results' must be an array"); + } + + await Promise.all( + results + .filter(result => { + if (typeof result !== "object" || result === null) { + throw new Error("'results' must include only objects"); + } + return ( + typeof result.output === "string" && + path.isAbsolute(result.filePath) + ); + }) + .map(r => fs.writeFile(r.filePath, r.output)), + ); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + const filtered = []; + + results.forEach(result => { + const filteredMessages = result.messages.filter(isErrorMessage); + const filteredSuppressedMessages = + result.suppressedMessages.filter(isErrorMessage); + + if (filteredMessages.length > 0) { + filtered.push({ + ...result, + messages: filteredMessages, + suppressedMessages: filteredSuppressedMessages, + errorCount: filteredMessages.length, + warningCount: 0, + fixableErrorCount: result.fixableErrorCount, + fixableWarningCount: 0, + }); + } + }); + + return filtered; + } + + /** + * Returns meta objects for each rule represented in the lint results. + * @param {LintResult[]} results The results to fetch rules meta for. + * @returns {Object} A mapping of ruleIds to rule meta objects. + * @throws {TypeError} When the results object wasn't created from this ESLint instance. + * @throws {TypeError} When a plugin or rule is missing. + */ + getRulesMetaForResults(results) { + // short-circuit simple case + if (results.length === 0) { + return {}; + } + + const resultRules = new Map(); + const { + configLoader, + options: { cwd }, + } = privateMembers.get(this); + + for (const result of results) { + /* + * Normalize filename for . + */ + const filePath = + result.filePath === "" + ? getPlaceholderPath(cwd) + : result.filePath; + const allMessages = result.messages.concat( + result.suppressedMessages, + ); + + for (const { ruleId } of allMessages) { + if (!ruleId) { + continue; + } + + /* + * All of the plugin and rule information is contained within the + * calculated config for the given file. + */ + let configs; + + try { + configs = + configLoader.getCachedConfigArrayForFile(filePath); + } catch { + throw createExtraneousResultsError(); + } + + const config = configs.getConfig(filePath); + + if (!config) { + throw createExtraneousResultsError(); + } + const rule = getRuleFromConfig(ruleId, config); + + // ignore unknown rules + if (rule) { + resultRules.set(ruleId, rule); + } + } + } + + return createRulesMeta(resultRules); + } + + /** + * Indicates if the given feature flag is enabled for this instance. + * @param {string} flag The feature flag to check. + * @returns {boolean} `true` if the feature flag is enabled, `false` if not. + */ + hasFlag(flag) { + // note: Linter does validation of the flags + return privateMembers.get(this).linter.hasFlag(flag); + } + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string|string[]} patterns An array of file and directory names. + * @returns {Promise} The results of linting the file patterns given. + */ + async lintFiles(patterns) { + let normalizedPatterns = patterns; + const { + cacheFilePath, + lintResultCache, + linter, + options: eslintOptions, + } = privateMembers.get(this); + + /* + * Special cases: + * 1. `patterns` is an empty string + * 2. `patterns` is an empty array + * + * In both cases, we use the cwd as the directory to lint. + */ + if ( + patterns === "" || + (Array.isArray(patterns) && patterns.length === 0) + ) { + /* + * Special case: If `passOnNoPatterns` is true, then we just exit + * without doing any work. + */ + if (eslintOptions.passOnNoPatterns) { + return []; + } + + normalizedPatterns = ["."]; + } else { + if ( + !isNonEmptyString(patterns) && + !isArrayOfNonEmptyString(patterns) + ) { + throw new Error( + "'patterns' must be a non-empty string or an array of non-empty strings", + ); + } + + if (typeof patterns === "string") { + normalizedPatterns = [patterns]; + } + } + + debug(`Using file patterns: ${normalizedPatterns}`); + + const { + allowInlineConfig, + cache, + cwd, + fix, + fixTypes, + ruleFilter, + stats, + globInputPaths, + errorOnUnmatchedPattern, + warnIgnored, + } = eslintOptions; + const startTime = Date.now(); + const fixTypesSet = fixTypes ? new Set(fixTypes) : null; + + // Delete cache file; should this be done here? + if (!cache && cacheFilePath) { + debug(`Deleting cache file at ${cacheFilePath}`); + + try { + await fs.unlink(cacheFilePath); + } catch (error) { + const errorCode = error && error.code; + + // Ignore errors when no such file exists or file system is read only (and cache file does not exist) + if ( + errorCode !== "ENOENT" && + !(errorCode === "EROFS" && !existsSync(cacheFilePath)) + ) { + throw error; + } + } + } + + const filePaths = await findFiles({ + patterns: normalizedPatterns, + cwd, + globInputPaths, + configLoader: this.#configLoader, + errorOnUnmatchedPattern, + }); + const controller = new AbortController(); + const retryCodes = new Set(["ENFILE", "EMFILE"]); + const retrier = new Retrier(error => retryCodes.has(error.code), { + concurrency: 100, + }); + + debug( + `${filePaths.length} files found in: ${Date.now() - startTime}ms`, + ); + + /* + * Because we need to process multiple files, including reading from disk, + * it is most efficient to start by reading each file via promises so that + * they can be done in parallel. Then, we can lint the returned text. This + * ensures we are waiting the minimum amount of time in between lints. + */ + const results = await Promise.all( + filePaths.map(async filePath => { + const configs = + await this.#configLoader.loadConfigArrayForFile(filePath); + const config = configs.getConfig(filePath); + + /* + * If a filename was entered that cannot be matched + * to a config, then notify the user. + */ + if (!config) { + if (warnIgnored) { + const configStatus = configs.getConfigStatus(filePath); + + return createIgnoreResult(filePath, cwd, configStatus); + } + + return void 0; + } + + // Skip if there is cached result. + if (lintResultCache) { + const cachedResult = lintResultCache.getCachedLintResults( + filePath, + config, + ); + + if (cachedResult) { + const hadMessages = + cachedResult.messages && + cachedResult.messages.length > 0; + + if (hadMessages && fix) { + debug( + `Reprocessing cached file to allow autofix: ${filePath}`, + ); + } else { + debug( + `Skipping file since it hasn't changed: ${filePath}`, + ); + return cachedResult; + } + } + } + + // set up fixer for fixTypes if necessary + const fixer = getFixerForFixTypes(fix, fixTypesSet, config); + + return retrier + .retry( + () => + fs + .readFile(filePath, { + encoding: "utf8", + signal: controller.signal, + }) + .then(text => { + // fail immediately if an error occurred in another file + controller.signal.throwIfAborted(); + + // do the linting + const result = verifyText({ + text, + filePath, + configs, + cwd, + fix: fixer, + allowInlineConfig, + ruleFilter, + stats, + linter, + }); + + /* + * Store the lint result in the LintResultCache. + * NOTE: The LintResultCache will remove the file source and any + * other properties that are difficult to serialize, and will + * hydrate those properties back in on future lint runs. + */ + if (lintResultCache) { + lintResultCache.setCachedLintResults( + filePath, + config, + result, + ); + } + + return result; + }), + { signal: controller.signal }, + ) + .catch(error => { + controller.abort(error); + throw error; + }); + }), + ); + + // Persist the cache to disk. + if (lintResultCache) { + lintResultCache.reconcile(); + } + + const finalResults = results.filter(result => !!result); + + return processLintReport(this, { + results: finalResults, + }); + } + + /** + * Executes the current configuration on text. + * @param {string} code A string of JavaScript code to lint. + * @param {Object} [options] The options. + * @param {string} [options.filePath] The path to the file of the source code. + * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. + * @returns {Promise} The results of linting the string of code given. + */ + async lintText(code, options = {}) { + // Parameter validation + + if (typeof code !== "string") { + throw new Error("'code' must be a string"); + } + + if (typeof options !== "object") { + throw new Error("'options' must be an object, null, or undefined"); + } + + // Options validation + + const { filePath, warnIgnored, ...unknownOptions } = options || {}; + + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length > 0) { + throw new Error( + `'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`, + ); + } + + if (filePath !== void 0 && !isNonEmptyString(filePath)) { + throw new Error( + "'options.filePath' must be a non-empty string or undefined", + ); + } + + if ( + typeof warnIgnored !== "boolean" && + typeof warnIgnored !== "undefined" + ) { + throw new Error( + "'options.warnIgnored' must be a boolean or undefined", + ); + } + + // Now we can get down to linting + + const { linter, options: eslintOptions } = privateMembers.get(this); + const { + allowInlineConfig, + cwd, + fix, + fixTypes, + warnIgnored: constructorWarnIgnored, + ruleFilter, + stats, + } = eslintOptions; + const results = []; + const startTime = Date.now(); + const fixTypesSet = fixTypes ? new Set(fixTypes) : null; + const resolvedFilename = path.resolve( + cwd, + filePath || "__placeholder__.js", + ); + const configs = + await this.#configLoader.loadConfigArrayForFile(resolvedFilename); + const configStatus = + configs?.getConfigStatus(resolvedFilename) ?? "unconfigured"; + + // Clear the last used config arrays. + if (resolvedFilename && configStatus !== "matched") { + const shouldWarnIgnored = + typeof warnIgnored === "boolean" + ? warnIgnored + : constructorWarnIgnored; + + if (shouldWarnIgnored) { + results.push( + createIgnoreResult(resolvedFilename, cwd, configStatus), + ); + } + } else { + const config = configs.getConfig(resolvedFilename); + const fixer = getFixerForFixTypes(fix, fixTypesSet, config); + + // Do lint. + results.push( + verifyText({ + text: code, + filePath: resolvedFilename.endsWith("__placeholder__.js") + ? "" + : resolvedFilename, + configs, + cwd, + fix: fixer, + allowInlineConfig, + ruleFilter, + stats, + linter, + }), + ); + } + + debug(`Linting complete in: ${Date.now() - startTime}ms`); + + return processLintReport(this, { + results, + }); + } + + /** + * Returns the formatter representing the given formatter name. + * @param {string} [name] The name of the formatter to load. + * The following values are allowed: + * - `undefined` ... Load `stylish` builtin formatter. + * - A builtin formatter name ... Load the builtin formatter. + * - A third-party formatter name: + * - `foo` → `eslint-formatter-foo` + * - `@foo` → `@foo/eslint-formatter` + * - `@foo/bar` → `@foo/eslint-formatter-bar` + * - A file path ... Load the file. + * @returns {Promise} A promise resolving to the formatter object. + * This promise will be rejected if the given formatter was not found or not + * a function. + */ + async loadFormatter(name = "stylish") { + if (typeof name !== "string") { + throw new Error("'name' must be a string"); + } + + // replace \ with / for Windows compatibility + const normalizedFormatName = name.replace(/\\/gu, "/"); + const namespace = naming.getNamespaceFromTerm(normalizedFormatName); + + // grab our options + const { cwd } = privateMembers.get(this).options; + + let formatterPath; + + // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages) + if (!namespace && normalizedFormatName.includes("/")) { + formatterPath = path.resolve(cwd, normalizedFormatName); + } else { + try { + const npmFormat = naming.normalizePackageName( + normalizedFormatName, + "eslint-formatter", + ); + + // TODO: This is pretty dirty...would be nice to clean up at some point. + formatterPath = ModuleResolver.resolve( + npmFormat, + getPlaceholderPath(cwd), + ); + } catch { + formatterPath = path.resolve( + __dirname, + "../", + "cli-engine", + "formatters", + `${normalizedFormatName}.js`, + ); + } + } + + let formatter; + + try { + formatter = (await import(pathToFileURL(formatterPath))).default; + } catch (ex) { + // check for formatters that have been removed + if (removedFormatters.has(name)) { + ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``; + } else { + ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`; + } + + throw ex; + } + + if (typeof formatter !== "function") { + throw new TypeError( + `Formatter must be a function, but got a ${typeof formatter}.`, + ); + } + + const eslint = this; + + return { + /** + * The main formatter method. + * @param {LintResult[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. + * @returns {string} The formatted lint results. + */ + format(results, resultsMeta) { + let rulesMeta = null; + + results.sort(compareResultsByFilePath); + + return formatter(results, { + ...resultsMeta, + cwd, + get rulesMeta() { + if (!rulesMeta) { + rulesMeta = eslint.getRulesMetaForResults(results); + } + + return rulesMeta; + }, + }); + }, + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {Promise} A configuration object for the file + * or `undefined` if there is no configuration data for the object. + */ + async calculateConfigForFile(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const options = privateMembers.get(this).options; + const absolutePath = path.resolve(options.cwd, filePath); + const configs = + await this.#configLoader.loadConfigArrayForFile(absolutePath); + + if (!configs) { + const error = new Error("Could not find config file."); + + error.messageTemplate = "config-file-missing"; + throw error; + } + + return configs.getConfig(absolutePath); + } + + /** + * Finds the config file being used by this instance based on the options + * passed to the constructor. + * @param {string} [filePath] The path of the file to find the config file for. + * @returns {Promise} The path to the config file being used or + * `undefined` if no config file is being used. + */ + findConfigFile(filePath) { + const options = privateMembers.get(this).options; + + /* + * Because the new config lookup scheme skips the current directory + * and looks into the parent directories, we need to use a placeholder + * directory to ensure the file in cwd is checked. + */ + const fakeCwd = path.join(options.cwd, "__placeholder__"); + + return this.#configLoader + .findConfigFileForPath(filePath ?? fakeCwd) + .catch(() => void 0); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {Promise} Whether or not the given path is ignored. + */ + async isPathIgnored(filePath) { + const config = await this.calculateConfigForFile(filePath); + + return config === void 0; + } } /** @@ -1110,7 +1168,7 @@ class ESLint { * @returns {Promise} Whether flat config should be used. */ async function shouldUseFlatConfig() { - return (process.env.ESLINT_USE_FLAT_CONFIG !== "false"); + return process.env.ESLINT_USE_FLAT_CONFIG !== "false"; } //------------------------------------------------------------------------------ @@ -1118,7 +1176,7 @@ async function shouldUseFlatConfig() { //------------------------------------------------------------------------------ module.exports = { - ESLint, - shouldUseFlatConfig, - locateConfigFileToUse + ESLint, + shouldUseFlatConfig, + locateConfigFileToUse, }; diff --git a/lib/eslint/index.js b/lib/eslint/index.js index b7c52a4ea701..40588d5d4d8e 100644 --- a/lib/eslint/index.js +++ b/lib/eslint/index.js @@ -4,6 +4,6 @@ const { ESLint } = require("./eslint"); const { LegacyESLint } = require("./legacy-eslint"); module.exports = { - ESLint, - LegacyESLint + ESLint, + LegacyESLint, }; diff --git a/lib/eslint/legacy-eslint.js b/lib/eslint/legacy-eslint.js index f282e5874641..7efa9dfd5ce2 100644 --- a/lib/eslint/legacy-eslint.js +++ b/lib/eslint/legacy-eslint.js @@ -13,14 +13,15 @@ const path = require("node:path"); const fs = require("node:fs"); const { promisify } = require("node:util"); -const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); +const { + CLIEngine, + getCLIEngineInternalSlots, +} = require("../cli-engine/cli-engine"); const BuiltinRules = require("../rules"); const { - Legacy: { - ConfigOps: { - getRuleSeverity - } - } + Legacy: { + ConfigOps: { getRuleSeverity }, + }, } = require("@eslint/eslintrc"); const { version } = require("../../package.json"); @@ -103,7 +104,7 @@ const privateMembersMap = new WeakMap(); * @returns {boolean} `true` if `value` is a non-empty string. */ function isNonEmptyString(value) { - return typeof value === "string" && value.trim() !== ""; + return typeof value === "string" && value.trim() !== ""; } /** @@ -112,7 +113,9 @@ function isNonEmptyString(value) { * @returns {boolean} `true` if `value` is an array of non-empty strings. */ function isArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.length && value.every(isNonEmptyString); + return ( + Array.isArray(value) && value.length && value.every(isNonEmptyString) + ); } /** @@ -122,7 +125,7 @@ function isArrayOfNonEmptyString(value) { * strings. */ function isEmptyArrayOrArrayOfNonEmptyString(value) { - return Array.isArray(value) && value.every(isNonEmptyString); + return Array.isArray(value) && value.every(isNonEmptyString); } /** @@ -131,7 +134,12 @@ function isEmptyArrayOrArrayOfNonEmptyString(value) { * @returns {boolean} `true` if `value` is valid fix type. */ function isFixType(value) { - return value === "directive" || value === "problem" || value === "suggestion" || value === "layout"; + return ( + value === "directive" || + value === "problem" || + value === "suggestion" || + value === "layout" + ); } /** @@ -140,18 +148,18 @@ function isFixType(value) { * @returns {boolean} `true` if `value` is an array of fix types. */ function isFixTypeArray(value) { - return Array.isArray(value) && value.every(isFixType); + return Array.isArray(value) && value.every(isFixType); } /** * The error for invalid options. */ class ESLintInvalidOptionsError extends Error { - constructor(messages) { - super(`Invalid Options:\n- ${messages.join("\n- ")}`); - this.code = "ESLINT_INVALID_OPTIONS"; - Error.captureStackTrace(this, ESLintInvalidOptionsError); - } + constructor(messages) { + super(`Invalid Options:\n- ${messages.join("\n- ")}`); + this.code = "ESLINT_INVALID_OPTIONS"; + Error.captureStackTrace(this, ESLintInvalidOptionsError); + } } /** @@ -161,182 +169,208 @@ class ESLintInvalidOptionsError extends Error { * @returns {LegacyESLintOptions} The normalized options. */ function processOptions({ - allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. - baseConfig = null, - cache = false, - cacheLocation = ".eslintcache", - cacheStrategy = "metadata", - cwd = process.cwd(), - errorOnUnmatchedPattern = true, - extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. - fix = false, - fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. - flags, /* eslint-disable-line no-unused-vars -- leaving for compatibility with ESLint#hasFlag */ - globInputPaths = true, - ignore = true, - ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. - overrideConfig = null, - overrideConfigFile = null, - plugins = {}, - reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. - resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. - rulePaths = [], - useEslintrc = true, - passOnNoPatterns = false, - ...unknownOptions + allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored. + baseConfig = null, + cache = false, + cacheLocation = ".eslintcache", + cacheStrategy = "metadata", + cwd = process.cwd(), + errorOnUnmatchedPattern = true, + extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature. + fix = false, + fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property. + flags /* eslint-disable-line no-unused-vars -- leaving for compatibility with ESLint#hasFlag */, + globInputPaths = true, + ignore = true, + ignorePath = null, // ← should be null by default because if it's a string then it may throw ENOENT. + overrideConfig = null, + overrideConfigFile = null, + plugins = {}, + reportUnusedDisableDirectives = null, // ← should be null by default because if it's a string then it overrides the 'reportUnusedDisableDirectives' setting in config files. And we cannot use `overrideConfig.reportUnusedDisableDirectives` instead because we cannot configure the `error` severity with that. + resolvePluginsRelativeTo = null, // ← should be null by default because if it's a string then it suppresses RFC47 feature. + rulePaths = [], + useEslintrc = true, + passOnNoPatterns = false, + ...unknownOptions }) { - const errors = []; - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length >= 1) { - errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); - if (unknownOptionKeys.includes("cacheFile")) { - errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead."); - } - if (unknownOptionKeys.includes("configFile")) { - errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead."); - } - if (unknownOptionKeys.includes("envs")) { - errors.push("'envs' has been removed. Please use the 'overrideConfig.env' option instead."); - } - if (unknownOptionKeys.includes("globals")) { - errors.push("'globals' has been removed. Please use the 'overrideConfig.globals' option instead."); - } - if (unknownOptionKeys.includes("ignorePattern")) { - errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead."); - } - if (unknownOptionKeys.includes("parser")) { - errors.push("'parser' has been removed. Please use the 'overrideConfig.parser' option instead."); - } - if (unknownOptionKeys.includes("parserOptions")) { - errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead."); - } - if (unknownOptionKeys.includes("rules")) { - errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead."); - } - } - if (typeof allowInlineConfig !== "boolean") { - errors.push("'allowInlineConfig' must be a boolean."); - } - if (typeof baseConfig !== "object") { - errors.push("'baseConfig' must be an object or null."); - } - if (typeof cache !== "boolean") { - errors.push("'cache' must be a boolean."); - } - if (!isNonEmptyString(cacheLocation)) { - errors.push("'cacheLocation' must be a non-empty string."); - } - if ( - cacheStrategy !== "metadata" && - cacheStrategy !== "content" - ) { - errors.push("'cacheStrategy' must be any of \"metadata\", \"content\"."); - } - if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { - errors.push("'cwd' must be an absolute path."); - } - if (typeof errorOnUnmatchedPattern !== "boolean") { - errors.push("'errorOnUnmatchedPattern' must be a boolean."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(extensions) && extensions !== null) { - errors.push("'extensions' must be an array of non-empty strings or null."); - } - if (typeof fix !== "boolean" && typeof fix !== "function") { - errors.push("'fix' must be a boolean or a function."); - } - if (fixTypes !== null && !isFixTypeArray(fixTypes)) { - errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\"."); - } - if (typeof globInputPaths !== "boolean") { - errors.push("'globInputPaths' must be a boolean."); - } - if (typeof ignore !== "boolean") { - errors.push("'ignore' must be a boolean."); - } - if (!isNonEmptyString(ignorePath) && ignorePath !== null) { - errors.push("'ignorePath' must be a non-empty string or null."); - } - if (typeof overrideConfig !== "object") { - errors.push("'overrideConfig' must be an object or null."); - } - if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { - errors.push("'overrideConfigFile' must be a non-empty string or null."); - } - if (typeof plugins !== "object") { - errors.push("'plugins' must be an object or null."); - } else if (plugins !== null && Object.keys(plugins).includes("")) { - errors.push("'plugins' must not include an empty string."); - } - if (Array.isArray(plugins)) { - errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead."); - } - if ( - reportUnusedDisableDirectives !== "error" && - reportUnusedDisableDirectives !== "warn" && - reportUnusedDisableDirectives !== "off" && - reportUnusedDisableDirectives !== null - ) { - errors.push("'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null."); - } - if ( - !isNonEmptyString(resolvePluginsRelativeTo) && - resolvePluginsRelativeTo !== null - ) { - errors.push("'resolvePluginsRelativeTo' must be a non-empty string or null."); - } - if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) { - errors.push("'rulePaths' must be an array of non-empty strings."); - } - if (typeof useEslintrc !== "boolean") { - errors.push("'useEslintrc' must be a boolean."); - } - if (typeof passOnNoPatterns !== "boolean") { - errors.push("'passOnNoPatterns' must be a boolean."); - } - - if (errors.length > 0) { - throw new ESLintInvalidOptionsError(errors); - } - - return { - allowInlineConfig, - baseConfig, - cache, - cacheLocation, - cacheStrategy, - configFile: overrideConfigFile, - cwd: path.normalize(cwd), - errorOnUnmatchedPattern, - extensions, - fix, - fixTypes, - flags: [], // LegacyESLint does not support flags, so just ignore them. - globInputPaths, - ignore, - ignorePath, - reportUnusedDisableDirectives, - resolvePluginsRelativeTo, - rulePaths, - useEslintrc, - passOnNoPatterns - }; + const errors = []; + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length >= 1) { + errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`); + if (unknownOptionKeys.includes("cacheFile")) { + errors.push( + "'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", + ); + } + if (unknownOptionKeys.includes("configFile")) { + errors.push( + "'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", + ); + } + if (unknownOptionKeys.includes("envs")) { + errors.push( + "'envs' has been removed. Please use the 'overrideConfig.env' option instead.", + ); + } + if (unknownOptionKeys.includes("globals")) { + errors.push( + "'globals' has been removed. Please use the 'overrideConfig.globals' option instead.", + ); + } + if (unknownOptionKeys.includes("ignorePattern")) { + errors.push( + "'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", + ); + } + if (unknownOptionKeys.includes("parser")) { + errors.push( + "'parser' has been removed. Please use the 'overrideConfig.parser' option instead.", + ); + } + if (unknownOptionKeys.includes("parserOptions")) { + errors.push( + "'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.", + ); + } + if (unknownOptionKeys.includes("rules")) { + errors.push( + "'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", + ); + } + } + if (typeof allowInlineConfig !== "boolean") { + errors.push("'allowInlineConfig' must be a boolean."); + } + if (typeof baseConfig !== "object") { + errors.push("'baseConfig' must be an object or null."); + } + if (typeof cache !== "boolean") { + errors.push("'cache' must be a boolean."); + } + if (!isNonEmptyString(cacheLocation)) { + errors.push("'cacheLocation' must be a non-empty string."); + } + if (cacheStrategy !== "metadata" && cacheStrategy !== "content") { + errors.push('\'cacheStrategy\' must be any of "metadata", "content".'); + } + if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) { + errors.push("'cwd' must be an absolute path."); + } + if (typeof errorOnUnmatchedPattern !== "boolean") { + errors.push("'errorOnUnmatchedPattern' must be a boolean."); + } + if ( + !isEmptyArrayOrArrayOfNonEmptyString(extensions) && + extensions !== null + ) { + errors.push( + "'extensions' must be an array of non-empty strings or null.", + ); + } + if (typeof fix !== "boolean" && typeof fix !== "function") { + errors.push("'fix' must be a boolean or a function."); + } + if (fixTypes !== null && !isFixTypeArray(fixTypes)) { + errors.push( + '\'fixTypes\' must be an array of any of "directive", "problem", "suggestion", and "layout".', + ); + } + if (typeof globInputPaths !== "boolean") { + errors.push("'globInputPaths' must be a boolean."); + } + if (typeof ignore !== "boolean") { + errors.push("'ignore' must be a boolean."); + } + if (!isNonEmptyString(ignorePath) && ignorePath !== null) { + errors.push("'ignorePath' must be a non-empty string or null."); + } + if (typeof overrideConfig !== "object") { + errors.push("'overrideConfig' must be an object or null."); + } + if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null) { + errors.push("'overrideConfigFile' must be a non-empty string or null."); + } + if (typeof plugins !== "object") { + errors.push("'plugins' must be an object or null."); + } else if (plugins !== null && Object.keys(plugins).includes("")) { + errors.push("'plugins' must not include an empty string."); + } + if (Array.isArray(plugins)) { + errors.push( + "'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.", + ); + } + if ( + reportUnusedDisableDirectives !== "error" && + reportUnusedDisableDirectives !== "warn" && + reportUnusedDisableDirectives !== "off" && + reportUnusedDisableDirectives !== null + ) { + errors.push( + '\'reportUnusedDisableDirectives\' must be any of "error", "warn", "off", and null.', + ); + } + if ( + !isNonEmptyString(resolvePluginsRelativeTo) && + resolvePluginsRelativeTo !== null + ) { + errors.push( + "'resolvePluginsRelativeTo' must be a non-empty string or null.", + ); + } + if (!isEmptyArrayOrArrayOfNonEmptyString(rulePaths)) { + errors.push("'rulePaths' must be an array of non-empty strings."); + } + if (typeof useEslintrc !== "boolean") { + errors.push("'useEslintrc' must be a boolean."); + } + if (typeof passOnNoPatterns !== "boolean") { + errors.push("'passOnNoPatterns' must be a boolean."); + } + + if (errors.length > 0) { + throw new ESLintInvalidOptionsError(errors); + } + + return { + allowInlineConfig, + baseConfig, + cache, + cacheLocation, + cacheStrategy, + configFile: overrideConfigFile, + cwd: path.normalize(cwd), + errorOnUnmatchedPattern, + extensions, + fix, + fixTypes, + flags: [], // LegacyESLint does not support flags, so just ignore them. + globInputPaths, + ignore, + ignorePath, + reportUnusedDisableDirectives, + resolvePluginsRelativeTo, + rulePaths, + useEslintrc, + passOnNoPatterns, + }; } /** * Check if a value has one or more properties and that value is not undefined. * @param {any} obj The value to check. - * @returns {boolean} `true` if `obj` has one or more properties that that value is not undefined. + * @returns {boolean} `true` if `obj` has one or more properties that value is not undefined. */ function hasDefinedProperty(obj) { - if (typeof obj === "object" && obj !== null) { - for (const key in obj) { - if (typeof obj[key] !== "undefined") { - return true; - } - } - } - return false; + if (typeof obj === "object" && obj !== null) { + for (const key in obj) { + if (typeof obj[key] !== "undefined") { + return true; + } + } + } + return false; } /** @@ -345,10 +379,10 @@ function hasDefinedProperty(obj) { * @returns {Object} metadata for all enabled rules. */ function createRulesMeta(rules) { - return Array.from(rules).reduce((retVal, [id, rule]) => { - retVal[id] = rule.meta; - return retVal; - }, {}); + return Array.from(rules).reduce((retVal, [id, rule]) => { + retVal[id] = rule.meta; + return retVal; + }, {}); } /** @type {WeakMap} */ @@ -361,37 +395,37 @@ const usedDeprecatedRulesCache = new WeakMap(); * @returns {DeprecatedRuleInfo[]} The used deprecated rule list. */ function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { - const { - configArrayFactory, - options: { cwd } - } = getCLIEngineInternalSlots(cliEngine); - const filePath = path.isAbsolute(maybeFilePath) - ? maybeFilePath - : path.join(cwd, "__placeholder__.js"); - const configArray = configArrayFactory.getConfigArrayForFile(filePath); - const config = configArray.extractConfig(filePath); - - // Most files use the same config, so cache it. - if (!usedDeprecatedRulesCache.has(config)) { - const pluginRules = configArray.pluginRules; - const retv = []; - - for (const [ruleId, ruleConf] of Object.entries(config.rules)) { - if (getRuleSeverity(ruleConf) === 0) { - continue; - } - const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); - const meta = rule && rule.meta; - - if (meta && meta.deprecated) { - retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); - } - } - - usedDeprecatedRulesCache.set(config, Object.freeze(retv)); - } - - return usedDeprecatedRulesCache.get(config); + const { + configArrayFactory, + options: { cwd }, + } = getCLIEngineInternalSlots(cliEngine); + const filePath = path.isAbsolute(maybeFilePath) + ? maybeFilePath + : path.join(cwd, "__placeholder__.js"); + const configArray = configArrayFactory.getConfigArrayForFile(filePath); + const config = configArray.extractConfig(filePath); + + // Most files use the same config, so cache it. + if (!usedDeprecatedRulesCache.has(config)) { + const pluginRules = configArray.pluginRules; + const retv = []; + + for (const [ruleId, ruleConf] of Object.entries(config.rules)) { + if (getRuleSeverity(ruleConf) === 0) { + continue; + } + const rule = pluginRules.get(ruleId) || BuiltinRules.get(ruleId); + const meta = rule && rule.meta; + + if (meta && meta.deprecated) { + retv.push({ ruleId, replacedBy: meta.replacedBy || [] }); + } + } + + usedDeprecatedRulesCache.set(config, Object.freeze(retv)); + } + + return usedDeprecatedRulesCache.get(config); } /** @@ -402,19 +436,19 @@ function getOrFindUsedDeprecatedRules(cliEngine, maybeFilePath) { * @returns {LintResult[]} The processed linting results. */ function processCLIEngineLintReport(cliEngine, { results }) { - const descriptor = { - configurable: true, - enumerable: true, - get() { - return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); - } - }; - - for (const result of results) { - Object.defineProperty(result, "usedDeprecatedRules", descriptor); - } - - return results; + const descriptor = { + configurable: true, + enumerable: true, + get() { + return getOrFindUsedDeprecatedRules(cliEngine, this.filePath); + }, + }; + + for (const result of results) { + Object.defineProperty(result, "usedDeprecatedRules", descriptor); + } + + return results; } /** @@ -424,304 +458,314 @@ function processCLIEngineLintReport(cliEngine, { results }) { * @returns {number} An integer representing the order in which the two results should occur. */ function compareResultsByFilePath(a, b) { - if (a.filePath < b.filePath) { - return -1; - } + if (a.filePath < b.filePath) { + return -1; + } - if (a.filePath > b.filePath) { - return 1; - } + if (a.filePath > b.filePath) { + return 1; + } - return 0; + return 0; } /** * Main API. */ class LegacyESLint { - - /** - * The type of configuration used by this class. - * @type {string} - */ - static configType = "eslintrc"; - - /** - * Creates a new instance of the main ESLint API. - * @param {LegacyESLintOptions} options The options for this instance. - */ - constructor(options = {}) { - const processedOptions = processOptions(options); - const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins }); - const { - configArrayFactory, - lastConfigArrays - } = getCLIEngineInternalSlots(cliEngine); - let updated = false; - - /* - * Address `overrideConfig` to set override config. - * Operate the `configArrayFactory` internal slot directly because this - * functionality doesn't exist as the public API of CLIEngine. - */ - if (hasDefinedProperty(options.overrideConfig)) { - configArrayFactory.setOverrideConfig(options.overrideConfig); - updated = true; - } - - // Update caches. - if (updated) { - configArrayFactory.clearCache(); - lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); - } - - // Initialize private properties. - privateMembersMap.set(this, { - cliEngine, - options: processedOptions - }); - } - - /** - * The version text. - * @type {string} - */ - static get version() { - return version; - } - - /** - * Outputs fixes from the given results to files. - * @param {LintResult[]} results The lint results. - * @returns {Promise} Returns a promise that is used to track side effects. - */ - static async outputFixes(results) { - if (!Array.isArray(results)) { - throw new Error("'results' must be an array"); - } - - await Promise.all( - results - .filter(result => { - if (typeof result !== "object" || result === null) { - throw new Error("'results' must include only objects"); - } - return ( - typeof result.output === "string" && - path.isAbsolute(result.filePath) - ); - }) - .map(r => writeFile(r.filePath, r.output)) - ); - } - - /** - * Returns results that only contains errors. - * @param {LintResult[]} results The results to filter. - * @returns {LintResult[]} The filtered results. - */ - static getErrorResults(results) { - return CLIEngine.getErrorResults(results); - } - - /** - * Returns meta objects for each rule represented in the lint results. - * @param {LintResult[]} results The results to fetch rules meta for. - * @returns {Object} A mapping of ruleIds to rule meta objects. - */ - getRulesMetaForResults(results) { - - const resultRuleIds = new Set(); - - // first gather all ruleIds from all results - - for (const result of results) { - for (const { ruleId } of result.messages) { - resultRuleIds.add(ruleId); - } - for (const { ruleId } of result.suppressedMessages) { - resultRuleIds.add(ruleId); - } - } - - // create a map of all rules in the results - - const { cliEngine } = privateMembersMap.get(this); - const rules = cliEngine.getRules(); - const resultRules = new Map(); - - for (const [ruleId, rule] of rules) { - if (resultRuleIds.has(ruleId)) { - resultRules.set(ruleId, rule); - } - } - - return createRulesMeta(resultRules); - - } - - /* eslint-disable no-unused-vars, class-methods-use-this -- leaving for compatibility with ESLint#hasFlag */ - /** - * Indicates if the given feature flag is enabled for this instance. For this - * class, this always returns `false` because it does not support feature flags. - * @param {string} flag The feature flag to check. - * @returns {boolean} Always false. - */ - hasFlag(flag) { - return false; - } - /* eslint-enable no-unused-vars, class-methods-use-this -- reenable rules for the rest of the file */ - - /** - * Executes the current configuration on an array of file and directory names. - * @param {string[]} patterns An array of file and directory names. - * @returns {Promise} The results of linting the file patterns given. - */ - async lintFiles(patterns) { - const { cliEngine, options } = privateMembersMap.get(this); - - if (options.passOnNoPatterns && (patterns === "" || (Array.isArray(patterns) && patterns.length === 0))) { - return []; - } - - if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { - throw new Error("'patterns' must be a non-empty string or an array of non-empty strings"); - } - - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnFiles(patterns) - ); - } - - /** - * Executes the current configuration on text. - * @param {string} code A string of JavaScript code to lint. - * @param {Object} [options] The options. - * @param {string} [options.filePath] The path to the file of the source code. - * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. - * @returns {Promise} The results of linting the string of code given. - */ - async lintText(code, options = {}) { - if (typeof code !== "string") { - throw new Error("'code' must be a string"); - } - if (typeof options !== "object") { - throw new Error("'options' must be an object, null, or undefined"); - } - const { - filePath, - warnIgnored = false, - ...unknownOptions - } = options || {}; - - const unknownOptionKeys = Object.keys(unknownOptions); - - if (unknownOptionKeys.length > 0) { - throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`); - } - - if (filePath !== void 0 && !isNonEmptyString(filePath)) { - throw new Error("'options.filePath' must be a non-empty string or undefined"); - } - if (typeof warnIgnored !== "boolean") { - throw new Error("'options.warnIgnored' must be a boolean or undefined"); - } - - const { cliEngine } = privateMembersMap.get(this); - - return processCLIEngineLintReport( - cliEngine, - cliEngine.executeOnText(code, filePath, warnIgnored) - ); - } - - /** - * Returns the formatter representing the given formatter name. - * @param {string} [name] The name of the formatter to load. - * The following values are allowed: - * - `undefined` ... Load `stylish` builtin formatter. - * - A builtin formatter name ... Load the builtin formatter. - * - A third-party formatter name: - * - `foo` → `eslint-formatter-foo` - * - `@foo` → `@foo/eslint-formatter` - * - `@foo/bar` → `@foo/eslint-formatter-bar` - * - A file path ... Load the file. - * @returns {Promise} A promise resolving to the formatter object. - * This promise will be rejected if the given formatter was not found or not - * a function. - */ - async loadFormatter(name = "stylish") { - if (typeof name !== "string") { - throw new Error("'name' must be a string"); - } - - const { cliEngine, options } = privateMembersMap.get(this); - const formatter = cliEngine.getFormatter(name); - - if (typeof formatter !== "function") { - throw new Error(`Formatter must be a function, but got a ${typeof formatter}.`); - } - - return { - - /** - * The main formatter method. - * @param {LintResult[]} results The lint results to format. - * @param {ResultsMeta} resultsMeta Warning count and max threshold. - * @returns {string | Promise} The formatted lint results. - */ - format(results, resultsMeta) { - let rulesMeta = null; - - results.sort(compareResultsByFilePath); - - return formatter(results, { - ...resultsMeta, - get cwd() { - return options.cwd; - }, - get rulesMeta() { - if (!rulesMeta) { - rulesMeta = createRulesMeta(cliEngine.getRules()); - } - - return rulesMeta; - } - }); - } - }; - } - - /** - * Returns a configuration object for the given file based on the CLI options. - * This is the same logic used by the ESLint CLI executable to determine - * configuration for each file it processes. - * @param {string} filePath The path of the file to retrieve a config object for. - * @returns {Promise} A configuration object for the file. - */ - async calculateConfigForFile(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const { cliEngine } = privateMembersMap.get(this); - - return cliEngine.getConfigForFile(filePath); - } - - /** - * Checks if a given path is ignored by ESLint. - * @param {string} filePath The path of the file to check. - * @returns {Promise} Whether or not the given path is ignored. - */ - async isPathIgnored(filePath) { - if (!isNonEmptyString(filePath)) { - throw new Error("'filePath' must be a non-empty string"); - } - const { cliEngine } = privateMembersMap.get(this); - - return cliEngine.isPathIgnored(filePath); - } + /** + * The type of configuration used by this class. + * @type {string} + */ + static configType = "eslintrc"; + + /** + * Creates a new instance of the main ESLint API. + * @param {LegacyESLintOptions} options The options for this instance. + */ + constructor(options = {}) { + const processedOptions = processOptions(options); + const cliEngine = new CLIEngine(processedOptions, { + preloadedPlugins: options.plugins, + }); + const { configArrayFactory, lastConfigArrays } = + getCLIEngineInternalSlots(cliEngine); + let updated = false; + + /* + * Address `overrideConfig` to set override config. + * Operate the `configArrayFactory` internal slot directly because this + * functionality doesn't exist as the public API of CLIEngine. + */ + if (hasDefinedProperty(options.overrideConfig)) { + configArrayFactory.setOverrideConfig(options.overrideConfig); + updated = true; + } + + // Update caches. + if (updated) { + configArrayFactory.clearCache(); + lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile(); + } + + // Initialize private properties. + privateMembersMap.set(this, { + cliEngine, + options: processedOptions, + }); + } + + /** + * The version text. + * @type {string} + */ + static get version() { + return version; + } + + /** + * Outputs fixes from the given results to files. + * @param {LintResult[]} results The lint results. + * @returns {Promise} Returns a promise that is used to track side effects. + */ + static async outputFixes(results) { + if (!Array.isArray(results)) { + throw new Error("'results' must be an array"); + } + + await Promise.all( + results + .filter(result => { + if (typeof result !== "object" || result === null) { + throw new Error("'results' must include only objects"); + } + return ( + typeof result.output === "string" && + path.isAbsolute(result.filePath) + ); + }) + .map(r => writeFile(r.filePath, r.output)), + ); + } + + /** + * Returns results that only contains errors. + * @param {LintResult[]} results The results to filter. + * @returns {LintResult[]} The filtered results. + */ + static getErrorResults(results) { + return CLIEngine.getErrorResults(results); + } + + /** + * Returns meta objects for each rule represented in the lint results. + * @param {LintResult[]} results The results to fetch rules meta for. + * @returns {Object} A mapping of ruleIds to rule meta objects. + */ + getRulesMetaForResults(results) { + const resultRuleIds = new Set(); + + // first gather all ruleIds from all results + + for (const result of results) { + for (const { ruleId } of result.messages) { + resultRuleIds.add(ruleId); + } + for (const { ruleId } of result.suppressedMessages) { + resultRuleIds.add(ruleId); + } + } + + // create a map of all rules in the results + + const { cliEngine } = privateMembersMap.get(this); + const rules = cliEngine.getRules(); + const resultRules = new Map(); + + for (const [ruleId, rule] of rules) { + if (resultRuleIds.has(ruleId)) { + resultRules.set(ruleId, rule); + } + } + + return createRulesMeta(resultRules); + } + + /* eslint-disable no-unused-vars, class-methods-use-this -- leaving for compatibility with ESLint#hasFlag */ + /** + * Indicates if the given feature flag is enabled for this instance. For this + * class, this always returns `false` because it does not support feature flags. + * @param {string} flag The feature flag to check. + * @returns {boolean} Always false. + */ + hasFlag(flag) { + return false; + } + /* eslint-enable no-unused-vars, class-methods-use-this -- reenable rules for the rest of the file */ + + /** + * Executes the current configuration on an array of file and directory names. + * @param {string[]} patterns An array of file and directory names. + * @returns {Promise} The results of linting the file patterns given. + */ + async lintFiles(patterns) { + const { cliEngine, options } = privateMembersMap.get(this); + + if ( + options.passOnNoPatterns && + (patterns === "" || + (Array.isArray(patterns) && patterns.length === 0)) + ) { + return []; + } + + if (!isNonEmptyString(patterns) && !isArrayOfNonEmptyString(patterns)) { + throw new Error( + "'patterns' must be a non-empty string or an array of non-empty strings", + ); + } + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnFiles(patterns), + ); + } + + /** + * Executes the current configuration on text. + * @param {string} code A string of JavaScript code to lint. + * @param {Object} [options] The options. + * @param {string} [options.filePath] The path to the file of the source code. + * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path. + * @returns {Promise} The results of linting the string of code given. + */ + async lintText(code, options = {}) { + if (typeof code !== "string") { + throw new Error("'code' must be a string"); + } + if (typeof options !== "object") { + throw new Error("'options' must be an object, null, or undefined"); + } + const { + filePath, + warnIgnored = false, + ...unknownOptions + } = options || {}; + + const unknownOptionKeys = Object.keys(unknownOptions); + + if (unknownOptionKeys.length > 0) { + throw new Error( + `'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`, + ); + } + + if (filePath !== void 0 && !isNonEmptyString(filePath)) { + throw new Error( + "'options.filePath' must be a non-empty string or undefined", + ); + } + if (typeof warnIgnored !== "boolean") { + throw new Error( + "'options.warnIgnored' must be a boolean or undefined", + ); + } + + const { cliEngine } = privateMembersMap.get(this); + + return processCLIEngineLintReport( + cliEngine, + cliEngine.executeOnText(code, filePath, warnIgnored), + ); + } + + /** + * Returns the formatter representing the given formatter name. + * @param {string} [name] The name of the formatter to load. + * The following values are allowed: + * - `undefined` ... Load `stylish` builtin formatter. + * - A builtin formatter name ... Load the builtin formatter. + * - A third-party formatter name: + * - `foo` → `eslint-formatter-foo` + * - `@foo` → `@foo/eslint-formatter` + * - `@foo/bar` → `@foo/eslint-formatter-bar` + * - A file path ... Load the file. + * @returns {Promise} A promise resolving to the formatter object. + * This promise will be rejected if the given formatter was not found or not + * a function. + */ + async loadFormatter(name = "stylish") { + if (typeof name !== "string") { + throw new Error("'name' must be a string"); + } + + const { cliEngine, options } = privateMembersMap.get(this); + const formatter = cliEngine.getFormatter(name); + + if (typeof formatter !== "function") { + throw new Error( + `Formatter must be a function, but got a ${typeof formatter}.`, + ); + } + + return { + /** + * The main formatter method. + * @param {LintResult[]} results The lint results to format. + * @param {ResultsMeta} resultsMeta Warning count and max threshold. + * @returns {string | Promise} The formatted lint results. + */ + format(results, resultsMeta) { + let rulesMeta = null; + + results.sort(compareResultsByFilePath); + + return formatter(results, { + ...resultsMeta, + get cwd() { + return options.cwd; + }, + get rulesMeta() { + if (!rulesMeta) { + rulesMeta = createRulesMeta(cliEngine.getRules()); + } + + return rulesMeta; + }, + }); + }, + }; + } + + /** + * Returns a configuration object for the given file based on the CLI options. + * This is the same logic used by the ESLint CLI executable to determine + * configuration for each file it processes. + * @param {string} filePath The path of the file to retrieve a config object for. + * @returns {Promise} A configuration object for the file. + */ + async calculateConfigForFile(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.getConfigForFile(filePath); + } + + /** + * Checks if a given path is ignored by ESLint. + * @param {string} filePath The path of the file to check. + * @returns {Promise} Whether or not the given path is ignored. + */ + async isPathIgnored(filePath) { + if (!isNonEmptyString(filePath)) { + throw new Error("'filePath' must be a non-empty string"); + } + const { cliEngine } = privateMembersMap.get(this); + + return cliEngine.isPathIgnored(filePath); + } } //------------------------------------------------------------------------------ @@ -729,14 +773,14 @@ class LegacyESLint { //------------------------------------------------------------------------------ module.exports = { - LegacyESLint, - - /** - * Get the private class members of a given ESLint instance for tests. - * @param {ESLint} instance The ESLint instance to get. - * @returns {ESLintPrivateMembers} The instance's private class members. - */ - getESLintPrivateMembers(instance) { - return privateMembersMap.get(instance); - } + LegacyESLint, + + /** + * Get the private class members of a given ESLint instance for tests. + * @param {ESLint} instance The ESLint instance to get. + * @returns {ESLintPrivateMembers} The instance's private class members. + */ + getESLintPrivateMembers(instance) { + return privateMembersMap.get(instance); + }, }; diff --git a/lib/languages/js/index.js b/lib/languages/js/index.js index 98d72a54c727..6c0150a20892 100644 --- a/lib/languages/js/index.js +++ b/lib/languages/js/index.js @@ -42,19 +42,19 @@ const parserSymbol = Symbol.for("eslint.RuleTester.parser"); * @returns {ScopeManager} The analysis result. */ function analyzeScope(ast, languageOptions, visitorKeys) { - const parserOptions = languageOptions.parserOptions; - const ecmaFeatures = parserOptions.ecmaFeatures || {}; - const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION; - - return eslintScope.analyze(ast, { - ignoreEval: true, - nodejsScope: ecmaFeatures.globalReturn, - impliedStrict: ecmaFeatures.impliedStrict, - ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, - sourceType: languageOptions.sourceType || "script", - childVisitorKeys: visitorKeys || evk.KEYS, - fallback: evk.getKeys - }); + const parserOptions = languageOptions.parserOptions; + const ecmaFeatures = parserOptions.ecmaFeatures || {}; + const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION; + + return eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: ecmaFeatures.globalReturn, + impliedStrict: ecmaFeatures.impliedStrict, + ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, + sourceType: languageOptions.sourceType || "script", + childVisitorKeys: visitorKeys || evk.KEYS, + fallback: evk.getKeys, + }); } /** @@ -63,7 +63,7 @@ function analyzeScope(ast, languageOptions, visitorKeys) { * @returns {boolean} True if the parser is Espree or false if not. */ function isEspree(parser) { - return !!(parser === espree || parser[parserSymbol] === espree); + return !!(parser === espree || parser[parserSymbol] === espree); } /** @@ -73,29 +73,28 @@ function isEspree(parser) { * @returns {number} normalized ECMAScript version */ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { - - switch (ecmaVersion) { - case 3: - return 3; - - // void 0 = no ecmaVersion specified so use the default - case 5: - case void 0: - return 5; - - default: - if (typeof ecmaVersion === "number") { - return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009; - } - } - - /* - * We default to the latest supported ecmaVersion for everything else. - * Remember, this is for languageOptions.ecmaVersion, which sets the version - * that is used for a number of processes inside of ESLint. It's normally - * safe to assume people want the latest unless otherwise specified. - */ - return LATEST_ECMA_VERSION; + switch (ecmaVersion) { + case 3: + return 3; + + // void 0 = no ecmaVersion specified so use the default + case 5: + case void 0: + return 5; + + default: + if (typeof ecmaVersion === "number") { + return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009; + } + } + + /* + * We default to the latest supported ecmaVersion for everything else. + * Remember, this is for languageOptions.ecmaVersion, which sets the version + * that is used for a number of processes inside of ESLint. It's normally + * safe to assume people want the latest unless otherwise specified. + */ + return LATEST_ECMA_VERSION; } //----------------------------------------------------------------------------- @@ -106,231 +105,231 @@ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { * @type {Language} */ module.exports = { - - fileType: "text", - lineStart: 1, - columnStart: 0, - nodeTypeKey: "type", - visitorKeys: evk.KEYS, - - defaultLanguageOptions: { - sourceType: "module", - ecmaVersion: "latest", - parser: espree, - parserOptions: {} - }, - - validateLanguageOptions, - - /** - * Normalizes the language options. - * @param {Object} languageOptions The language options to normalize. - * @returns {Object} The normalized language options. - */ - normalizeLanguageOptions(languageOptions) { - - languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions( - languageOptions.ecmaVersion - ); - - // Espree expects this information to be passed in - if (isEspree(languageOptions.parser)) { - const parserOptions = languageOptions.parserOptions; - - if (languageOptions.sourceType) { - - parserOptions.sourceType = languageOptions.sourceType; - - if ( - parserOptions.sourceType === "module" && - parserOptions.ecmaFeatures && - parserOptions.ecmaFeatures.globalReturn - ) { - parserOptions.ecmaFeatures.globalReturn = false; - } - } - } - - return languageOptions; - - }, - - /** - * Determines if a given node matches a given selector class. - * @param {string} className The class name to check. - * @param {ASTNode} node The node to check. - * @param {Array} ancestry The ancestry of the node. - * @returns {boolean} True if there's a match, false if not. - * @throws {Error} When an unknown class name is passed. - */ - matchesSelectorClass(className, node, ancestry) { - - /* - * Copyright (c) 2013, Joel Feenstra - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name of the ESQuery nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - - switch (className.toLowerCase()) { - - case "statement": - if (node.type.slice(-9) === "Statement") { - return true; - } - - // fallthrough: interface Declaration <: Statement { } - - case "declaration": - return node.type.slice(-11) === "Declaration"; - - case "pattern": - if (node.type.slice(-7) === "Pattern") { - return true; - } - - // fallthrough: interface Expression <: Node, Pattern { } - - case "expression": - return node.type.slice(-10) === "Expression" || - node.type.slice(-7) === "Literal" || - ( - node.type === "Identifier" && - (ancestry.length === 0 || ancestry[0].type !== "MetaProperty") - ) || - node.type === "MetaProperty"; - - case "function": - return node.type === "FunctionDeclaration" || - node.type === "FunctionExpression" || - node.type === "ArrowFunctionExpression"; - - default: - throw new Error(`Unknown class name: ${className}`); - } - }, - - /** - * Parses the given file into an AST. - * @param {File} file The virtual file to parse. - * @param {Object} options Additional options passed from ESLint. - * @param {LanguageOptions} options.languageOptions The language options. - * @returns {Object} The result of parsing. - */ - parse(file, { languageOptions }) { - - // Note: BOM already removed - const { body: text, path: filePath } = file; - const textToParse = text.replace(astUtils.shebangPattern, (match, captured) => `//${captured}`); - const { ecmaVersion, sourceType, parser } = languageOptions; - const parserOptions = Object.assign( - { ecmaVersion, sourceType }, - languageOptions.parserOptions, - { - loc: true, - range: true, - raw: true, - tokens: true, - comment: true, - eslintVisitorKeys: true, - eslintScopeManager: true, - filePath - } - ); - - /* - * Check for parsing errors first. If there's a parsing error, nothing - * else can happen. However, a parsing error does not throw an error - * from this method - it's just considered a fatal error message, a - * problem that ESLint identified just like any other. - */ - try { - debug("Parsing:", filePath); - const parseResult = (typeof parser.parseForESLint === "function") - ? parser.parseForESLint(textToParse, parserOptions) - : { ast: parser.parse(textToParse, parserOptions) }; - - debug("Parsing successful:", filePath); - - const { - ast, - services: parserServices = {}, - visitorKeys = evk.KEYS, - scopeManager - } = parseResult; - - return { - ok: true, - ast, - parserServices, - visitorKeys, - scopeManager - }; - } catch (ex) { - - // If the message includes a leading line number, strip it: - const message = ex.message.replace(/^line \d+:/iu, "").trim(); - - debug("%s\n%s", message, ex.stack); - - return { - ok: false, - errors: [{ - message, - line: ex.lineNumber, - column: ex.column - }] - }; - } - - }, - - /** - * Creates a new `SourceCode` object from the given information. - * @param {File} file The virtual file to create a `SourceCode` object from. - * @param {OkParseResult} parseResult The result returned from `parse()`. - * @param {Object} options Additional options passed from ESLint. - * @param {LanguageOptions} options.languageOptions The language options. - * @returns {SourceCode} The new `SourceCode` object. - */ - createSourceCode(file, parseResult, { languageOptions }) { - - const { body: text, path: filePath, bom: hasBOM } = file; - const { ast, parserServices, visitorKeys } = parseResult; - - debug("Scope analysis:", filePath); - const scopeManager = parseResult.scopeManager || analyzeScope(ast, languageOptions, visitorKeys); - - debug("Scope analysis successful:", filePath); - - return new SourceCode({ - text, - ast, - hasBOM, - parserServices, - scopeManager, - visitorKeys - }); - } - + fileType: "text", + lineStart: 1, + columnStart: 0, + nodeTypeKey: "type", + visitorKeys: evk.KEYS, + + defaultLanguageOptions: { + sourceType: "module", + ecmaVersion: "latest", + parser: espree, + parserOptions: {}, + }, + + validateLanguageOptions, + + /** + * Normalizes the language options. + * @param {Object} languageOptions The language options to normalize. + * @returns {Object} The normalized language options. + */ + normalizeLanguageOptions(languageOptions) { + languageOptions.ecmaVersion = normalizeEcmaVersionForLanguageOptions( + languageOptions.ecmaVersion, + ); + + // Espree expects this information to be passed in + if (isEspree(languageOptions.parser)) { + const parserOptions = languageOptions.parserOptions; + + if (languageOptions.sourceType) { + parserOptions.sourceType = languageOptions.sourceType; + + if ( + parserOptions.sourceType === "module" && + parserOptions.ecmaFeatures && + parserOptions.ecmaFeatures.globalReturn + ) { + parserOptions.ecmaFeatures.globalReturn = false; + } + } + } + + return languageOptions; + }, + + /** + * Determines if a given node matches a given selector class. + * @param {string} className The class name to check. + * @param {ASTNode} node The node to check. + * @param {Array} ancestry The ancestry of the node. + * @returns {boolean} True if there's a match, false if not. + * @throws {Error} When an unknown class name is passed. + */ + matchesSelectorClass(className, node, ancestry) { + /* + * Copyright (c) 2013, Joel Feenstra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the ESQuery nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL JOEL FEENSTRA BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + switch (className.toLowerCase()) { + case "statement": + if (node.type.slice(-9) === "Statement") { + return true; + } + + // fallthrough: interface Declaration <: Statement { } + + case "declaration": + return node.type.slice(-11) === "Declaration"; + + case "pattern": + if (node.type.slice(-7) === "Pattern") { + return true; + } + + // fallthrough: interface Expression <: Node, Pattern { } + + case "expression": + return ( + node.type.slice(-10) === "Expression" || + node.type.slice(-7) === "Literal" || + (node.type === "Identifier" && + (ancestry.length === 0 || + ancestry[0].type !== "MetaProperty")) || + node.type === "MetaProperty" + ); + + case "function": + return ( + node.type === "FunctionDeclaration" || + node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression" + ); + + default: + throw new Error(`Unknown class name: ${className}`); + } + }, + + /** + * Parses the given file into an AST. + * @param {File} file The virtual file to parse. + * @param {Object} options Additional options passed from ESLint. + * @param {LanguageOptions} options.languageOptions The language options. + * @returns {Object} The result of parsing. + */ + parse(file, { languageOptions }) { + // Note: BOM already removed + const { body: text, path: filePath } = file; + const textToParse = text.replace( + astUtils.shebangPattern, + (match, captured) => `//${captured}`, + ); + const { ecmaVersion, sourceType, parser } = languageOptions; + const parserOptions = Object.assign( + { ecmaVersion, sourceType }, + languageOptions.parserOptions, + { + loc: true, + range: true, + raw: true, + tokens: true, + comment: true, + eslintVisitorKeys: true, + eslintScopeManager: true, + filePath, + }, + ); + + /* + * Check for parsing errors first. If there's a parsing error, nothing + * else can happen. However, a parsing error does not throw an error + * from this method - it's just considered a fatal error message, a + * problem that ESLint identified just like any other. + */ + try { + debug("Parsing:", filePath); + const parseResult = + typeof parser.parseForESLint === "function" + ? parser.parseForESLint(textToParse, parserOptions) + : { ast: parser.parse(textToParse, parserOptions) }; + + debug("Parsing successful:", filePath); + + const { + ast, + services: parserServices = {}, + visitorKeys = evk.KEYS, + scopeManager, + } = parseResult; + + return { + ok: true, + ast, + parserServices, + visitorKeys, + scopeManager, + }; + } catch (ex) { + // If the message includes a leading line number, strip it: + const message = ex.message.replace(/^line \d+:/iu, "").trim(); + + debug("%s\n%s", message, ex.stack); + + return { + ok: false, + errors: [ + { + message, + line: ex.lineNumber, + column: ex.column, + }, + ], + }; + } + }, + + /** + * Creates a new `SourceCode` object from the given information. + * @param {File} file The virtual file to create a `SourceCode` object from. + * @param {OkParseResult} parseResult The result returned from `parse()`. + * @param {Object} options Additional options passed from ESLint. + * @param {LanguageOptions} options.languageOptions The language options. + * @returns {SourceCode} The new `SourceCode` object. + */ + createSourceCode(file, parseResult, { languageOptions }) { + const { body: text, path: filePath, bom: hasBOM } = file; + const { ast, parserServices, visitorKeys } = parseResult; + + debug("Scope analysis:", filePath); + const scopeManager = + parseResult.scopeManager || + analyzeScope(ast, languageOptions, visitorKeys); + + debug("Scope analysis successful:", filePath); + + return new SourceCode({ + text, + ast, + hasBOM, + parserServices, + scopeManager, + visitorKeys, + }); + }, }; diff --git a/lib/languages/js/source-code/index.js b/lib/languages/js/source-code/index.js index 1ecfbe470aa7..f4003e393ae8 100644 --- a/lib/languages/js/source-code/index.js +++ b/lib/languages/js/source-code/index.js @@ -3,5 +3,5 @@ const SourceCode = require("./source-code"); module.exports = { - SourceCode + SourceCode, }; diff --git a/lib/languages/js/source-code/source-code.js b/lib/languages/js/source-code/source-code.js index c308a325792c..852587c55219 100644 --- a/lib/languages/js/source-code/source-code.js +++ b/lib/languages/js/source-code/source-code.js @@ -8,21 +8,21 @@ // Requirements //------------------------------------------------------------------------------ -const - { isCommentToken } = require("@eslint-community/eslint-utils"), - TokenStore = require("./token-store"), - astUtils = require("../../../shared/ast-utils"), - Traverser = require("../../../shared/traverser"), - globals = require("../../../../conf/globals"), - { - directivesPattern - } = require("../../../shared/directives"), - - CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"), - createEmitter = require("../../../linter/safe-emitter"), - { ConfigCommentParser, VisitNodeStep, CallMethodStep, Directive } = require("@eslint/plugin-kit"), - - eslintScope = require("eslint-scope"); +const { isCommentToken } = require("@eslint-community/eslint-utils"), + TokenStore = require("./token-store"), + astUtils = require("../../../shared/ast-utils"), + Traverser = require("../../../shared/traverser"), + globals = require("../../../../conf/globals"), + { directivesPattern } = require("../../../shared/directives"), + CodePathAnalyzer = require("../../../linter/code-path-analysis/code-path-analyzer"), + createEmitter = require("../../../linter/safe-emitter"), + { + ConfigCommentParser, + VisitNodeStep, + CallMethodStep, + Directive, + } = require("@eslint/plugin-kit"), + eslintScope = require("eslint-scope"); //------------------------------------------------------------------------------ // Type Definitions @@ -41,13 +41,13 @@ const const commentParser = new ConfigCommentParser(); const CODE_PATH_EVENTS = [ - "onCodePathStart", - "onCodePathEnd", - "onCodePathSegmentStart", - "onCodePathSegmentEnd", - "onCodePathSegmentLoop", - "onUnreachableCodePathSegmentStart", - "onUnreachableCodePathSegmentEnd" + "onCodePathStart", + "onCodePathEnd", + "onCodePathSegmentStart", + "onCodePathSegmentEnd", + "onCodePathSegmentLoop", + "onUnreachableCodePathSegmentStart", + "onUnreachableCodePathSegmentEnd", ]; /** @@ -58,25 +58,25 @@ const CODE_PATH_EVENTS = [ * @private */ function validate(ast) { - if (!ast) { - throw new TypeError(`Unexpected empty AST. (${ast})`); - } + if (!ast) { + throw new TypeError(`Unexpected empty AST. (${ast})`); + } - if (!ast.tokens) { - throw new TypeError("AST is missing the tokens array."); - } + if (!ast.tokens) { + throw new TypeError("AST is missing the tokens array."); + } - if (!ast.comments) { - throw new TypeError("AST is missing the comments array."); - } + if (!ast.comments) { + throw new TypeError("AST is missing the comments array."); + } - if (!ast.loc) { - throw new TypeError("AST is missing location information."); - } + if (!ast.loc) { + throw new TypeError("AST is missing location information."); + } - if (!ast.range) { - throw new TypeError("AST is missing range information"); - } + if (!ast.range) { + throw new TypeError("AST is missing range information"); + } } /** @@ -85,21 +85,20 @@ function validate(ast) { * @returns {Object} The globals for the given ecmaVersion. */ function getGlobalsForEcmaVersion(ecmaVersion) { + switch (ecmaVersion) { + case 3: + return globals.es3; - switch (ecmaVersion) { - case 3: - return globals.es3; + case 5: + return globals.es5; - case 5: - return globals.es5; + default: + if (ecmaVersion < 2015) { + return globals[`es${ecmaVersion + 2009}`]; + } - default: - if (ecmaVersion < 2015) { - return globals[`es${ecmaVersion + 2009}`]; - } - - return globals[`es${ecmaVersion}`]; - } + return globals[`es${ecmaVersion}`]; + } } /** @@ -109,8 +108,12 @@ function getGlobalsForEcmaVersion(ecmaVersion) { * @private */ function looksLikeExport(astNode) { - return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" || - astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier"; + return ( + astNode.type === "ExportDefaultDeclaration" || + astNode.type === "ExportNamedDeclaration" || + astNode.type === "ExportAllDeclaration" || + astNode.type === "ExportSpecifier" + ); } /** @@ -121,19 +124,23 @@ function looksLikeExport(astNode) { * @private */ function sortedMerge(tokens, comments) { - const result = []; - let tokenIndex = 0; - let commentIndex = 0; - - while (tokenIndex < tokens.length || commentIndex < comments.length) { - if (commentIndex >= comments.length || tokenIndex < tokens.length && tokens[tokenIndex].range[0] < comments[commentIndex].range[0]) { - result.push(tokens[tokenIndex++]); - } else { - result.push(comments[commentIndex++]); - } - } - - return result; + const result = []; + let tokenIndex = 0; + let commentIndex = 0; + + while (tokenIndex < tokens.length || commentIndex < comments.length) { + if ( + commentIndex >= comments.length || + (tokenIndex < tokens.length && + tokens[tokenIndex].range[0] < comments[commentIndex].range[0]) + ) { + result.push(tokens[tokenIndex++]); + } else { + result.push(comments[commentIndex++]); + } + } + + return result; } /** @@ -144,26 +151,28 @@ function sortedMerge(tokens, comments) { * @throws Error if global value is invalid */ function normalizeConfigGlobal(configuredValue) { - switch (configuredValue) { - case "off": - return "off"; - - case true: - case "true": - case "writeable": - case "writable": - return "writable"; - - case null: - case false: - case "false": - case "readable": - case "readonly": - return "readonly"; - - default: - throw new Error(`'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`); - } + switch (configuredValue) { + case "off": + return "off"; + + case true: + case "true": + case "writeable": + case "writable": + return "writable"; + + case null: + case false: + case "false": + case "readable": + case "readonly": + return "readonly"; + + default: + throw new Error( + `'${configuredValue}' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')`, + ); + } } /** @@ -174,8 +183,11 @@ function normalizeConfigGlobal(configuredValue) { * @private */ function nodesOrTokensOverlap(first, second) { - return (first.range[0] <= second.range[0] && first.range[1] >= second.range[0]) || - (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]); + return ( + (first.range[0] <= second.range[0] && + first.range[1] >= second.range[0]) || + (second.range[0] <= first.range[0] && second.range[1] >= first.range[0]) + ); } /** @@ -191,41 +203,41 @@ function nodesOrTokensOverlap(first, second) { * @public */ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { - if (nodesOrTokensOverlap(first, second)) { - return false; - } - - const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0] - ? [first, second] - : [second, first]; - const firstToken = sourceCode.getLastToken(startingNodeOrToken) || startingNodeOrToken; - const finalToken = sourceCode.getFirstToken(endingNodeOrToken) || endingNodeOrToken; - let currentToken = firstToken; - - while (currentToken !== finalToken) { - const nextToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); - - if ( - currentToken.range[1] !== nextToken.range[0] || - - /* - * For backward compatibility, check spaces in JSXText. - * https://github.com/eslint/eslint/issues/12614 - */ - ( - checkInsideOfJSXText && - nextToken !== finalToken && - nextToken.type === "JSXText" && - /\s/u.test(nextToken.value) - ) - ) { - return true; - } - - currentToken = nextToken; - } - - return false; + if (nodesOrTokensOverlap(first, second)) { + return false; + } + + const [startingNodeOrToken, endingNodeOrToken] = + first.range[1] <= second.range[0] ? [first, second] : [second, first]; + const firstToken = + sourceCode.getLastToken(startingNodeOrToken) || startingNodeOrToken; + const finalToken = + sourceCode.getFirstToken(endingNodeOrToken) || endingNodeOrToken; + let currentToken = firstToken; + + while (currentToken !== finalToken) { + const nextToken = sourceCode.getTokenAfter(currentToken, { + includeComments: true, + }); + + if ( + currentToken.range[1] !== nextToken.range[0] || + /* + * For backward compatibility, check spaces in JSXText. + * https://github.com/eslint/eslint/issues/12614 + */ + (checkInsideOfJSXText && + nextToken !== finalToken && + nextToken.type === "JSXText" && + /\s/u.test(nextToken.value)) + ) { + return true; + } + + currentToken = nextToken; + } + + return false; } //----------------------------------------------------------------------------- @@ -241,62 +253,69 @@ function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) { * @param {Object|undefined} inlineGlobals The globals declared in the source code * @returns {void} */ -function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) { - - // Define configured global variables. - for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(inlineGlobals)])) { - - /* - * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would - * typically be caught when validating a config anyway (validity for inline global comments is checked separately). - */ - const configValue = configGlobals[id] === void 0 ? void 0 : normalizeConfigGlobal(configGlobals[id]); - const commentValue = inlineGlobals[id] && inlineGlobals[id].value; - const value = commentValue || configValue; - const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments; - - if (value === "off") { - continue; - } - - let variable = globalScope.set.get(id); - - if (!variable) { - variable = new eslintScope.Variable(id, globalScope); - - globalScope.variables.push(variable); - globalScope.set.set(id, variable); - } - - variable.eslintImplicitGlobalSetting = configValue; - variable.eslintExplicitGlobal = sourceComments !== void 0; - variable.eslintExplicitGlobalComments = sourceComments; - variable.writeable = (value === "writable"); - } - - /* - * "through" contains all references which definitions cannot be found. - * Since we augment the global scope using configuration, we need to update - * references and remove the ones that were added by configuration. - */ - globalScope.through = globalScope.through.filter(reference => { - const name = reference.identifier.name; - const variable = globalScope.set.get(name); - - if (variable) { - - /* - * Links the variable and the reference. - * And this reference is removed from `Scope#through`. - */ - reference.resolved = variable; - variable.references.push(reference); - - return false; - } - - return true; - }); +function addDeclaredGlobals( + globalScope, + configGlobals = {}, + inlineGlobals = {}, +) { + // Define configured global variables. + for (const id of new Set([ + ...Object.keys(configGlobals), + ...Object.keys(inlineGlobals), + ])) { + /* + * `normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would + * typically be caught when validating a config anyway (validity for inline global comments is checked separately). + */ + const configValue = + configGlobals[id] === void 0 + ? void 0 + : normalizeConfigGlobal(configGlobals[id]); + const commentValue = inlineGlobals[id] && inlineGlobals[id].value; + const value = commentValue || configValue; + const sourceComments = inlineGlobals[id] && inlineGlobals[id].comments; + + if (value === "off") { + continue; + } + + let variable = globalScope.set.get(id); + + if (!variable) { + variable = new eslintScope.Variable(id, globalScope); + + globalScope.variables.push(variable); + globalScope.set.set(id, variable); + } + + variable.eslintImplicitGlobalSetting = configValue; + variable.eslintExplicitGlobal = sourceComments !== void 0; + variable.eslintExplicitGlobalComments = sourceComments; + variable.writeable = value === "writable"; + } + + /* + * "through" contains all references which definitions cannot be found. + * Since we augment the global scope using configuration, we need to update + * references and remove the ones that were added by configuration. + */ + globalScope.through = globalScope.through.filter(reference => { + const name = reference.identifier.name; + const variable = globalScope.set.get(name); + + if (variable) { + /* + * Links the variable and the reference. + * And this reference is removed from `Scope#through`. + */ + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); } /** @@ -308,16 +327,14 @@ function addDeclaredGlobals(globalScope, configGlobals = {}, inlineGlobals = {}) * @returns {void} */ function markExportedVariables(globalScope, variables) { - - Object.keys(variables).forEach(name => { - const variable = globalScope.set.get(name); - - if (variable) { - variable.eslintUsed = true; - variable.eslintExported = true; - } - }); - + Object.keys(variables).forEach(name => { + const variable = globalScope.set.get(name); + + if (variable) { + variable.eslintUsed = true; + variable.eslintExported = true; + } + }); } //------------------------------------------------------------------------------ @@ -331,876 +348,930 @@ const caches = Symbol("caches"); * @implements {ISourceCode} */ class SourceCode extends TokenStore { - - /** - * The cache of steps that were taken while traversing the source code. - * @type {Array} - */ - #steps; - - /** - * Creates a new instance. - * @param {string|Object} textOrConfig The source code text or config object. - * @param {string} textOrConfig.text The source code text. - * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. - * @param {boolean} textOrConfig.hasBOM Indicates if the text has a Unicode BOM. - * @param {Object|null} textOrConfig.parserServices The parser services. - * @param {ScopeManager|null} textOrConfig.scopeManager The scope of this source code. - * @param {Object|null} textOrConfig.visitorKeys The visitor keys to traverse AST. - * @param {ASTNode} [astIfNoConfig] The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. - */ - constructor(textOrConfig, astIfNoConfig) { - let text, hasBOM, ast, parserServices, scopeManager, visitorKeys; - - // Process overloading of arguments - if (typeof textOrConfig === "string") { - text = textOrConfig; - ast = astIfNoConfig; - hasBOM = false; - } else if (typeof textOrConfig === "object" && textOrConfig !== null) { - text = textOrConfig.text; - ast = textOrConfig.ast; - hasBOM = textOrConfig.hasBOM; - parserServices = textOrConfig.parserServices; - scopeManager = textOrConfig.scopeManager; - visitorKeys = textOrConfig.visitorKeys; - } - - validate(ast); - super(ast.tokens, ast.comments); - - /** - * General purpose caching for the class. - */ - this[caches] = new Map([ - ["scopes", new WeakMap()], - ["vars", new Map()], - ["configNodes", void 0] - ]); - - /** - * Indicates if the AST is ESTree compatible. - * @type {boolean} - */ - this.isESTree = ast.type === "Program"; - - /* - * Backwards compatibility for BOM handling. - * - * The `hasBOM` property has been available on the `SourceCode` object - * for a long time and is used to indicate if the source contains a BOM. - * The linter strips the BOM and just passes the `hasBOM` property to the - * `SourceCode` constructor to make it easier for languages to not deal with - * the BOM. - * - * However, the text passed in to the `SourceCode` constructor might still - * have a BOM if the constructor is called outside of the linter, so we still - * need to check for the BOM in the text. - */ - const textHasBOM = text.charCodeAt(0) === 0xFEFF; - - /** - * The flag to indicate that the source code has Unicode BOM. - * @type {boolean} - */ - this.hasBOM = textHasBOM || !!hasBOM; - - /** - * The original text source code. - * BOM was stripped from this text. - * @type {string} - */ - this.text = (textHasBOM ? text.slice(1) : text); - - /** - * The parsed AST for the source code. - * @type {ASTNode} - */ - this.ast = ast; - - /** - * The parser services of this source code. - * @type {Object} - */ - this.parserServices = parserServices || {}; - - /** - * The scope of this source code. - * @type {ScopeManager|null} - */ - this.scopeManager = scopeManager || null; - - /** - * The visitor keys to traverse AST. - * @type {Object} - */ - this.visitorKeys = visitorKeys || Traverser.DEFAULT_VISITOR_KEYS; - - // Check the source text for the presence of a shebang since it is parsed as a standard line comment. - const shebangMatched = this.text.match(astUtils.shebangPattern); - const hasShebang = shebangMatched && ast.comments.length && ast.comments[0].value === shebangMatched[1]; - - if (hasShebang) { - ast.comments[0].type = "Shebang"; - } - - this.tokensAndComments = sortedMerge(ast.tokens, ast.comments); - - /** - * The source code split into lines according to ECMA-262 specification. - * This is done to avoid each rule needing to do so separately. - * @type {string[]} - */ - this.lines = []; - this.lineStartIndices = [0]; - - const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); - let match; - - /* - * Previously, this was implemented using a regex that - * matched a sequence of non-linebreak characters followed by a - * linebreak, then adding the lengths of the matches. However, - * this caused a catastrophic backtracking issue when the end - * of a file contained a large number of non-newline characters. - * To avoid this, the current implementation just matches newlines - * and uses match.index to get the correct line start indices. - */ - while ((match = lineEndingPattern.exec(this.text))) { - this.lines.push(this.text.slice(this.lineStartIndices.at(-1), match.index)); - this.lineStartIndices.push(match.index + match[0].length); - } - this.lines.push(this.text.slice(this.lineStartIndices.at(-1))); - - // don't allow further modification of this object - Object.freeze(this); - Object.freeze(this.lines); - } - - /** - * Split the source code into multiple lines based on the line delimiters. - * @param {string} text Source code as a string. - * @returns {string[]} Array of source code lines. - * @public - */ - static splitLines(text) { - return text.split(astUtils.createGlobalLinebreakMatcher()); - } - - /** - * Gets the source code for the given node. - * @param {ASTNode} [node] The AST node to get the text for. - * @param {int} [beforeCount] The number of characters before the node to retrieve. - * @param {int} [afterCount] The number of characters after the node to retrieve. - * @returns {string} The text representing the AST node. - * @public - */ - getText(node, beforeCount, afterCount) { - if (node) { - return this.text.slice(Math.max(node.range[0] - (beforeCount || 0), 0), - node.range[1] + (afterCount || 0)); - } - return this.text; - } - - /** - * Gets the entire source text split into an array of lines. - * @returns {Array} The source text as an array of lines. - * @public - */ - getLines() { - return this.lines; - } - - /** - * Retrieves an array containing all comments in the source code. - * @returns {ASTNode[]} An array of comment nodes. - * @public - */ - getAllComments() { - return this.ast.comments; - } - - /** - * Retrieves the JSDoc comment for a given node. - * @param {ASTNode} node The AST node to get the comment for. - * @returns {Token|null} The Block comment token containing the JSDoc comment - * for the given node or null if not found. - * @public - * @deprecated - */ - getJSDocComment(node) { - - /** - * Checks for the presence of a JSDoc comment for the given node and returns it. - * @param {ASTNode} astNode The AST node to get the comment for. - * @returns {Token|null} The Block comment token containing the JSDoc comment - * for the given node or null if not found. - * @private - */ - const findJSDocComment = astNode => { - const tokenBefore = this.getTokenBefore(astNode, { includeComments: true }); - - if ( - tokenBefore && - isCommentToken(tokenBefore) && - tokenBefore.type === "Block" && - tokenBefore.value.charAt(0) === "*" && - astNode.loc.start.line - tokenBefore.loc.end.line <= 1 - ) { - return tokenBefore; - } - - return null; - }; - let parent = node.parent; - - switch (node.type) { - case "ClassDeclaration": - case "FunctionDeclaration": - return findJSDocComment(looksLikeExport(parent) ? parent : node); - - case "ClassExpression": - return findJSDocComment(parent.parent); - - case "ArrowFunctionExpression": - case "FunctionExpression": - if (parent.type !== "CallExpression" && parent.type !== "NewExpression") { - while ( - !this.getCommentsBefore(parent).length && - !/Function/u.test(parent.type) && - parent.type !== "MethodDefinition" && - parent.type !== "Property" - ) { - parent = parent.parent; - - if (!parent) { - break; - } - } - - if (parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program") { - return findJSDocComment(parent); - } - } - - return findJSDocComment(node); - - // falls through - default: - return null; - } - } - - /** - * Gets the deepest node containing a range index. - * @param {int} index Range index of the desired node. - * @returns {ASTNode} The node if found or null if not found. - * @public - */ - getNodeByRangeIndex(index) { - let result = null; - - Traverser.traverse(this.ast, { - visitorKeys: this.visitorKeys, - enter(node) { - if (node.range[0] <= index && index < node.range[1]) { - result = node; - } else { - this.skip(); - } - }, - leave(node) { - if (node === result) { - this.break(); - } - } - }); - - return result; - } - - /** - * Determines if two nodes or tokens have at least one whitespace character - * between them. Order does not matter. Returns false if the given nodes or - * tokens overlap. - * @param {ASTNode|Token} first The first node or token to check between. - * @param {ASTNode|Token} second The second node or token to check between. - * @returns {boolean} True if there is a whitespace character between - * any of the tokens found between the two given nodes or tokens. - * @public - */ - isSpaceBetween(first, second) { - return isSpaceBetween(this, first, second, false); - } - - /** - * Determines if two nodes or tokens have at least one whitespace character - * between them. Order does not matter. Returns false if the given nodes or - * tokens overlap. - * For backward compatibility, this method returns true if there are - * `JSXText` tokens that contain whitespaces between the two. - * @param {ASTNode|Token} first The first node or token to check between. - * @param {ASTNode|Token} second The second node or token to check between. - * @returns {boolean} True if there is a whitespace character between - * any of the tokens found between the two given nodes or tokens. - * @deprecated in favor of isSpaceBetween(). - * @public - */ - isSpaceBetweenTokens(first, second) { - return isSpaceBetween(this, first, second, true); - } - - /** - * Converts a source text index into a (line, column) pair. - * @param {number} index The index of a character in a file - * @throws {TypeError} If non-numeric index or index out of range. - * @returns {Object} A {line, column} location object with a 0-indexed column - * @public - */ - getLocFromIndex(index) { - if (typeof index !== "number") { - throw new TypeError("Expected `index` to be a number."); - } - - if (index < 0 || index > this.text.length) { - throw new RangeError(`Index out of range (requested index ${index}, but source text has length ${this.text.length}).`); - } - - /* - * For an argument of this.text.length, return the location one "spot" past the last character - * of the file. If the last character is a linebreak, the location will be column 0 of the next - * line; otherwise, the location will be in the next column on the same line. - * - * See getIndexFromLoc for the motivation for this special case. - */ - if (index === this.text.length) { - return { line: this.lines.length, column: this.lines.at(-1).length }; - } - - /* - * To figure out which line index is on, determine the last place at which index could - * be inserted into lineStartIndices to keep the list sorted. - */ - const lineNumber = index >= this.lineStartIndices.at(-1) - ? this.lineStartIndices.length - : this.lineStartIndices.findIndex(el => index < el); - - return { line: lineNumber, column: index - this.lineStartIndices[lineNumber - 1] }; - } - - /** - * Converts a (line, column) pair into a range index. - * @param {Object} loc A line/column location - * @param {number} loc.line The line number of the location (1-indexed) - * @param {number} loc.column The column number of the location (0-indexed) - * @throws {TypeError|RangeError} If `loc` is not an object with a numeric - * `line` and `column`, if the `line` is less than or equal to zero or - * the line or column is out of the expected range. - * @returns {number} The range index of the location in the file. - * @public - */ - getIndexFromLoc(loc) { - if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") { - throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties."); - } - - if (loc.line <= 0) { - throw new RangeError(`Line number out of range (line ${loc.line} requested). Line numbers should be 1-based.`); - } - - if (loc.line > this.lineStartIndices.length) { - throw new RangeError(`Line number out of range (line ${loc.line} requested, but only ${this.lineStartIndices.length} lines present).`); - } - - const lineStartIndex = this.lineStartIndices[loc.line - 1]; - const lineEndIndex = loc.line === this.lineStartIndices.length ? this.text.length : this.lineStartIndices[loc.line]; - const positionIndex = lineStartIndex + loc.column; - - /* - * By design, getIndexFromLoc({ line: lineNum, column: 0 }) should return the start index of - * the given line, provided that the line number is valid element of this.lines. Since the - * last element of this.lines is an empty string for files with trailing newlines, add a - * special case where getting the index for the first location after the end of the file - * will return the length of the file, rather than throwing an error. This allows rules to - * use getIndexFromLoc consistently without worrying about edge cases at the end of a file. - */ - if ( - loc.line === this.lineStartIndices.length && positionIndex > lineEndIndex || - loc.line < this.lineStartIndices.length && positionIndex >= lineEndIndex - ) { - throw new RangeError(`Column number out of range (column ${loc.column} requested, but the length of line ${loc.line} is ${lineEndIndex - lineStartIndex}).`); - } - - return positionIndex; - } - - /** - * Gets the scope for the given node - * @param {ASTNode} currentNode The node to get the scope of - * @returns {Scope} The scope information for this node - * @throws {TypeError} If the `currentNode` argument is missing. - */ - getScope(currentNode) { - - if (!currentNode) { - throw new TypeError("Missing required argument: node."); - } - - // check cache first - const cache = this[caches].get("scopes"); - const cachedScope = cache.get(currentNode); - - if (cachedScope) { - return cachedScope; - } - - // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. - const inner = currentNode.type !== "Program"; - - for (let node = currentNode; node; node = node.parent) { - const scope = this.scopeManager.acquire(node, inner); - - if (scope) { - if (scope.type === "function-expression-name") { - cache.set(currentNode, scope.childScopes[0]); - return scope.childScopes[0]; - } - - cache.set(currentNode, scope); - return scope; - } - } - - cache.set(currentNode, this.scopeManager.scopes[0]); - return this.scopeManager.scopes[0]; - } - - /** - * Get the variables that `node` defines. - * This is a convenience method that passes through - * to the same method on the `scopeManager`. - * @param {ASTNode} node The node for which the variables are obtained. - * @returns {Array} An array of variable nodes representing - * the variables that `node` defines. - */ - getDeclaredVariables(node) { - return this.scopeManager.getDeclaredVariables(node); - } - - /* eslint-disable class-methods-use-this -- node is owned by SourceCode */ - /** - * Gets all the ancestors of a given node - * @param {ASTNode} node The node - * @returns {Array} All the ancestor nodes in the AST, not including the provided node, starting - * from the root node at index 0 and going inwards to the parent node. - * @throws {TypeError} When `node` is missing. - */ - getAncestors(node) { - - if (!node) { - throw new TypeError("Missing required argument: node."); - } - - const ancestorsStartingAtParent = []; - - for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { - ancestorsStartingAtParent.push(ancestor); - } - - return ancestorsStartingAtParent.reverse(); - } - - /** - * Returns the location of the given node or token. - * @param {ASTNode|Token} nodeOrToken The node or token to get the location of. - * @returns {SourceLocation} The location of the node or token. - */ - getLoc(nodeOrToken) { - return nodeOrToken.loc; - } - - /** - * Returns the range of the given node or token. - * @param {ASTNode|Token} nodeOrToken The node or token to get the range of. - * @returns {[number, number]} The range of the node or token. - */ - getRange(nodeOrToken) { - return nodeOrToken.range; - } - - /* eslint-enable class-methods-use-this -- node is owned by SourceCode */ - - /** - * Marks a variable as used in the current scope - * @param {string} name The name of the variable to mark as used. - * @param {ASTNode} [refNode] The closest node to the variable reference. - * @returns {boolean} True if the variable was found and marked as used, false if not. - */ - markVariableAsUsed(name, refNode = this.ast) { - - const currentScope = this.getScope(refNode); - let initialScope = currentScope; - - /* - * When we are in an ESM or CommonJS module, we need to start searching - * from the top-level scope, not the global scope. For ESM the top-level - * scope is the module scope; for CommonJS the top-level scope is the - * outer function scope. - * - * Without this check, we might miss a variable declared with `var` at - * the top-level because it won't exist in the global scope. - */ - if ( - currentScope.type === "global" && - currentScope.childScopes.length > 0 && - - // top-level scopes refer to a `Program` node - currentScope.childScopes[0].block === this.ast - ) { - initialScope = currentScope.childScopes[0]; - } - - for (let scope = initialScope; scope; scope = scope.upper) { - const variable = scope.variables.find(scopeVar => scopeVar.name === name); - - if (variable) { - variable.eslintUsed = true; - return true; - } - } - - return false; - } - - - /** - * Returns an array of all inline configuration nodes found in the - * source code. - * @returns {Array} An array of all inline configuration nodes. - */ - getInlineConfigNodes() { - - // check the cache first - let configNodes = this[caches].get("configNodes"); - - if (configNodes) { - return configNodes; - } - - // calculate fresh config nodes - configNodes = this.ast.comments.filter(comment => { - - // shebang comments are never directives - if (comment.type === "Shebang") { - return false; - } - - const directive = commentParser.parseDirective(comment.value); - - if (!directive) { - return false; - } - - if (!directivesPattern.test(directive.label)) { - return false; - } - - // only certain comment types are supported as line comments - return comment.type !== "Line" || !!/^eslint-disable-(next-)?line$/u.test(directive.label); - }); - - this[caches].set("configNodes", configNodes); - - return configNodes; - } - - /** - * Returns an all directive nodes that enable or disable rules along with any problems - * encountered while parsing the directives. - * @returns {{problems:Array,directives:Array}} Information - * that ESLint needs to further process the directives. - */ - getDisableDirectives() { - - // check the cache first - const cachedDirectives = this[caches].get("disableDirectives"); - - if (cachedDirectives) { - return cachedDirectives; - } - - const problems = []; - const directives = []; - - this.getInlineConfigNodes().forEach(comment => { - - // Step 1: Parse the directive - const { - label, - value, - justification: justificationPart - } = commentParser.parseDirective(comment.value); - - // Step 2: Extract the directive value - const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label); - - if (comment.type === "Line" && !lineCommentSupported) { - return; - } - - // Step 3: Validate the directive does not span multiple lines - if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) { - const message = `${label} comment should not span multiple lines.`; - - problems.push({ - ruleId: null, - message, - loc: comment.loc - }); - return; - } - - // Step 4: Extract the directive value and create the Directive object - switch (label) { - case "eslint-disable": - case "eslint-enable": - case "eslint-disable-next-line": - case "eslint-disable-line": { - const directiveType = label.slice("eslint-".length); - - directives.push(new Directive({ - type: directiveType, - node: comment, - value, - justification: justificationPart - })); - } - - // no default - } - }); - - const result = { problems, directives }; - - this[caches].set("disableDirectives", result); - - return result; - } - - /** - * Applies language options sent in from the core. - * @param {Object} languageOptions The language options for this run. - * @returns {void} - */ - applyLanguageOptions(languageOptions) { - - /* - * Add configured globals and language globals - * - * Using Object.assign instead of object spread for performance reasons - * https://github.com/eslint/eslint/issues/16302 - */ - const configGlobals = Object.assign( - Object.create(null), // https://github.com/eslint/eslint/issues/18363 - getGlobalsForEcmaVersion(languageOptions.ecmaVersion), - languageOptions.sourceType === "commonjs" ? globals.commonjs : void 0, - languageOptions.globals - ); - const varsCache = this[caches].get("vars"); - - varsCache.set("configGlobals", configGlobals); - } - - /** - * Applies configuration found inside of the source code. This method is only - * called when ESLint is running with inline configuration allowed. - * @returns {{problems:Array,configs:{config:FlatConfigArray,loc:Location}}} Information - * that ESLint needs to further process the inline configuration. - */ - applyInlineConfig() { - - const problems = []; - const configs = []; - const exportedVariables = {}; - const inlineGlobals = Object.create(null); - - this.getInlineConfigNodes().forEach(comment => { - - const { label, value } = commentParser.parseDirective(comment.value); - - switch (label) { - case "exported": - Object.assign(exportedVariables, commentParser.parseListConfig(value)); - break; - - case "globals": - case "global": - for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) { - let normalizedValue; - - try { - normalizedValue = normalizeConfigGlobal(idSetting); - } catch (err) { - problems.push({ - ruleId: null, - loc: comment.loc, - message: err.message - }); - continue; - } - - if (inlineGlobals[id]) { - inlineGlobals[id].comments.push(comment); - inlineGlobals[id].value = normalizedValue; - } else { - inlineGlobals[id] = { - comments: [comment], - value: normalizedValue - }; - } - } - break; - - case "eslint": { - const parseResult = commentParser.parseJSONLikeConfig(value); - - if (parseResult.ok) { - configs.push({ - config: { - rules: parseResult.config - }, - loc: comment.loc - }); - } else { - problems.push({ - ruleId: null, - loc: comment.loc, - message: parseResult.error.message - }); - } - - break; - } - - // no default - } - }); - - // save all the new variables for later - const varsCache = this[caches].get("vars"); - - varsCache.set("inlineGlobals", inlineGlobals); - varsCache.set("exportedVariables", exportedVariables); - - return { - configs, - problems - }; - } - - /** - * Called by ESLint core to indicate that it has finished providing - * information. We now add in all the missing variables and ensure that - * state-changing methods cannot be called by rules. - * @returns {void} - */ - finalize() { - - const varsCache = this[caches].get("vars"); - const configGlobals = varsCache.get("configGlobals"); - const inlineGlobals = varsCache.get("inlineGlobals"); - const exportedVariables = varsCache.get("exportedVariables"); - const globalScope = this.scopeManager.scopes[0]; - - addDeclaredGlobals(globalScope, configGlobals, inlineGlobals); - - if (exportedVariables) { - markExportedVariables(globalScope, exportedVariables); - } - - } - - /** - * Traverse the source code and return the steps that were taken. - * @returns {Array} The steps that were taken while traversing the source code. - */ - traverse() { - - // Because the AST doesn't mutate, we can cache the steps - if (this.#steps) { - return this.#steps; - } - - const steps = this.#steps = []; - - /* - * This logic works for any AST, not just ESTree. Because ESLint has allowed - * custom parsers to return any AST, we need to ensure that the traversal - * logic works for any AST. - */ - const emitter = createEmitter(); - let analyzer = { - enterNode(node) { - steps.push(new VisitNodeStep({ - target: node, - phase: 1, - args: [node, node.parent] - })); - }, - leaveNode(node) { - steps.push(new VisitNodeStep({ - target: node, - phase: 2, - args: [node, node.parent] - })); - }, - emitter - }; - - /* - * We do code path analysis for ESTree only. Code path analysis is not - * necessary for other ASTs, and it's also not possible to do for other - * ASTs because the necessary information is not available. - * - * Generally speaking, we can tell that the AST is an ESTree if it has a - * Program node at the top level. This is not a perfect heuristic, but it - * is good enough for now. - */ - if (this.isESTree) { - analyzer = new CodePathAnalyzer(analyzer); - - CODE_PATH_EVENTS.forEach(eventName => { - emitter.on(eventName, (...args) => { - steps.push(new CallMethodStep({ - target: eventName, - args - })); - }); - }); - } - - /* - * The actual AST traversal is done by the `Traverser` class. This class - * is responsible for walking the AST and calling the appropriate methods - * on the `analyzer` object, which is appropriate for the given AST. - */ - Traverser.traverse(this.ast, { - enter(node, parent) { - - // save the parent node on a property for backwards compatibility - node.parent = parent; - - analyzer.enterNode(node); - }, - leave(node) { - analyzer.leaveNode(node); - }, - visitorKeys: this.visitorKeys - }); - - return steps; - } + /** + * The cache of steps that were taken while traversing the source code. + * @type {Array} + */ + #steps; + + /** + * Creates a new instance. + * @param {string|Object} textOrConfig The source code text or config object. + * @param {string} textOrConfig.text The source code text. + * @param {ASTNode} textOrConfig.ast The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. + * @param {boolean} textOrConfig.hasBOM Indicates if the text has a Unicode BOM. + * @param {Object|null} textOrConfig.parserServices The parser services. + * @param {ScopeManager|null} textOrConfig.scopeManager The scope of this source code. + * @param {Object|null} textOrConfig.visitorKeys The visitor keys to traverse AST. + * @param {ASTNode} [astIfNoConfig] The Program node of the AST representing the code. This AST should be created from the text that BOM was stripped. + */ + constructor(textOrConfig, astIfNoConfig) { + let text, hasBOM, ast, parserServices, scopeManager, visitorKeys; + + // Process overloading of arguments + if (typeof textOrConfig === "string") { + text = textOrConfig; + ast = astIfNoConfig; + hasBOM = false; + } else if (typeof textOrConfig === "object" && textOrConfig !== null) { + text = textOrConfig.text; + ast = textOrConfig.ast; + hasBOM = textOrConfig.hasBOM; + parserServices = textOrConfig.parserServices; + scopeManager = textOrConfig.scopeManager; + visitorKeys = textOrConfig.visitorKeys; + } + + validate(ast); + super(ast.tokens, ast.comments); + + /** + * General purpose caching for the class. + */ + this[caches] = new Map([ + ["scopes", new WeakMap()], + ["vars", new Map()], + ["configNodes", void 0], + ]); + + /** + * Indicates if the AST is ESTree compatible. + * @type {boolean} + */ + this.isESTree = ast.type === "Program"; + + /* + * Backwards compatibility for BOM handling. + * + * The `hasBOM` property has been available on the `SourceCode` object + * for a long time and is used to indicate if the source contains a BOM. + * The linter strips the BOM and just passes the `hasBOM` property to the + * `SourceCode` constructor to make it easier for languages to not deal with + * the BOM. + * + * However, the text passed in to the `SourceCode` constructor might still + * have a BOM if the constructor is called outside of the linter, so we still + * need to check for the BOM in the text. + */ + const textHasBOM = text.charCodeAt(0) === 0xfeff; + + /** + * The flag to indicate that the source code has Unicode BOM. + * @type {boolean} + */ + this.hasBOM = textHasBOM || !!hasBOM; + + /** + * The original text source code. + * BOM was stripped from this text. + * @type {string} + */ + this.text = textHasBOM ? text.slice(1) : text; + + /** + * The parsed AST for the source code. + * @type {ASTNode} + */ + this.ast = ast; + + /** + * The parser services of this source code. + * @type {Object} + */ + this.parserServices = parserServices || {}; + + /** + * The scope of this source code. + * @type {ScopeManager|null} + */ + this.scopeManager = scopeManager || null; + + /** + * The visitor keys to traverse AST. + * @type {Object} + */ + this.visitorKeys = visitorKeys || Traverser.DEFAULT_VISITOR_KEYS; + + // Check the source text for the presence of a shebang since it is parsed as a standard line comment. + const shebangMatched = this.text.match(astUtils.shebangPattern); + const hasShebang = + shebangMatched && + ast.comments.length && + ast.comments[0].value === shebangMatched[1]; + + if (hasShebang) { + ast.comments[0].type = "Shebang"; + } + + this.tokensAndComments = sortedMerge(ast.tokens, ast.comments); + + /** + * The source code split into lines according to ECMA-262 specification. + * This is done to avoid each rule needing to do so separately. + * @type {string[]} + */ + this.lines = []; + this.lineStartIndices = [0]; + + const lineEndingPattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + /* + * Previously, this was implemented using a regex that + * matched a sequence of non-linebreak characters followed by a + * linebreak, then adding the lengths of the matches. However, + * this caused a catastrophic backtracking issue when the end + * of a file contained a large number of non-newline characters. + * To avoid this, the current implementation just matches newlines + * and uses match.index to get the correct line start indices. + */ + while ((match = lineEndingPattern.exec(this.text))) { + this.lines.push( + this.text.slice(this.lineStartIndices.at(-1), match.index), + ); + this.lineStartIndices.push(match.index + match[0].length); + } + this.lines.push(this.text.slice(this.lineStartIndices.at(-1))); + + // don't allow further modification of this object + Object.freeze(this); + Object.freeze(this.lines); + } + + /** + * Split the source code into multiple lines based on the line delimiters. + * @param {string} text Source code as a string. + * @returns {string[]} Array of source code lines. + * @public + */ + static splitLines(text) { + return text.split(astUtils.createGlobalLinebreakMatcher()); + } + + /** + * Gets the source code for the given node. + * @param {ASTNode} [node] The AST node to get the text for. + * @param {int} [beforeCount] The number of characters before the node to retrieve. + * @param {int} [afterCount] The number of characters after the node to retrieve. + * @returns {string} The text representing the AST node. + * @public + */ + getText(node, beforeCount, afterCount) { + if (node) { + return this.text.slice( + Math.max(node.range[0] - (beforeCount || 0), 0), + node.range[1] + (afterCount || 0), + ); + } + return this.text; + } + + /** + * Gets the entire source text split into an array of lines. + * @returns {Array} The source text as an array of lines. + * @public + */ + getLines() { + return this.lines; + } + + /** + * Retrieves an array containing all comments in the source code. + * @returns {ASTNode[]} An array of comment nodes. + * @public + */ + getAllComments() { + return this.ast.comments; + } + + /** + * Retrieves the JSDoc comment for a given node. + * @param {ASTNode} node The AST node to get the comment for. + * @returns {Token|null} The Block comment token containing the JSDoc comment + * for the given node or null if not found. + * @public + * @deprecated + */ + getJSDocComment(node) { + /** + * Checks for the presence of a JSDoc comment for the given node and returns it. + * @param {ASTNode} astNode The AST node to get the comment for. + * @returns {Token|null} The Block comment token containing the JSDoc comment + * for the given node or null if not found. + * @private + */ + const findJSDocComment = astNode => { + const tokenBefore = this.getTokenBefore(astNode, { + includeComments: true, + }); + + if ( + tokenBefore && + isCommentToken(tokenBefore) && + tokenBefore.type === "Block" && + tokenBefore.value.charAt(0) === "*" && + astNode.loc.start.line - tokenBefore.loc.end.line <= 1 + ) { + return tokenBefore; + } + + return null; + }; + let parent = node.parent; + + switch (node.type) { + case "ClassDeclaration": + case "FunctionDeclaration": + return findJSDocComment( + looksLikeExport(parent) ? parent : node, + ); + + case "ClassExpression": + return findJSDocComment(parent.parent); + + case "ArrowFunctionExpression": + case "FunctionExpression": + if ( + parent.type !== "CallExpression" && + parent.type !== "NewExpression" + ) { + while ( + !this.getCommentsBefore(parent).length && + !/Function/u.test(parent.type) && + parent.type !== "MethodDefinition" && + parent.type !== "Property" + ) { + parent = parent.parent; + + if (!parent) { + break; + } + } + + if ( + parent && + parent.type !== "FunctionDeclaration" && + parent.type !== "Program" + ) { + return findJSDocComment(parent); + } + } + + return findJSDocComment(node); + + // falls through + default: + return null; + } + } + + /** + * Gets the deepest node containing a range index. + * @param {int} index Range index of the desired node. + * @returns {ASTNode} The node if found or null if not found. + * @public + */ + getNodeByRangeIndex(index) { + let result = null; + + Traverser.traverse(this.ast, { + visitorKeys: this.visitorKeys, + enter(node) { + if (node.range[0] <= index && index < node.range[1]) { + result = node; + } else { + this.skip(); + } + }, + leave(node) { + if (node === result) { + this.break(); + } + }, + }); + + return result; + } + + /** + * Determines if two nodes or tokens have at least one whitespace character + * between them. Order does not matter. Returns false if the given nodes or + * tokens overlap. + * @param {ASTNode|Token} first The first node or token to check between. + * @param {ASTNode|Token} second The second node or token to check between. + * @returns {boolean} True if there is a whitespace character between + * any of the tokens found between the two given nodes or tokens. + * @public + */ + isSpaceBetween(first, second) { + return isSpaceBetween(this, first, second, false); + } + + /** + * Determines if two nodes or tokens have at least one whitespace character + * between them. Order does not matter. Returns false if the given nodes or + * tokens overlap. + * For backward compatibility, this method returns true if there are + * `JSXText` tokens that contain whitespaces between the two. + * @param {ASTNode|Token} first The first node or token to check between. + * @param {ASTNode|Token} second The second node or token to check between. + * @returns {boolean} True if there is a whitespace character between + * any of the tokens found between the two given nodes or tokens. + * @deprecated in favor of isSpaceBetween(). + * @public + */ + isSpaceBetweenTokens(first, second) { + return isSpaceBetween(this, first, second, true); + } + + /** + * Converts a source text index into a (line, column) pair. + * @param {number} index The index of a character in a file + * @throws {TypeError} If non-numeric index or index out of range. + * @returns {Object} A {line, column} location object with a 0-indexed column + * @public + */ + getLocFromIndex(index) { + if (typeof index !== "number") { + throw new TypeError("Expected `index` to be a number."); + } + + if (index < 0 || index > this.text.length) { + throw new RangeError( + `Index out of range (requested index ${index}, but source text has length ${this.text.length}).`, + ); + } + + /* + * For an argument of this.text.length, return the location one "spot" past the last character + * of the file. If the last character is a linebreak, the location will be column 0 of the next + * line; otherwise, the location will be in the next column on the same line. + * + * See getIndexFromLoc for the motivation for this special case. + */ + if (index === this.text.length) { + return { + line: this.lines.length, + column: this.lines.at(-1).length, + }; + } + + /* + * To figure out which line index is on, determine the last place at which index could + * be inserted into lineStartIndices to keep the list sorted. + */ + const lineNumber = + index >= this.lineStartIndices.at(-1) + ? this.lineStartIndices.length + : this.lineStartIndices.findIndex(el => index < el); + + return { + line: lineNumber, + column: index - this.lineStartIndices[lineNumber - 1], + }; + } + + /** + * Converts a (line, column) pair into a range index. + * @param {Object} loc A line/column location + * @param {number} loc.line The line number of the location (1-indexed) + * @param {number} loc.column The column number of the location (0-indexed) + * @throws {TypeError|RangeError} If `loc` is not an object with a numeric + * `line` and `column`, if the `line` is less than or equal to zero or + * the line or column is out of the expected range. + * @returns {number} The range index of the location in the file. + * @public + */ + getIndexFromLoc(loc) { + if ( + typeof loc !== "object" || + typeof loc.line !== "number" || + typeof loc.column !== "number" + ) { + throw new TypeError( + "Expected `loc` to be an object with numeric `line` and `column` properties.", + ); + } + + if (loc.line <= 0) { + throw new RangeError( + `Line number out of range (line ${loc.line} requested). Line numbers should be 1-based.`, + ); + } + + if (loc.line > this.lineStartIndices.length) { + throw new RangeError( + `Line number out of range (line ${loc.line} requested, but only ${this.lineStartIndices.length} lines present).`, + ); + } + + const lineStartIndex = this.lineStartIndices[loc.line - 1]; + const lineEndIndex = + loc.line === this.lineStartIndices.length + ? this.text.length + : this.lineStartIndices[loc.line]; + const positionIndex = lineStartIndex + loc.column; + + /* + * By design, getIndexFromLoc({ line: lineNum, column: 0 }) should return the start index of + * the given line, provided that the line number is valid element of this.lines. Since the + * last element of this.lines is an empty string for files with trailing newlines, add a + * special case where getting the index for the first location after the end of the file + * will return the length of the file, rather than throwing an error. This allows rules to + * use getIndexFromLoc consistently without worrying about edge cases at the end of a file. + */ + if ( + (loc.line === this.lineStartIndices.length && + positionIndex > lineEndIndex) || + (loc.line < this.lineStartIndices.length && + positionIndex >= lineEndIndex) + ) { + throw new RangeError( + `Column number out of range (column ${loc.column} requested, but the length of line ${loc.line} is ${lineEndIndex - lineStartIndex}).`, + ); + } + + return positionIndex; + } + + /** + * Gets the scope for the given node + * @param {ASTNode} currentNode The node to get the scope of + * @returns {Scope} The scope information for this node + * @throws {TypeError} If the `currentNode` argument is missing. + */ + getScope(currentNode) { + if (!currentNode) { + throw new TypeError("Missing required argument: node."); + } + + // check cache first + const cache = this[caches].get("scopes"); + const cachedScope = cache.get(currentNode); + + if (cachedScope) { + return cachedScope; + } + + // On Program node, get the outermost scope to avoid return Node.js special function scope or ES modules scope. + const inner = currentNode.type !== "Program"; + + for (let node = currentNode; node; node = node.parent) { + const scope = this.scopeManager.acquire(node, inner); + + if (scope) { + if (scope.type === "function-expression-name") { + cache.set(currentNode, scope.childScopes[0]); + return scope.childScopes[0]; + } + + cache.set(currentNode, scope); + return scope; + } + } + + cache.set(currentNode, this.scopeManager.scopes[0]); + return this.scopeManager.scopes[0]; + } + + /** + * Get the variables that `node` defines. + * This is a convenience method that passes through + * to the same method on the `scopeManager`. + * @param {ASTNode} node The node for which the variables are obtained. + * @returns {Array} An array of variable nodes representing + * the variables that `node` defines. + */ + getDeclaredVariables(node) { + return this.scopeManager.getDeclaredVariables(node); + } + + /* eslint-disable class-methods-use-this -- node is owned by SourceCode */ + /** + * Gets all the ancestors of a given node + * @param {ASTNode} node The node + * @returns {Array} All the ancestor nodes in the AST, not including the provided node, starting + * from the root node at index 0 and going inwards to the parent node. + * @throws {TypeError} When `node` is missing. + */ + getAncestors(node) { + if (!node) { + throw new TypeError("Missing required argument: node."); + } + + const ancestorsStartingAtParent = []; + + for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) { + ancestorsStartingAtParent.push(ancestor); + } + + return ancestorsStartingAtParent.reverse(); + } + + /** + * Returns the location of the given node or token. + * @param {ASTNode|Token} nodeOrToken The node or token to get the location of. + * @returns {SourceLocation} The location of the node or token. + */ + getLoc(nodeOrToken) { + return nodeOrToken.loc; + } + + /** + * Returns the range of the given node or token. + * @param {ASTNode|Token} nodeOrToken The node or token to get the range of. + * @returns {[number, number]} The range of the node or token. + */ + getRange(nodeOrToken) { + return nodeOrToken.range; + } + + /* eslint-enable class-methods-use-this -- node is owned by SourceCode */ + + /** + * Marks a variable as used in the current scope + * @param {string} name The name of the variable to mark as used. + * @param {ASTNode} [refNode] The closest node to the variable reference. + * @returns {boolean} True if the variable was found and marked as used, false if not. + */ + markVariableAsUsed(name, refNode = this.ast) { + const currentScope = this.getScope(refNode); + let initialScope = currentScope; + + /* + * When we are in an ESM or CommonJS module, we need to start searching + * from the top-level scope, not the global scope. For ESM the top-level + * scope is the module scope; for CommonJS the top-level scope is the + * outer function scope. + * + * Without this check, we might miss a variable declared with `var` at + * the top-level because it won't exist in the global scope. + */ + if ( + currentScope.type === "global" && + currentScope.childScopes.length > 0 && + // top-level scopes refer to a `Program` node + currentScope.childScopes[0].block === this.ast + ) { + initialScope = currentScope.childScopes[0]; + } + + for (let scope = initialScope; scope; scope = scope.upper) { + const variable = scope.variables.find( + scopeVar => scopeVar.name === name, + ); + + if (variable) { + variable.eslintUsed = true; + return true; + } + } + + return false; + } + + /** + * Returns an array of all inline configuration nodes found in the + * source code. + * @returns {Array} An array of all inline configuration nodes. + */ + getInlineConfigNodes() { + // check the cache first + let configNodes = this[caches].get("configNodes"); + + if (configNodes) { + return configNodes; + } + + // calculate fresh config nodes + configNodes = this.ast.comments.filter(comment => { + // shebang comments are never directives + if (comment.type === "Shebang") { + return false; + } + + const directive = commentParser.parseDirective(comment.value); + + if (!directive) { + return false; + } + + if (!directivesPattern.test(directive.label)) { + return false; + } + + // only certain comment types are supported as line comments + return ( + comment.type !== "Line" || + !!/^eslint-disable-(next-)?line$/u.test(directive.label) + ); + }); + + this[caches].set("configNodes", configNodes); + + return configNodes; + } + + /** + * Returns an all directive nodes that enable or disable rules along with any problems + * encountered while parsing the directives. + * @returns {{problems:Array,directives:Array}} Information + * that ESLint needs to further process the directives. + */ + getDisableDirectives() { + // check the cache first + const cachedDirectives = this[caches].get("disableDirectives"); + + if (cachedDirectives) { + return cachedDirectives; + } + + const problems = []; + const directives = []; + + this.getInlineConfigNodes().forEach(comment => { + // Step 1: Parse the directive + const { + label, + value, + justification: justificationPart, + } = commentParser.parseDirective(comment.value); + + // Step 2: Extract the directive value + const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test( + label, + ); + + if (comment.type === "Line" && !lineCommentSupported) { + return; + } + + // Step 3: Validate the directive does not span multiple lines + if ( + label === "eslint-disable-line" && + comment.loc.start.line !== comment.loc.end.line + ) { + const message = `${label} comment should not span multiple lines.`; + + problems.push({ + ruleId: null, + message, + loc: comment.loc, + }); + return; + } + + // Step 4: Extract the directive value and create the Directive object + switch (label) { + case "eslint-disable": + case "eslint-enable": + case "eslint-disable-next-line": + case "eslint-disable-line": { + const directiveType = label.slice("eslint-".length); + + directives.push( + new Directive({ + type: directiveType, + node: comment, + value, + justification: justificationPart, + }), + ); + } + + // no default + } + }); + + const result = { problems, directives }; + + this[caches].set("disableDirectives", result); + + return result; + } + + /** + * Applies language options sent in from the core. + * @param {Object} languageOptions The language options for this run. + * @returns {void} + */ + applyLanguageOptions(languageOptions) { + /* + * Add configured globals and language globals + * + * Using Object.assign instead of object spread for performance reasons + * https://github.com/eslint/eslint/issues/16302 + */ + const configGlobals = Object.assign( + Object.create(null), // https://github.com/eslint/eslint/issues/18363 + getGlobalsForEcmaVersion(languageOptions.ecmaVersion), + languageOptions.sourceType === "commonjs" + ? globals.commonjs + : void 0, + languageOptions.globals, + ); + const varsCache = this[caches].get("vars"); + + varsCache.set("configGlobals", configGlobals); + } + + /** + * Applies configuration found inside of the source code. This method is only + * called when ESLint is running with inline configuration allowed. + * @returns {{problems:Array,configs:{config:FlatConfigArray,loc:Location}}} Information + * that ESLint needs to further process the inline configuration. + */ + applyInlineConfig() { + const problems = []; + const configs = []; + const exportedVariables = {}; + const inlineGlobals = Object.create(null); + + this.getInlineConfigNodes().forEach(comment => { + const { label, value } = commentParser.parseDirective( + comment.value, + ); + + switch (label) { + case "exported": + Object.assign( + exportedVariables, + commentParser.parseListConfig(value), + ); + break; + + case "globals": + case "global": + for (const [id, idSetting] of Object.entries( + commentParser.parseStringConfig(value), + )) { + let normalizedValue; + + try { + normalizedValue = normalizeConfigGlobal(idSetting); + } catch (err) { + problems.push({ + ruleId: null, + loc: comment.loc, + message: err.message, + }); + continue; + } + + if (inlineGlobals[id]) { + inlineGlobals[id].comments.push(comment); + inlineGlobals[id].value = normalizedValue; + } else { + inlineGlobals[id] = { + comments: [comment], + value: normalizedValue, + }; + } + } + break; + + case "eslint": { + const parseResult = + commentParser.parseJSONLikeConfig(value); + + if (parseResult.ok) { + configs.push({ + config: { + rules: parseResult.config, + }, + loc: comment.loc, + }); + } else { + problems.push({ + ruleId: null, + loc: comment.loc, + message: parseResult.error.message, + }); + } + + break; + } + + // no default + } + }); + + // save all the new variables for later + const varsCache = this[caches].get("vars"); + + varsCache.set("inlineGlobals", inlineGlobals); + varsCache.set("exportedVariables", exportedVariables); + + return { + configs, + problems, + }; + } + + /** + * Called by ESLint core to indicate that it has finished providing + * information. We now add in all the missing variables and ensure that + * state-changing methods cannot be called by rules. + * @returns {void} + */ + finalize() { + const varsCache = this[caches].get("vars"); + const configGlobals = varsCache.get("configGlobals"); + const inlineGlobals = varsCache.get("inlineGlobals"); + const exportedVariables = varsCache.get("exportedVariables"); + const globalScope = this.scopeManager.scopes[0]; + + addDeclaredGlobals(globalScope, configGlobals, inlineGlobals); + + if (exportedVariables) { + markExportedVariables(globalScope, exportedVariables); + } + } + + /** + * Traverse the source code and return the steps that were taken. + * @returns {Array} The steps that were taken while traversing the source code. + */ + traverse() { + // Because the AST doesn't mutate, we can cache the steps + if (this.#steps) { + return this.#steps; + } + + const steps = (this.#steps = []); + + /* + * This logic works for any AST, not just ESTree. Because ESLint has allowed + * custom parsers to return any AST, we need to ensure that the traversal + * logic works for any AST. + */ + const emitter = createEmitter(); + let analyzer = { + enterNode(node) { + steps.push( + new VisitNodeStep({ + target: node, + phase: 1, + args: [node, node.parent], + }), + ); + }, + leaveNode(node) { + steps.push( + new VisitNodeStep({ + target: node, + phase: 2, + args: [node, node.parent], + }), + ); + }, + emitter, + }; + + /* + * We do code path analysis for ESTree only. Code path analysis is not + * necessary for other ASTs, and it's also not possible to do for other + * ASTs because the necessary information is not available. + * + * Generally speaking, we can tell that the AST is an ESTree if it has a + * Program node at the top level. This is not a perfect heuristic, but it + * is good enough for now. + */ + if (this.isESTree) { + analyzer = new CodePathAnalyzer(analyzer); + + CODE_PATH_EVENTS.forEach(eventName => { + emitter.on(eventName, (...args) => { + steps.push( + new CallMethodStep({ + target: eventName, + args, + }), + ); + }); + }); + } + + /* + * The actual AST traversal is done by the `Traverser` class. This class + * is responsible for walking the AST and calling the appropriate methods + * on the `analyzer` object, which is appropriate for the given AST. + */ + Traverser.traverse(this.ast, { + enter(node, parent) { + // save the parent node on a property for backwards compatibility + node.parent = parent; + + analyzer.enterNode(node); + }, + leave(node) { + analyzer.leaveNode(node); + }, + visitorKeys: this.visitorKeys, + }); + + return steps; + } } module.exports = SourceCode; diff --git a/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js b/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js index 7255a62260b2..56f920a47e89 100644 --- a/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js +++ b/lib/languages/js/source-code/token-store/backward-token-comment-cursor.js @@ -19,39 +19,43 @@ const utils = require("./utils"); * The cursor which iterates tokens and comments in reverse. */ module.exports = class BackwardTokenCommentCursor extends Cursor { - - /** - * Initializes this cursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - */ - constructor(tokens, comments, indexMap, startLoc, endLoc) { - super(); - this.tokens = tokens; - this.comments = comments; - this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc); - this.commentIndex = utils.search(comments, endLoc) - 1; - this.border = startLoc; - } - - /** @inheritdoc */ - moveNext() { - const token = (this.tokenIndex >= 0) ? this.tokens[this.tokenIndex] : null; - const comment = (this.commentIndex >= 0) ? this.comments[this.commentIndex] : null; - - if (token && (!comment || token.range[1] > comment.range[1])) { - this.current = token; - this.tokenIndex -= 1; - } else if (comment) { - this.current = comment; - this.commentIndex -= 1; - } else { - this.current = null; - } - - return Boolean(this.current) && (this.border === -1 || this.current.range[0] >= this.border); - } + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.comments = comments; + this.tokenIndex = utils.getLastIndex(tokens, indexMap, endLoc); + this.commentIndex = utils.search(comments, endLoc) - 1; + this.border = startLoc; + } + + /** @inheritdoc */ + moveNext() { + const token = + this.tokenIndex >= 0 ? this.tokens[this.tokenIndex] : null; + const comment = + this.commentIndex >= 0 ? this.comments[this.commentIndex] : null; + + if (token && (!comment || token.range[1] > comment.range[1])) { + this.current = token; + this.tokenIndex -= 1; + } else if (comment) { + this.current = comment; + this.commentIndex -= 1; + } else { + this.current = null; + } + + return ( + Boolean(this.current) && + (this.border === -1 || this.current.range[0] >= this.border) + ); + } }; diff --git a/lib/languages/js/source-code/token-store/backward-token-cursor.js b/lib/languages/js/source-code/token-store/backward-token-cursor.js index d3469c99b145..b6b2e120d1fd 100644 --- a/lib/languages/js/source-code/token-store/backward-token-cursor.js +++ b/lib/languages/js/source-code/token-store/backward-token-cursor.js @@ -19,40 +19,39 @@ const { getLastIndex, getFirstIndex } = require("./utils"); * The cursor which iterates tokens only in reverse. */ module.exports = class BackwardTokenCursor extends Cursor { - - /** - * Initializes this cursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - */ - constructor(tokens, comments, indexMap, startLoc, endLoc) { - super(); - this.tokens = tokens; - this.index = getLastIndex(tokens, indexMap, endLoc); - this.indexEnd = getFirstIndex(tokens, indexMap, startLoc); - } - - /** @inheritdoc */ - moveNext() { - if (this.index >= this.indexEnd) { - this.current = this.tokens[this.index]; - this.index -= 1; - return true; - } - return false; - } - - /* - * - * Shorthand for performance. - * - */ - - /** @inheritdoc */ - getOneToken() { - return (this.index >= this.indexEnd) ? this.tokens[this.index] : null; - } + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.index = getLastIndex(tokens, indexMap, endLoc); + this.indexEnd = getFirstIndex(tokens, indexMap, startLoc); + } + + /** @inheritdoc */ + moveNext() { + if (this.index >= this.indexEnd) { + this.current = this.tokens[this.index]; + this.index -= 1; + return true; + } + return false; + } + + /* + * + * Shorthand for performance. + * + */ + + /** @inheritdoc */ + getOneToken() { + return this.index >= this.indexEnd ? this.tokens[this.index] : null; + } }; diff --git a/lib/languages/js/source-code/token-store/cursor.js b/lib/languages/js/source-code/token-store/cursor.js index 0b726006e8e3..e640d23fb90a 100644 --- a/lib/languages/js/source-code/token-store/cursor.js +++ b/lib/languages/js/source-code/token-store/cursor.js @@ -32,45 +32,45 @@ * */ module.exports = class Cursor { + /** + * Initializes this cursor. + */ + constructor() { + this.current = null; + } - /** - * Initializes this cursor. - */ - constructor() { - this.current = null; - } + /** + * Gets the first token. + * This consumes this cursor. + * @returns {Token|Comment} The first token or null. + */ + getOneToken() { + return this.moveNext() ? this.current : null; + } - /** - * Gets the first token. - * This consumes this cursor. - * @returns {Token|Comment} The first token or null. - */ - getOneToken() { - return this.moveNext() ? this.current : null; - } + /** + * Gets the first tokens. + * This consumes this cursor. + * @returns {(Token|Comment)[]} All tokens. + */ + getAllTokens() { + const tokens = []; - /** - * Gets the first tokens. - * This consumes this cursor. - * @returns {(Token|Comment)[]} All tokens. - */ - getAllTokens() { - const tokens = []; + while (this.moveNext()) { + tokens.push(this.current); + } - while (this.moveNext()) { - tokens.push(this.current); - } + return tokens; + } - return tokens; - } - - /** - * Moves this cursor to the next token. - * @returns {boolean} `true` if the next token exists. - * @abstract - */ - /* c8 ignore next */ - moveNext() { // eslint-disable-line class-methods-use-this -- Unused - throw new Error("Not implemented."); - } + /** + * Moves this cursor to the next token. + * @returns {boolean} `true` if the next token exists. + * @abstract + */ + /* c8 ignore next */ + // eslint-disable-next-line class-methods-use-this -- Unused + moveNext() { + throw new Error("Not implemented."); + } }; diff --git a/lib/languages/js/source-code/token-store/cursors.js b/lib/languages/js/source-code/token-store/cursors.js index f2676f13da62..1e9c084cf654 100644 --- a/lib/languages/js/source-code/token-store/cursors.js +++ b/lib/languages/js/source-code/token-store/cursors.js @@ -25,61 +25,86 @@ const SkipCursor = require("./skip-cursor"); * @private */ class CursorFactory { + /** + * Initializes this cursor. + * @param {Function} TokenCursor The class of the cursor which iterates tokens only. + * @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments. + */ + constructor(TokenCursor, TokenCommentCursor) { + this.TokenCursor = TokenCursor; + this.TokenCommentCursor = TokenCommentCursor; + } - /** - * Initializes this cursor. - * @param {Function} TokenCursor The class of the cursor which iterates tokens only. - * @param {Function} TokenCommentCursor The class of the cursor which iterates the mix of tokens and comments. - */ - constructor(TokenCursor, TokenCommentCursor) { - this.TokenCursor = TokenCursor; - this.TokenCommentCursor = TokenCommentCursor; - } + /** + * Creates a base cursor instance that can be decorated by createCursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {boolean} includeComments The flag to iterate comments as well. + * @returns {Cursor} The created base cursor. + */ + createBaseCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + includeComments, + ) { + const Cursor = includeComments + ? this.TokenCommentCursor + : this.TokenCursor; - /** - * Creates a base cursor instance that can be decorated by createCursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - * @param {boolean} includeComments The flag to iterate comments as well. - * @returns {Cursor} The created base cursor. - */ - createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments) { - const Cursor = includeComments ? this.TokenCommentCursor : this.TokenCursor; + return new Cursor(tokens, comments, indexMap, startLoc, endLoc); + } - return new Cursor(tokens, comments, indexMap, startLoc, endLoc); - } + /** + * Creates a cursor that iterates tokens with normalized options. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {boolean} includeComments The flag to iterate comments as well. + * @param {Function|null} filter The predicate function to choose tokens. + * @param {number} skip The count of tokens the cursor skips. + * @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility. + * @returns {Cursor} The created cursor. + */ + createCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + includeComments, + filter, + skip, + count, + ) { + let cursor = this.createBaseCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + includeComments, + ); - /** - * Creates a cursor that iterates tokens with normalized options. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - * @param {boolean} includeComments The flag to iterate comments as well. - * @param {Function|null} filter The predicate function to choose tokens. - * @param {number} skip The count of tokens the cursor skips. - * @param {number} count The maximum count of tokens the cursor iterates. Zero is no iteration for backward compatibility. - * @returns {Cursor} The created cursor. - */ - createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, count) { - let cursor = this.createBaseCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments); + if (filter) { + cursor = new FilterCursor(cursor, filter); + } + if (skip >= 1) { + cursor = new SkipCursor(cursor, skip); + } + if (count >= 0) { + cursor = new LimitCursor(cursor, count); + } - if (filter) { - cursor = new FilterCursor(cursor, filter); - } - if (skip >= 1) { - cursor = new SkipCursor(cursor, skip); - } - if (count >= 0) { - cursor = new LimitCursor(cursor, count); - } - - return cursor; - } + return cursor; + } } //------------------------------------------------------------------------------ @@ -87,6 +112,9 @@ class CursorFactory { //------------------------------------------------------------------------------ module.exports = { - forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor), - backward: new CursorFactory(BackwardTokenCursor, BackwardTokenCommentCursor) + forward: new CursorFactory(ForwardTokenCursor, ForwardTokenCommentCursor), + backward: new CursorFactory( + BackwardTokenCursor, + BackwardTokenCommentCursor, + ), }; diff --git a/lib/languages/js/source-code/token-store/decorative-cursor.js b/lib/languages/js/source-code/token-store/decorative-cursor.js index 3ee7b0b39755..3a1d21ecc034 100644 --- a/lib/languages/js/source-code/token-store/decorative-cursor.js +++ b/lib/languages/js/source-code/token-store/decorative-cursor.js @@ -18,22 +18,21 @@ const Cursor = require("./cursor"); * The abstract class about cursors which manipulate another cursor. */ module.exports = class DecorativeCursor extends Cursor { - - /** - * Initializes this cursor. - * @param {Cursor} cursor The cursor to be decorated. - */ - constructor(cursor) { - super(); - this.cursor = cursor; - } - - /** @inheritdoc */ - moveNext() { - const retv = this.cursor.moveNext(); - - this.current = this.cursor.current; - - return retv; - } + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + */ + constructor(cursor) { + super(); + this.cursor = cursor; + } + + /** @inheritdoc */ + moveNext() { + const retv = this.cursor.moveNext(); + + this.current = this.cursor.current; + + return retv; + } }; diff --git a/lib/languages/js/source-code/token-store/filter-cursor.js b/lib/languages/js/source-code/token-store/filter-cursor.js index 08c4f22031af..1e2ec9969a7f 100644 --- a/lib/languages/js/source-code/token-store/filter-cursor.js +++ b/lib/languages/js/source-code/token-store/filter-cursor.js @@ -18,26 +18,25 @@ const DecorativeCursor = require("./decorative-cursor"); * The decorative cursor which ignores specified tokens. */ module.exports = class FilterCursor extends DecorativeCursor { + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {Function} predicate The predicate function to decide tokens this cursor iterates. + */ + constructor(cursor, predicate) { + super(cursor); + this.predicate = predicate; + } - /** - * Initializes this cursor. - * @param {Cursor} cursor The cursor to be decorated. - * @param {Function} predicate The predicate function to decide tokens this cursor iterates. - */ - constructor(cursor, predicate) { - super(cursor); - this.predicate = predicate; - } + /** @inheritdoc */ + moveNext() { + const predicate = this.predicate; - /** @inheritdoc */ - moveNext() { - const predicate = this.predicate; - - while (super.moveNext()) { - if (predicate(this.current)) { - return true; - } - } - return false; - } + while (super.moveNext()) { + if (predicate(this.current)) { + return true; + } + } + return false; + } }; diff --git a/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js b/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js index 8aa46c27b743..d4a1439fe291 100644 --- a/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js +++ b/lib/languages/js/source-code/token-store/forward-token-comment-cursor.js @@ -19,39 +19,47 @@ const { getFirstIndex, search } = require("./utils"); * The cursor which iterates tokens and comments. */ module.exports = class ForwardTokenCommentCursor extends Cursor { + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.comments = comments; + this.tokenIndex = getFirstIndex(tokens, indexMap, startLoc); + this.commentIndex = search(comments, startLoc); + this.border = endLoc; + } - /** - * Initializes this cursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - */ - constructor(tokens, comments, indexMap, startLoc, endLoc) { - super(); - this.tokens = tokens; - this.comments = comments; - this.tokenIndex = getFirstIndex(tokens, indexMap, startLoc); - this.commentIndex = search(comments, startLoc); - this.border = endLoc; - } + /** @inheritdoc */ + moveNext() { + const token = + this.tokenIndex < this.tokens.length + ? this.tokens[this.tokenIndex] + : null; + const comment = + this.commentIndex < this.comments.length + ? this.comments[this.commentIndex] + : null; - /** @inheritdoc */ - moveNext() { - const token = (this.tokenIndex < this.tokens.length) ? this.tokens[this.tokenIndex] : null; - const comment = (this.commentIndex < this.comments.length) ? this.comments[this.commentIndex] : null; + if (token && (!comment || token.range[0] < comment.range[0])) { + this.current = token; + this.tokenIndex += 1; + } else if (comment) { + this.current = comment; + this.commentIndex += 1; + } else { + this.current = null; + } - if (token && (!comment || token.range[0] < comment.range[0])) { - this.current = token; - this.tokenIndex += 1; - } else if (comment) { - this.current = comment; - this.commentIndex += 1; - } else { - this.current = null; - } - - return Boolean(this.current) && (this.border === -1 || this.current.range[1] <= this.border); - } + return ( + Boolean(this.current) && + (this.border === -1 || this.current.range[1] <= this.border) + ); + } }; diff --git a/lib/languages/js/source-code/token-store/forward-token-cursor.js b/lib/languages/js/source-code/token-store/forward-token-cursor.js index 9305cbef6838..5ca2290f1dd5 100644 --- a/lib/languages/js/source-code/token-store/forward-token-cursor.js +++ b/lib/languages/js/source-code/token-store/forward-token-cursor.js @@ -19,45 +19,44 @@ const { getFirstIndex, getLastIndex } = require("./utils"); * The cursor which iterates tokens only. */ module.exports = class ForwardTokenCursor extends Cursor { - - /** - * Initializes this cursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - */ - constructor(tokens, comments, indexMap, startLoc, endLoc) { - super(); - this.tokens = tokens; - this.index = getFirstIndex(tokens, indexMap, startLoc); - this.indexEnd = getLastIndex(tokens, indexMap, endLoc); - } - - /** @inheritdoc */ - moveNext() { - if (this.index <= this.indexEnd) { - this.current = this.tokens[this.index]; - this.index += 1; - return true; - } - return false; - } - - /* - * - * Shorthand for performance. - * - */ - - /** @inheritdoc */ - getOneToken() { - return (this.index <= this.indexEnd) ? this.tokens[this.index] : null; - } - - /** @inheritdoc */ - getAllTokens() { - return this.tokens.slice(this.index, this.indexEnd + 1); - } + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + */ + constructor(tokens, comments, indexMap, startLoc, endLoc) { + super(); + this.tokens = tokens; + this.index = getFirstIndex(tokens, indexMap, startLoc); + this.indexEnd = getLastIndex(tokens, indexMap, endLoc); + } + + /** @inheritdoc */ + moveNext() { + if (this.index <= this.indexEnd) { + this.current = this.tokens[this.index]; + this.index += 1; + return true; + } + return false; + } + + /* + * + * Shorthand for performance. + * + */ + + /** @inheritdoc */ + getOneToken() { + return this.index <= this.indexEnd ? this.tokens[this.index] : null; + } + + /** @inheritdoc */ + getAllTokens() { + return this.tokens.slice(this.index, this.indexEnd + 1); + } }; diff --git a/lib/languages/js/source-code/token-store/index.js b/lib/languages/js/source-code/token-store/index.js index d44191ac52b0..ffb811ebd3f2 100644 --- a/lib/languages/js/source-code/token-store/index.js +++ b/lib/languages/js/source-code/token-store/index.js @@ -34,29 +34,41 @@ const INDEX_MAP = Symbol("indexMap"); * @private */ function createIndexMap(tokens, comments) { - const map = Object.create(null); - let tokenIndex = 0; - let commentIndex = 0; - let nextStart; - let range; - - while (tokenIndex < tokens.length || commentIndex < comments.length) { - nextStart = (commentIndex < comments.length) ? comments[commentIndex].range[0] : Number.MAX_SAFE_INTEGER; - while (tokenIndex < tokens.length && (range = tokens[tokenIndex].range)[0] < nextStart) { - map[range[0]] = tokenIndex; - map[range[1] - 1] = tokenIndex; - tokenIndex += 1; - } - - nextStart = (tokenIndex < tokens.length) ? tokens[tokenIndex].range[0] : Number.MAX_SAFE_INTEGER; - while (commentIndex < comments.length && (range = comments[commentIndex].range)[0] < nextStart) { - map[range[0]] = tokenIndex; - map[range[1] - 1] = tokenIndex; - commentIndex += 1; - } - } - - return map; + const map = Object.create(null); + let tokenIndex = 0; + let commentIndex = 0; + let nextStart; + let range; + + while (tokenIndex < tokens.length || commentIndex < comments.length) { + nextStart = + commentIndex < comments.length + ? comments[commentIndex].range[0] + : Number.MAX_SAFE_INTEGER; + while ( + tokenIndex < tokens.length && + (range = tokens[tokenIndex].range)[0] < nextStart + ) { + map[range[0]] = tokenIndex; + map[range[1] - 1] = tokenIndex; + tokenIndex += 1; + } + + nextStart = + tokenIndex < tokens.length + ? tokens[tokenIndex].range[0] + : Number.MAX_SAFE_INTEGER; + while ( + commentIndex < comments.length && + (range = comments[commentIndex].range)[0] < nextStart + ) { + map[range[0]] = tokenIndex; + map[range[1] - 1] = tokenIndex; + commentIndex += 1; + } + } + + return map; } /** @@ -74,24 +86,45 @@ function createIndexMap(tokens, comments) { * @returns {Cursor} The created cursor. * @private */ -function createCursorWithSkip(factory, tokens, comments, indexMap, startLoc, endLoc, opts) { - let includeComments = false; - let skip = 0; - let filter = null; - - if (typeof opts === "number") { - skip = opts | 0; - } else if (typeof opts === "function") { - filter = opts; - } else if (opts) { - includeComments = !!opts.includeComments; - skip = opts.skip | 0; - filter = opts.filter || null; - } - assert(skip >= 0, "options.skip should be zero or a positive integer."); - assert(!filter || typeof filter === "function", "options.filter should be a function."); - - return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, skip, -1); +function createCursorWithSkip( + factory, + tokens, + comments, + indexMap, + startLoc, + endLoc, + opts, +) { + let includeComments = false; + let skip = 0; + let filter = null; + + if (typeof opts === "number") { + skip = opts | 0; + } else if (typeof opts === "function") { + filter = opts; + } else if (opts) { + includeComments = !!opts.includeComments; + skip = opts.skip | 0; + filter = opts.filter || null; + } + assert(skip >= 0, "options.skip should be zero or a positive integer."); + assert( + !filter || typeof filter === "function", + "options.filter should be a function.", + ); + + return factory.createCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + includeComments, + filter, + skip, + -1, + ); } /** @@ -109,27 +142,48 @@ function createCursorWithSkip(factory, tokens, comments, indexMap, startLoc, end * @returns {Cursor} The created cursor. * @private */ -function createCursorWithCount(factory, tokens, comments, indexMap, startLoc, endLoc, opts) { - let includeComments = false; - let count = 0; - let countExists = false; - let filter = null; - - if (typeof opts === "number") { - count = opts | 0; - countExists = true; - } else if (typeof opts === "function") { - filter = opts; - } else if (opts) { - includeComments = !!opts.includeComments; - count = opts.count | 0; - countExists = typeof opts.count === "number"; - filter = opts.filter || null; - } - assert(count >= 0, "options.count should be zero or a positive integer."); - assert(!filter || typeof filter === "function", "options.filter should be a function."); - - return factory.createCursor(tokens, comments, indexMap, startLoc, endLoc, includeComments, filter, 0, countExists ? count : -1); +function createCursorWithCount( + factory, + tokens, + comments, + indexMap, + startLoc, + endLoc, + opts, +) { + let includeComments = false; + let count = 0; + let countExists = false; + let filter = null; + + if (typeof opts === "number") { + count = opts | 0; + countExists = true; + } else if (typeof opts === "function") { + filter = opts; + } else if (opts) { + includeComments = !!opts.includeComments; + count = opts.count | 0; + countExists = typeof opts.count === "number"; + filter = opts.filter || null; + } + assert(count >= 0, "options.count should be zero or a positive integer."); + assert( + !filter || typeof filter === "function", + "options.filter should be a function.", + ); + + return factory.createCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + includeComments, + filter, + 0, + countExists ? count : -1, + ); } /** @@ -159,14 +213,47 @@ function createCursorWithCount(factory, tokens, comments, indexMap, startLoc, en * @returns {Cursor} The created cursor. * @private */ -function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) { - if (typeof beforeCount === "undefined" && typeof afterCount === "undefined") { - return new ForwardTokenCursor(tokens, comments, indexMap, startLoc, endLoc); - } - if (typeof beforeCount === "number" || typeof beforeCount === "undefined") { - return new PaddedTokenCursor(tokens, comments, indexMap, startLoc, endLoc, beforeCount | 0, afterCount | 0); - } - return createCursorWithCount(cursors.forward, tokens, comments, indexMap, startLoc, endLoc, beforeCount); +function createCursorWithPadding( + tokens, + comments, + indexMap, + startLoc, + endLoc, + beforeCount, + afterCount, +) { + if ( + typeof beforeCount === "undefined" && + typeof afterCount === "undefined" + ) { + return new ForwardTokenCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + ); + } + if (typeof beforeCount === "number" || typeof beforeCount === "undefined") { + return new PaddedTokenCursor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + beforeCount | 0, + afterCount | 0, + ); + } + return createCursorWithCount( + cursors.forward, + tokens, + comments, + indexMap, + startLoc, + endLoc, + beforeCount, + ); } /** @@ -176,15 +263,15 @@ function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, b * @private */ function getAdjacentCommentTokensFromCursor(cursor) { - const tokens = []; - let currentToken = cursor.getOneToken(); + const tokens = []; + let currentToken = cursor.getOneToken(); - while (currentToken && isCommentToken(currentToken)) { - tokens.push(currentToken); - currentToken = cursor.getOneToken(); - } + while (currentToken && isCommentToken(currentToken)) { + tokens.push(currentToken); + currentToken = cursor.getOneToken(); + } - return tokens; + return tokens; } //------------------------------------------------------------------------------ @@ -203,425 +290,432 @@ function getAdjacentCommentTokensFromCursor(cursor) { * This uses binary-searching instead for comments. */ module.exports = class TokenStore { - - /** - * Initializes this token store. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - */ - constructor(tokens, comments) { - this[TOKENS] = tokens; - this[COMMENTS] = comments; - this[INDEX_MAP] = createIndexMap(tokens, comments); - } - - //-------------------------------------------------------------------------- - // Gets single token. - //-------------------------------------------------------------------------- - - /** - * Gets the token starting at the specified index. - * @param {number} offset Index of the start of the token's range. - * @param {Object} [options=0] The option object. - * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. - * @returns {Token|null} The token starting at index, or null if no such token. - */ - getTokenByRangeStart(offset, options) { - const includeComments = options && options.includeComments; - const token = cursors.forward.createBaseCursor( - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - offset, - -1, - includeComments - ).getOneToken(); - - if (token && token.range[0] === offset) { - return token; - } - return null; - } - - /** - * Gets the first token of the given node. - * @param {ASTNode} node The AST node. - * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`. - * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. - * @param {Function|null} [options.filter=null] The predicate function to choose tokens. - * @param {number} [options.skip=0] The count of tokens the cursor skips. - * @returns {Token|null} An object representing the token. - */ - getFirstToken(node, options) { - return createCursorWithSkip( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[0], - node.range[1], - options - ).getOneToken(); - } - - /** - * Gets the last token of the given node. - * @param {ASTNode} node The AST node. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() - * @returns {Token|null} An object representing the token. - */ - getLastToken(node, options) { - return createCursorWithSkip( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[0], - node.range[1], - options - ).getOneToken(); - } - - /** - * Gets the token that precedes a given node or token. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() - * @returns {Token|null} An object representing the token. - */ - getTokenBefore(node, options) { - return createCursorWithSkip( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - -1, - node.range[0], - options - ).getOneToken(); - } - - /** - * Gets the token that follows a given node or token. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() - * @returns {Token|null} An object representing the token. - */ - getTokenAfter(node, options) { - return createCursorWithSkip( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[1], - -1, - options - ).getOneToken(); - } - - /** - * Gets the first token between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() - * @returns {Token|null} An object representing the token. - */ - getFirstTokenBetween(left, right, options) { - return createCursorWithSkip( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - left.range[1], - right.range[0], - options - ).getOneToken(); - } - - /** - * Gets the last token between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() - * @returns {Token|null} An object representing the token. - */ - getLastTokenBetween(left, right, options) { - return createCursorWithSkip( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - left.range[1], - right.range[0], - options - ).getOneToken(); - } - - /** - * Gets the token that precedes a given node or token in the token stream. - * This is defined for backward compatibility. Use `includeComments` option instead. - * TODO: We have a plan to remove this in a future major version. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number} [skip=0] A number of tokens to skip. - * @returns {Token|null} An object representing the token. - * @deprecated - */ - getTokenOrCommentBefore(node, skip) { - return this.getTokenBefore(node, { includeComments: true, skip }); - } - - /** - * Gets the token that follows a given node or token in the token stream. - * This is defined for backward compatibility. Use `includeComments` option instead. - * TODO: We have a plan to remove this in a future major version. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number} [skip=0] A number of tokens to skip. - * @returns {Token|null} An object representing the token. - * @deprecated - */ - getTokenOrCommentAfter(node, skip) { - return this.getTokenAfter(node, { includeComments: true, skip }); - } - - //-------------------------------------------------------------------------- - // Gets multiple tokens. - //-------------------------------------------------------------------------- - - /** - * Gets the first `count` tokens of the given node. - * @param {ASTNode} node The AST node. - * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`. - * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. - * @param {Function|null} [options.filter=null] The predicate function to choose tokens. - * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. - * @returns {Token[]} Tokens. - */ - getFirstTokens(node, options) { - return createCursorWithCount( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[0], - node.range[1], - options - ).getAllTokens(); - } - - /** - * Gets the last `count` tokens of the given node. - * @param {ASTNode} node The AST node. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() - * @returns {Token[]} Tokens. - */ - getLastTokens(node, options) { - return createCursorWithCount( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[0], - node.range[1], - options - ).getAllTokens().reverse(); - } - - /** - * Gets the `count` tokens that precedes a given node or token. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() - * @returns {Token[]} Tokens. - */ - getTokensBefore(node, options) { - return createCursorWithCount( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - -1, - node.range[0], - options - ).getAllTokens().reverse(); - } - - /** - * Gets the `count` tokens that follows a given node or token. - * @param {ASTNode|Token|Comment} node The AST node or token. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() - * @returns {Token[]} Tokens. - */ - getTokensAfter(node, options) { - return createCursorWithCount( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[1], - -1, - options - ).getAllTokens(); - } - - /** - * Gets the first `count` tokens between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() - * @returns {Token[]} Tokens between left and right. - */ - getFirstTokensBetween(left, right, options) { - return createCursorWithCount( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - left.range[1], - right.range[0], - options - ).getAllTokens(); - } - - /** - * Gets the last `count` tokens between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() - * @returns {Token[]} Tokens between left and right. - */ - getLastTokensBetween(left, right, options) { - return createCursorWithCount( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - left.range[1], - right.range[0], - options - ).getAllTokens().reverse(); - } - - /** - * Gets all tokens that are related to the given node. - * @param {ASTNode} node The AST node. - * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. - * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. - * @param {Function|null} [options.filter=null] The predicate function to choose tokens. - * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. - * @returns {Token[]} Array of objects representing tokens. - */ - /** - * Gets all tokens that are related to the given node. - * @param {ASTNode} node The AST node. - * @param {int} [beforeCount=0] The number of tokens before the node to retrieve. - * @param {int} [afterCount=0] The number of tokens after the node to retrieve. - * @returns {Token[]} Array of objects representing tokens. - */ - getTokens(node, beforeCount, afterCount) { - return createCursorWithPadding( - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - node.range[0], - node.range[1], - beforeCount, - afterCount - ).getAllTokens(); - } - - /** - * Gets all of the tokens between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. - * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. - * @param {Function|null} [options.filter=null] The predicate function to choose tokens. - * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. - * @returns {Token[]} Tokens between left and right. - */ - /** - * Gets all of the tokens between two non-overlapping nodes. - * @param {ASTNode|Token|Comment} left Node before the desired token range. - * @param {ASTNode|Token|Comment} right Node after the desired token range. - * @param {int} [padding=0] Number of extra tokens on either side of center. - * @returns {Token[]} Tokens between left and right. - */ - getTokensBetween(left, right, padding) { - return createCursorWithPadding( - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - left.range[1], - right.range[0], - padding, - padding - ).getAllTokens(); - } - - //-------------------------------------------------------------------------- - // Others. - //-------------------------------------------------------------------------- - - /** - * Checks whether any comments exist or not between the given 2 nodes. - * @param {ASTNode} left The node to check. - * @param {ASTNode} right The node to check. - * @returns {boolean} `true` if one or more comments exist. - */ - commentsExistBetween(left, right) { - const index = utils.search(this[COMMENTS], left.range[1]); - - return ( - index < this[COMMENTS].length && - this[COMMENTS][index].range[1] <= right.range[0] - ); - } - - /** - * Gets all comment tokens directly before the given node or token. - * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. - * @returns {Array} An array of comments in occurrence order. - */ - getCommentsBefore(nodeOrToken) { - const cursor = createCursorWithCount( - cursors.backward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - -1, - nodeOrToken.range[0], - { includeComments: true } - ); - - return getAdjacentCommentTokensFromCursor(cursor).reverse(); - } - - /** - * Gets all comment tokens directly after the given node or token. - * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. - * @returns {Array} An array of comments in occurrence order. - */ - getCommentsAfter(nodeOrToken) { - const cursor = createCursorWithCount( - cursors.forward, - this[TOKENS], - this[COMMENTS], - this[INDEX_MAP], - nodeOrToken.range[1], - -1, - { includeComments: true } - ); - - return getAdjacentCommentTokensFromCursor(cursor); - } - - /** - * Gets all comment tokens inside the given node. - * @param {ASTNode} node The AST node to get the comments for. - * @returns {Array} An array of comments in occurrence order. - */ - getCommentsInside(node) { - return this.getTokens(node, { - includeComments: true, - filter: isCommentToken - }); - } + /** + * Initializes this token store. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + */ + constructor(tokens, comments) { + this[TOKENS] = tokens; + this[COMMENTS] = comments; + this[INDEX_MAP] = createIndexMap(tokens, comments); + } + + //-------------------------------------------------------------------------- + // Gets single token. + //-------------------------------------------------------------------------- + + /** + * Gets the token starting at the specified index. + * @param {number} offset Index of the start of the token's range. + * @param {Object} [options=0] The option object. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @returns {Token|null} The token starting at index, or null if no such token. + */ + getTokenByRangeStart(offset, options) { + const includeComments = options && options.includeComments; + const token = cursors.forward + .createBaseCursor( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + offset, + -1, + includeComments, + ) + .getOneToken(); + + if (token && token.range[0] === offset) { + return token; + } + return null; + } + + /** + * Gets the first token of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.skip`. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.skip=0] The count of tokens the cursor skips. + * @returns {Token|null} An object representing the token. + */ + getFirstToken(node, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options, + ).getOneToken(); + } + + /** + * Gets the last token of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getLastToken(node, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options, + ).getOneToken(); + } + + /** + * Gets the token that precedes a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getTokenBefore(node, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + node.range[0], + options, + ).getOneToken(); + } + + /** + * Gets the token that follows a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getTokenAfter(node, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[1], + -1, + options, + ).getOneToken(); + } + + /** + * Gets the first token between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getFirstTokenBetween(left, right, options) { + return createCursorWithSkip( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options, + ).getOneToken(); + } + + /** + * Gets the last token between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstToken() + * @returns {Token|null} An object representing the token. + */ + getLastTokenBetween(left, right, options) { + return createCursorWithSkip( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options, + ).getOneToken(); + } + + /** + * Gets the token that precedes a given node or token in the token stream. + * This is defined for backward compatibility. Use `includeComments` option instead. + * TODO: We have a plan to remove this in a future major version. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number} [skip=0] A number of tokens to skip. + * @returns {Token|null} An object representing the token. + * @deprecated + */ + getTokenOrCommentBefore(node, skip) { + return this.getTokenBefore(node, { includeComments: true, skip }); + } + + /** + * Gets the token that follows a given node or token in the token stream. + * This is defined for backward compatibility. Use `includeComments` option instead. + * TODO: We have a plan to remove this in a future major version. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number} [skip=0] A number of tokens to skip. + * @returns {Token|null} An object representing the token. + * @deprecated + */ + getTokenOrCommentAfter(node, skip) { + return this.getTokenAfter(node, { includeComments: true, skip }); + } + + //-------------------------------------------------------------------------- + // Gets multiple tokens. + //-------------------------------------------------------------------------- + + /** + * Gets the first `count` tokens of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. If this is a number then it's `options.count`. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Tokens. + */ + getFirstTokens(node, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options, + ).getAllTokens(); + } + + /** + * Gets the last `count` tokens of the given node. + * @param {ASTNode} node The AST node. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getLastTokens(node, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + options, + ) + .getAllTokens() + .reverse(); + } + + /** + * Gets the `count` tokens that precedes a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getTokensBefore(node, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + node.range[0], + options, + ) + .getAllTokens() + .reverse(); + } + + /** + * Gets the `count` tokens that follows a given node or token. + * @param {ASTNode|Token|Comment} node The AST node or token. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens. + */ + getTokensAfter(node, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[1], + -1, + options, + ).getAllTokens(); + } + + /** + * Gets the first `count` tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens between left and right. + */ + getFirstTokensBetween(left, right, options) { + return createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options, + ).getAllTokens(); + } + + /** + * Gets the last `count` tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {number|Function|Object} [options=0] The option object. Same options as getFirstTokens() + * @returns {Token[]} Tokens between left and right. + */ + getLastTokensBetween(left, right, options) { + return createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + options, + ) + .getAllTokens() + .reverse(); + } + + /** + * Gets all tokens that are related to the given node. + * @param {ASTNode} node The AST node. + * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Array of objects representing tokens. + */ + /** + * Gets all tokens that are related to the given node. + * @param {ASTNode} node The AST node. + * @param {int} [beforeCount=0] The number of tokens before the node to retrieve. + * @param {int} [afterCount=0] The number of tokens after the node to retrieve. + * @returns {Token[]} Array of objects representing tokens. + */ + getTokens(node, beforeCount, afterCount) { + return createCursorWithPadding( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + node.range[0], + node.range[1], + beforeCount, + afterCount, + ).getAllTokens(); + } + + /** + * Gets all of the tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {Function|Object} options The option object. If this is a function then it's `options.filter`. + * @param {boolean} [options.includeComments=false] The flag to iterate comments as well. + * @param {Function|null} [options.filter=null] The predicate function to choose tokens. + * @param {number} [options.count=0] The maximum count of tokens the cursor iterates. + * @returns {Token[]} Tokens between left and right. + */ + /** + * Gets all of the tokens between two non-overlapping nodes. + * @param {ASTNode|Token|Comment} left Node before the desired token range. + * @param {ASTNode|Token|Comment} right Node after the desired token range. + * @param {int} [padding=0] Number of extra tokens on either side of center. + * @returns {Token[]} Tokens between left and right. + */ + getTokensBetween(left, right, padding) { + return createCursorWithPadding( + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + left.range[1], + right.range[0], + padding, + padding, + ).getAllTokens(); + } + + //-------------------------------------------------------------------------- + // Others. + //-------------------------------------------------------------------------- + + /** + * Checks whether any comments exist or not between the given 2 nodes. + * @param {ASTNode} left The node to check. + * @param {ASTNode} right The node to check. + * @returns {boolean} `true` if one or more comments exist. + */ + commentsExistBetween(left, right) { + const index = utils.search(this[COMMENTS], left.range[1]); + + return ( + index < this[COMMENTS].length && + this[COMMENTS][index].range[1] <= right.range[0] + ); + } + + /** + * Gets all comment tokens directly before the given node or token. + * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsBefore(nodeOrToken) { + const cursor = createCursorWithCount( + cursors.backward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + -1, + nodeOrToken.range[0], + { includeComments: true }, + ); + + return getAdjacentCommentTokensFromCursor(cursor).reverse(); + } + + /** + * Gets all comment tokens directly after the given node or token. + * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsAfter(nodeOrToken) { + const cursor = createCursorWithCount( + cursors.forward, + this[TOKENS], + this[COMMENTS], + this[INDEX_MAP], + nodeOrToken.range[1], + -1, + { includeComments: true }, + ); + + return getAdjacentCommentTokensFromCursor(cursor); + } + + /** + * Gets all comment tokens inside the given node. + * @param {ASTNode} node The AST node to get the comments for. + * @returns {Array} An array of comments in occurrence order. + */ + getCommentsInside(node) { + return this.getTokens(node, { + includeComments: true, + filter: isCommentToken, + }); + } }; diff --git a/lib/languages/js/source-code/token-store/limit-cursor.js b/lib/languages/js/source-code/token-store/limit-cursor.js index 0fd92a77657a..301d7f54c911 100644 --- a/lib/languages/js/source-code/token-store/limit-cursor.js +++ b/lib/languages/js/source-code/token-store/limit-cursor.js @@ -18,23 +18,22 @@ const DecorativeCursor = require("./decorative-cursor"); * The decorative cursor which limits the number of tokens. */ module.exports = class LimitCursor extends DecorativeCursor { + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {number} count The count of tokens this cursor iterates. + */ + constructor(cursor, count) { + super(cursor); + this.count = count; + } - /** - * Initializes this cursor. - * @param {Cursor} cursor The cursor to be decorated. - * @param {number} count The count of tokens this cursor iterates. - */ - constructor(cursor, count) { - super(cursor); - this.count = count; - } - - /** @inheritdoc */ - moveNext() { - if (this.count > 0) { - this.count -= 1; - return super.moveNext(); - } - return false; - } + /** @inheritdoc */ + moveNext() { + if (this.count > 0) { + this.count -= 1; + return super.moveNext(); + } + return false; + } }; diff --git a/lib/languages/js/source-code/token-store/padded-token-cursor.js b/lib/languages/js/source-code/token-store/padded-token-cursor.js index 89349fa1c695..3d3ce04f1ce8 100644 --- a/lib/languages/js/source-code/token-store/padded-token-cursor.js +++ b/lib/languages/js/source-code/token-store/padded-token-cursor.js @@ -19,20 +19,27 @@ const ForwardTokenCursor = require("./forward-token-cursor"); * This is for the backward compatibility of padding options. */ module.exports = class PaddedTokenCursor extends ForwardTokenCursor { - - /** - * Initializes this cursor. - * @param {Token[]} tokens The array of tokens. - * @param {Comment[]} comments The array of comments. - * @param {Object} indexMap The map from locations to indices in `tokens`. - * @param {number} startLoc The start location of the iteration range. - * @param {number} endLoc The end location of the iteration range. - * @param {number} beforeCount The number of tokens this cursor iterates before start. - * @param {number} afterCount The number of tokens this cursor iterates after end. - */ - constructor(tokens, comments, indexMap, startLoc, endLoc, beforeCount, afterCount) { - super(tokens, comments, indexMap, startLoc, endLoc); - this.index = Math.max(0, this.index - beforeCount); - this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount); - } + /** + * Initializes this cursor. + * @param {Token[]} tokens The array of tokens. + * @param {Comment[]} comments The array of comments. + * @param {Object} indexMap The map from locations to indices in `tokens`. + * @param {number} startLoc The start location of the iteration range. + * @param {number} endLoc The end location of the iteration range. + * @param {number} beforeCount The number of tokens this cursor iterates before start. + * @param {number} afterCount The number of tokens this cursor iterates after end. + */ + constructor( + tokens, + comments, + indexMap, + startLoc, + endLoc, + beforeCount, + afterCount, + ) { + super(tokens, comments, indexMap, startLoc, endLoc); + this.index = Math.max(0, this.index - beforeCount); + this.indexEnd = Math.min(tokens.length - 1, this.indexEnd + afterCount); + } }; diff --git a/lib/languages/js/source-code/token-store/skip-cursor.js b/lib/languages/js/source-code/token-store/skip-cursor.js index f068f531c1e3..6bc43d85604f 100644 --- a/lib/languages/js/source-code/token-store/skip-cursor.js +++ b/lib/languages/js/source-code/token-store/skip-cursor.js @@ -18,25 +18,24 @@ const DecorativeCursor = require("./decorative-cursor"); * The decorative cursor which ignores the first few tokens. */ module.exports = class SkipCursor extends DecorativeCursor { + /** + * Initializes this cursor. + * @param {Cursor} cursor The cursor to be decorated. + * @param {number} count The count of tokens this cursor skips. + */ + constructor(cursor, count) { + super(cursor); + this.count = count; + } - /** - * Initializes this cursor. - * @param {Cursor} cursor The cursor to be decorated. - * @param {number} count The count of tokens this cursor skips. - */ - constructor(cursor, count) { - super(cursor); - this.count = count; - } - - /** @inheritdoc */ - moveNext() { - while (this.count > 0) { - this.count -= 1; - if (!super.moveNext()) { - return false; - } - } - return super.moveNext(); - } + /** @inheritdoc */ + moveNext() { + while (this.count > 0) { + this.count -= 1; + if (!super.moveNext()) { + return false; + } + } + return super.moveNext(); + } }; diff --git a/lib/languages/js/source-code/token-store/utils.js b/lib/languages/js/source-code/token-store/utils.js index 3e01470321a4..94e456c9f36d 100644 --- a/lib/languages/js/source-code/token-store/utils.js +++ b/lib/languages/js/source-code/token-store/utils.js @@ -16,28 +16,31 @@ * @returns {number} The found index or `tokens.length`. */ exports.search = function search(tokens, location) { - for (let minIndex = 0, maxIndex = tokens.length - 1; minIndex <= maxIndex;) { + for ( + let minIndex = 0, maxIndex = tokens.length - 1; + minIndex <= maxIndex; - /* - * Calculate the index in the middle between minIndex and maxIndex. - * `| 0` is used to round a fractional value down to the nearest integer: this is similar to - * using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to - * be faster. - */ - const index = (minIndex + maxIndex) / 2 | 0; - const token = tokens[index]; - const tokenStartLocation = token.range[0]; + ) { + /* + * Calculate the index in the middle between minIndex and maxIndex. + * `| 0` is used to round a fractional value down to the nearest integer: this is similar to + * using `Math.trunc()` or `Math.floor()`, but performance tests have shown this method to + * be faster. + */ + const index = ((minIndex + maxIndex) / 2) | 0; + const token = tokens[index]; + const tokenStartLocation = token.range[0]; - if (location <= tokenStartLocation) { - if (index === minIndex) { - return index; - } - maxIndex = index; - } else { - minIndex = index + 1; - } - } - return tokens.length; + if (location <= tokenStartLocation) { + if (index === minIndex) { + return index; + } + maxIndex = index; + } else { + minIndex = index + 1; + } + } + return tokens.length; }; /** @@ -49,28 +52,28 @@ exports.search = function search(tokens, location) { * @returns {number} The index. */ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { - if (startLoc in indexMap) { - return indexMap[startLoc]; - } - if ((startLoc - 1) in indexMap) { - const index = indexMap[startLoc - 1]; - const token = tokens[index]; + if (startLoc in indexMap) { + return indexMap[startLoc]; + } + if (startLoc - 1 in indexMap) { + const index = indexMap[startLoc - 1]; + const token = tokens[index]; - // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. - if (!token) { - return tokens.length; - } + // If the mapped index is out of bounds, the returned cursor index will point after the end of the tokens array. + if (!token) { + return tokens.length; + } - /* - * For the map of "comment's location -> token's index", it points the next token of a comment. - * In that case, +1 is unnecessary. - */ - if (token.range[0] >= startLoc) { - return index; - } - return index + 1; - } - return 0; + /* + * For the map of "comment's location -> token's index", it points the next token of a comment. + * In that case, +1 is unnecessary. + */ + if (token.range[0] >= startLoc) { + return index; + } + return index + 1; + } + return 0; }; /** @@ -82,26 +85,26 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) { * @returns {number} The index. */ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) { - if (endLoc in indexMap) { - return indexMap[endLoc] - 1; - } - if ((endLoc - 1) in indexMap) { - const index = indexMap[endLoc - 1]; - const token = tokens[index]; + if (endLoc in indexMap) { + return indexMap[endLoc] - 1; + } + if (endLoc - 1 in indexMap) { + const index = indexMap[endLoc - 1]; + const token = tokens[index]; - // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. - if (!token) { - return tokens.length - 1; - } + // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array. + if (!token) { + return tokens.length - 1; + } - /* - * For the map of "comment's location -> token's index", it points the next token of a comment. - * In that case, -1 is necessary. - */ - if (token.range[1] > endLoc) { - return index - 1; - } - return index; - } - return tokens.length - 1; + /* + * For the map of "comment's location -> token's index", it points the next token of a comment. + * In that case, -1 is necessary. + */ + if (token.range[1] > endLoc) { + return index - 1; + } + return index; + } + return tokens.length - 1; }; diff --git a/lib/languages/js/validate-language-options.js b/lib/languages/js/validate-language-options.js index ba1e5e39b83d..23ed266642ae 100644 --- a/lib/languages/js/validate-language-options.js +++ b/lib/languages/js/validate-language-options.js @@ -10,9 +10,16 @@ //----------------------------------------------------------------------------- const globalVariablesValues = new Set([ - true, "true", "writable", "writeable", - false, "false", "readonly", "readable", null, - "off" + true, + "true", + "writable", + "writeable", + false, + "false", + "readonly", + "readable", + null, + "off", ]); //------------------------------------------------------------------------------ @@ -25,7 +32,7 @@ const globalVariablesValues = new Set([ * @returns {boolean} `true` if the value is a non-null object. */ function isNonNullObject(value) { - return typeof value === "object" && value !== null; + return typeof value === "object" && value !== null; } /** @@ -34,7 +41,7 @@ function isNonNullObject(value) { * @returns {boolean} `true` if the value is a non-null non-array object. */ function isNonArrayObject(value) { - return isNonNullObject(value) && !Array.isArray(value); + return isNonNullObject(value) && !Array.isArray(value); } /** @@ -43,7 +50,7 @@ function isNonArrayObject(value) { * @returns {boolean} `true` if the value is undefined. */ function isUndefined(value) { - return typeof value === "undefined"; + return typeof value === "undefined"; } //----------------------------------------------------------------------------- @@ -57,15 +64,17 @@ function isUndefined(value) { * @throws {TypeError} If the value is invalid. */ function validateEcmaVersion(ecmaVersion) { - - if (isUndefined(ecmaVersion)) { - throw new TypeError("Key \"ecmaVersion\": Expected an \"ecmaVersion\" property."); - } - - if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") { - throw new TypeError("Key \"ecmaVersion\": Expected a number or \"latest\"."); - } - + if (isUndefined(ecmaVersion)) { + throw new TypeError( + 'Key "ecmaVersion": Expected an "ecmaVersion" property.', + ); + } + + if (typeof ecmaVersion !== "number" && ecmaVersion !== "latest") { + throw new TypeError( + 'Key "ecmaVersion": Expected a number or "latest".', + ); + } } /** @@ -75,11 +84,14 @@ function validateEcmaVersion(ecmaVersion) { * @throws {TypeError} If the value is invalid. */ function validateSourceType(sourceType) { - - if (typeof sourceType !== "string" || !/^(?:script|module|commonjs)$/u.test(sourceType)) { - throw new TypeError("Key \"sourceType\": Expected \"script\", \"module\", or \"commonjs\"."); - } - + if ( + typeof sourceType !== "string" || + !/^(?:script|module|commonjs)$/u.test(sourceType) + ) { + throw new TypeError( + 'Key "sourceType": Expected "script", "module", or "commonjs".', + ); + } } /** @@ -89,26 +101,28 @@ function validateSourceType(sourceType) { * @throws {TypeError} If the value is invalid. */ function validateGlobals(globals) { - - if (!isNonArrayObject(globals)) { - throw new TypeError("Key \"globals\": Expected an object."); - } - - for (const key of Object.keys(globals)) { - - // avoid hairy edge case - if (key === "__proto__") { - continue; - } - - if (key !== key.trim()) { - throw new TypeError(`Key "globals": Global "${key}" has leading or trailing whitespace.`); - } - - if (!globalVariablesValues.has(globals[key])) { - throw new TypeError(`Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`); - } - } + if (!isNonArrayObject(globals)) { + throw new TypeError('Key "globals": Expected an object.'); + } + + for (const key of Object.keys(globals)) { + // avoid hairy edge case + if (key === "__proto__") { + continue; + } + + if (key !== key.trim()) { + throw new TypeError( + `Key "globals": Global "${key}" has leading or trailing whitespace.`, + ); + } + + if (!globalVariablesValues.has(globals[key])) { + throw new TypeError( + `Key "globals": Key "${key}": Expected "readonly", "writable", or "off".`, + ); + } + } } /** @@ -118,13 +132,16 @@ function validateGlobals(globals) { * @throws {TypeError} If the value is invalid. */ function validateParser(parser) { - - if (!parser || typeof parser !== "object" || - (typeof parser.parse !== "function" && typeof parser.parseForESLint !== "function") - ) { - throw new TypeError("Key \"parser\": Expected object with parse() or parseForESLint() method."); - } - + if ( + !parser || + typeof parser !== "object" || + (typeof parser.parse !== "function" && + typeof parser.parseForESLint !== "function") + ) { + throw new TypeError( + 'Key "parser": Expected object with parse() or parseForESLint() method.', + ); + } } /** @@ -134,48 +151,46 @@ function validateParser(parser) { * @throws {TypeError} If the language options are invalid. */ function validateLanguageOptions(languageOptions) { - - if (!isNonArrayObject(languageOptions)) { - throw new TypeError("Expected an object."); - } - - const { - ecmaVersion, - sourceType, - globals, - parser, - parserOptions, - ...otherOptions - } = languageOptions; - - if ("ecmaVersion" in languageOptions) { - validateEcmaVersion(ecmaVersion); - } - - if ("sourceType" in languageOptions) { - validateSourceType(sourceType); - } - - if ("globals" in languageOptions) { - validateGlobals(globals); - } - - if ("parser" in languageOptions) { - validateParser(parser); - } - - if ("parserOptions" in languageOptions) { - if (!isNonArrayObject(parserOptions)) { - throw new TypeError("Key \"parserOptions\": Expected an object."); - } - } - - const otherOptionKeys = Object.keys(otherOptions); - - if (otherOptionKeys.length > 0) { - throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`); - } - + if (!isNonArrayObject(languageOptions)) { + throw new TypeError("Expected an object."); + } + + const { + ecmaVersion, + sourceType, + globals, + parser, + parserOptions, + ...otherOptions + } = languageOptions; + + if ("ecmaVersion" in languageOptions) { + validateEcmaVersion(ecmaVersion); + } + + if ("sourceType" in languageOptions) { + validateSourceType(sourceType); + } + + if ("globals" in languageOptions) { + validateGlobals(globals); + } + + if ("parser" in languageOptions) { + validateParser(parser); + } + + if ("parserOptions" in languageOptions) { + if (!isNonArrayObject(parserOptions)) { + throw new TypeError('Key "parserOptions": Expected an object.'); + } + } + + const otherOptionKeys = Object.keys(otherOptions); + + if (otherOptionKeys.length > 0) { + throw new TypeError(`Unexpected key "${otherOptionKeys[0]}" found.`); + } } module.exports = { validateLanguageOptions }; diff --git a/lib/linter/apply-disable-directives.js b/lib/linter/apply-disable-directives.js index 1f07447fbb4e..df595db57f71 100644 --- a/lib/linter/apply-disable-directives.js +++ b/lib/linter/apply-disable-directives.js @@ -20,9 +20,7 @@ const escapeRegExp = require("escape-string-regexp"); const { - Legacy: { - ConfigOps - } + Legacy: { ConfigOps }, } = require("@eslint/eslintrc/universal"); /** @@ -33,7 +31,7 @@ const { * itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location. */ function compareLocations(itemA, itemB) { - return itemA.line - itemB.line || itemA.column - itemB.column; + return itemA.line - itemB.line || itemA.column - itemB.column; } /** @@ -42,19 +40,21 @@ function compareLocations(itemA, itemB) { * @returns {Directive[][]} Directives grouped by their parent comment. */ function groupByParentDirective(directives) { - const groups = new Map(); + const groups = new Map(); - for (const directive of directives) { - const { unprocessedDirective: { parentDirective } } = directive; + for (const directive of directives) { + const { + unprocessedDirective: { parentDirective }, + } = directive; - if (groups.has(parentDirective)) { - groups.get(parentDirective).push(directive); - } else { - groups.set(parentDirective, [directive]); - } - } + if (groups.has(parentDirective)) { + groups.get(parentDirective).push(directive); + } else { + groups.set(parentDirective, [directive]); + } + } - return [...groups.values()]; + return [...groups.values()]; } /** @@ -64,93 +64,96 @@ function groupByParentDirective(directives) { * @param {SourceCode} sourceCode The source code object for the file being linted. * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. */ -function createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) { - - /* - * Get the list of the rules text without any surrounding whitespace. In order to preserve the original - * formatting, we don't want to change that whitespace. - * - * // eslint-disable-line rule-one , rule-two , rule-three -- comment - * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - */ - const listText = parentDirective.value.trim(); - - // Calculate where it starts in the source code text - const listStart = sourceCode.text.indexOf(listText, sourceCode.getRange(parentDirective.node)[0]); - - /* - * We can assume that `listText` contains multiple elements. - * Otherwise, this function wouldn't be called - if there is - * only one rule in the list, then the whole comment must be removed. - */ - - return directives.map(directive => { - const { ruleId } = directive; - - const regex = new RegExp(String.raw`(?:^|\s*,\s*)(?['"]?)${escapeRegExp(ruleId)}\k(?:\s*,\s*|$)`, "u"); - const match = regex.exec(listText); - const matchedText = match[0]; - const matchStart = listStart + match.index; - const matchEnd = matchStart + matchedText.length; - - const firstIndexOfComma = matchedText.indexOf(","); - const lastIndexOfComma = matchedText.lastIndexOf(","); - - let removalStart, removalEnd; - - if (firstIndexOfComma !== lastIndexOfComma) { - - /* - * Since there are two commas, this must one of the elements in the middle of the list. - * Matched range starts where the previous rule name ends, and ends where the next rule name starts. - * - * // eslint-disable-line rule-one , rule-two , rule-three -- comment - * ^^^^^^^^^^^^^^ - * - * We want to remove only the content between the two commas, and also one of the commas. - * - * // eslint-disable-line rule-one , rule-two , rule-three -- comment - * ^^^^^^^^^^^ - */ - removalStart = matchStart + firstIndexOfComma; - removalEnd = matchStart + lastIndexOfComma; - - } else { - - /* - * This is either the first element or the last element. - * - * If this is the first element, matched range starts where the first rule name starts - * and ends where the second rule name starts. This is exactly the range we want - * to remove so that the second rule name will start where the first one was starting - * and thus preserve the original formatting. - * - * // eslint-disable-line rule-one , rule-two , rule-three -- comment - * ^^^^^^^^^^^ - * - * Similarly, if this is the last element, we've already matched the range we want to - * remove. The previous rule name will end where the last one was ending, relative - * to the content on the right side. - * - * // eslint-disable-line rule-one , rule-two , rule-three -- comment - * ^^^^^^^^^^^^^ - */ - removalStart = matchStart; - removalEnd = matchEnd; - } - - return { - description: `'${ruleId}'`, - fix: { - range: [ - removalStart, - removalEnd - ], - text: "" - }, - unprocessedDirective: directive.unprocessedDirective - }; - }); +function createIndividualDirectivesRemoval( + directives, + parentDirective, + sourceCode, +) { + /* + * Get the list of the rules text without any surrounding whitespace. In order to preserve the original + * formatting, we don't want to change that whitespace. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + */ + const listText = parentDirective.value.trim(); + + // Calculate where it starts in the source code text + const listStart = sourceCode.text.indexOf( + listText, + sourceCode.getRange(parentDirective.node)[0], + ); + + /* + * We can assume that `listText` contains multiple elements. + * Otherwise, this function wouldn't be called - if there is + * only one rule in the list, then the whole comment must be removed. + */ + + return directives.map(directive => { + const { ruleId } = directive; + + const regex = new RegExp( + String.raw`(?:^|\s*,\s*)(?['"]?)${escapeRegExp(ruleId)}\k(?:\s*,\s*|$)`, + "u", + ); + const match = regex.exec(listText); + const matchedText = match[0]; + const matchStart = listStart + match.index; + const matchEnd = matchStart + matchedText.length; + + const firstIndexOfComma = matchedText.indexOf(","); + const lastIndexOfComma = matchedText.lastIndexOf(","); + + let removalStart, removalEnd; + + if (firstIndexOfComma !== lastIndexOfComma) { + /* + * Since there are two commas, this must one of the elements in the middle of the list. + * Matched range starts where the previous rule name ends, and ends where the next rule name starts. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^^ + * + * We want to remove only the content between the two commas, and also one of the commas. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^ + */ + removalStart = matchStart + firstIndexOfComma; + removalEnd = matchStart + lastIndexOfComma; + } else { + /* + * This is either the first element or the last element. + * + * If this is the first element, matched range starts where the first rule name starts + * and ends where the second rule name starts. This is exactly the range we want + * to remove so that the second rule name will start where the first one was starting + * and thus preserve the original formatting. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^ + * + * Similarly, if this is the last element, we've already matched the range we want to + * remove. The previous rule name will end where the last one was ending, relative + * to the content on the right side. + * + * // eslint-disable-line rule-one , rule-two , rule-three -- comment + * ^^^^^^^^^^^^^ + */ + removalStart = matchStart; + removalEnd = matchEnd; + } + + return { + description: `'${ruleId}'`, + fix: { + range: [removalStart, removalEnd], + text: "", + }, + unprocessedDirective: directive.unprocessedDirective, + }; + }); } /** @@ -161,19 +164,22 @@ function createIndividualDirectivesRemoval(directives, parentDirective, sourceCo * @returns {{ description, fix, unprocessedDirective }} Details for later creation of an output problem. */ function createDirectiveRemoval(directives, node, sourceCode) { - const range = sourceCode.getRange(node); - const ruleIds = directives.filter(directive => directive.ruleId).map(directive => `'${directive.ruleId}'`); - - return { - description: ruleIds.length <= 2 - ? ruleIds.join(" or ") - : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`, - fix: { - range, - text: " " - }, - unprocessedDirective: directives[0].unprocessedDirective - }; + const range = sourceCode.getRange(node); + const ruleIds = directives + .filter(directive => directive.ruleId) + .map(directive => `'${directive.ruleId}'`); + + return { + description: + ruleIds.length <= 2 + ? ruleIds.join(" or ") + : `${ruleIds.slice(0, ruleIds.length - 1).join(", ")}, or ${ruleIds.at(-1)}`, + fix: { + range, + text: " ", + }, + unprocessedDirective: directives[0].unprocessedDirective, + }; } /** @@ -183,22 +189,30 @@ function createDirectiveRemoval(directives, node, sourceCode) { * @returns {{ description, fix, unprocessedDirective }[]} Details for later creation of output Problems. */ function processUnusedDirectives(allDirectives, sourceCode) { - const directiveGroups = groupByParentDirective(allDirectives); - - return directiveGroups.flatMap( - directives => { - const { parentDirective } = directives[0].unprocessedDirective; - const remainingRuleIds = new Set(parentDirective.ruleIds); - - for (const directive of directives) { - remainingRuleIds.delete(directive.ruleId); - } - - return remainingRuleIds.size - ? createIndividualDirectivesRemoval(directives, parentDirective, sourceCode) - : [createDirectiveRemoval(directives, parentDirective.node, sourceCode)]; - } - ); + const directiveGroups = groupByParentDirective(allDirectives); + + return directiveGroups.flatMap(directives => { + const { parentDirective } = directives[0].unprocessedDirective; + const remainingRuleIds = new Set(parentDirective.ruleIds); + + for (const directive of directives) { + remainingRuleIds.delete(directive.ruleId); + } + + return remainingRuleIds.size + ? createIndividualDirectivesRemoval( + directives, + parentDirective, + sourceCode, + ) + : [ + createDirectiveRemoval( + directives, + parentDirective.node, + sourceCode, + ), + ]; + }); } /** @@ -207,87 +221,83 @@ function processUnusedDirectives(allDirectives, sourceCode) { * @returns {Set} The used eslint-enable comments */ function collectUsedEnableDirectives(directives) { - - /** - * A Map of `eslint-enable` keyed by ruleIds that may be marked as used. - * If `eslint-enable` does not have a ruleId, the key will be `null`. - * @type {Map} - */ - const enabledRules = new Map(); - - /** - * A Set of `eslint-enable` marked as used. - * It is also the return value of `collectUsedEnableDirectives` function. - * @type {Set} - */ - const usedEnableDirectives = new Set(); - - /* - * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`, - * and if so, stores the `eslint-enable` in `usedEnableDirectives`. - */ - for (let index = directives.length - 1; index >= 0; index--) { - const directive = directives[index]; - - if (directive.type === "disable") { - if (enabledRules.size === 0) { - continue; - } - if (directive.ruleId === null) { - - // If encounter `eslint-disable` without ruleId, - // mark all `eslint-enable` currently held in enabledRules as used. - // e.g. - // /* eslint-disable */ <- current directive - // /* eslint-enable rule-id1 */ <- used - // /* eslint-enable rule-id2 */ <- used - // /* eslint-enable */ <- used - for (const enableDirective of enabledRules.values()) { - usedEnableDirectives.add(enableDirective); - } - enabledRules.clear(); - } else { - const enableDirective = enabledRules.get(directive.ruleId); - - if (enableDirective) { - - // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules, - // mark `eslint-enable` with ruleId as used. - // e.g. - // /* eslint-disable rule-id */ <- current directive - // /* eslint-enable rule-id */ <- used - usedEnableDirectives.add(enableDirective); - } else { - const enabledDirectiveWithoutRuleId = enabledRules.get(null); - - if (enabledDirectiveWithoutRuleId) { - - // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules, - // mark `eslint-enable` without ruleId as used. - // e.g. - // /* eslint-disable rule-id */ <- current directive - // /* eslint-enable */ <- used - usedEnableDirectives.add(enabledDirectiveWithoutRuleId); - } - } - } - } else if (directive.type === "enable") { - if (directive.ruleId === null) { - - // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused. - // So clear enabledRules. - // e.g. - // /* eslint-enable */ <- current directive - // /* eslint-enable rule-id *// <- unused - // /* eslint-enable */ <- unused - enabledRules.clear(); - enabledRules.set(null, directive); - } else { - enabledRules.set(directive.ruleId, directive); - } - } - } - return usedEnableDirectives; + /** + * A Map of `eslint-enable` keyed by ruleIds that may be marked as used. + * If `eslint-enable` does not have a ruleId, the key will be `null`. + * @type {Map} + */ + const enabledRules = new Map(); + + /** + * A Set of `eslint-enable` marked as used. + * It is also the return value of `collectUsedEnableDirectives` function. + * @type {Set} + */ + const usedEnableDirectives = new Set(); + + /* + * Checks the directives backwards to see if the encountered `eslint-enable` is used by the previous `eslint-disable`, + * and if so, stores the `eslint-enable` in `usedEnableDirectives`. + */ + for (let index = directives.length - 1; index >= 0; index--) { + const directive = directives[index]; + + if (directive.type === "disable") { + if (enabledRules.size === 0) { + continue; + } + if (directive.ruleId === null) { + // If encounter `eslint-disable` without ruleId, + // mark all `eslint-enable` currently held in enabledRules as used. + // e.g. + // /* eslint-disable */ <- current directive + // /* eslint-enable rule-id1 */ <- used + // /* eslint-enable rule-id2 */ <- used + // /* eslint-enable */ <- used + for (const enableDirective of enabledRules.values()) { + usedEnableDirectives.add(enableDirective); + } + enabledRules.clear(); + } else { + const enableDirective = enabledRules.get(directive.ruleId); + + if (enableDirective) { + // If encounter `eslint-disable` with ruleId, and there is an `eslint-enable` with the same ruleId in enabledRules, + // mark `eslint-enable` with ruleId as used. + // e.g. + // /* eslint-disable rule-id */ <- current directive + // /* eslint-enable rule-id */ <- used + usedEnableDirectives.add(enableDirective); + } else { + const enabledDirectiveWithoutRuleId = + enabledRules.get(null); + + if (enabledDirectiveWithoutRuleId) { + // If encounter `eslint-disable` with ruleId, and there is no `eslint-enable` with the same ruleId in enabledRules, + // mark `eslint-enable` without ruleId as used. + // e.g. + // /* eslint-disable rule-id */ <- current directive + // /* eslint-enable */ <- used + usedEnableDirectives.add(enabledDirectiveWithoutRuleId); + } + } + } + } else if (directive.type === "enable") { + if (directive.ruleId === null) { + // If encounter `eslint-enable` without ruleId, the `eslint-enable` that follows it are unused. + // So clear enabledRules. + // e.g. + // /* eslint-enable */ <- current directive + // /* eslint-enable rule-id *// <- unused + // /* eslint-enable */ <- unused + enabledRules.clear(); + enabledRules.set(null, directive); + } else { + enabledRules.set(directive.ruleId, directive); + } + } + } + return usedEnableDirectives; } /** @@ -301,105 +311,132 @@ function collectUsedEnableDirectives(directives) { * of problems (including suppressed ones) and unused eslint-disable directives */ function applyDirectives(options) { - const problems = []; - const usedDisableDirectives = new Set(); - const { sourceCode } = options; - - for (const problem of options.problems) { - let disableDirectivesForProblem = []; - let nextDirectiveIndex = 0; - - while ( - nextDirectiveIndex < options.directives.length && - compareLocations(options.directives[nextDirectiveIndex], problem) <= 0 - ) { - const directive = options.directives[nextDirectiveIndex++]; - - if (directive.ruleId === null || directive.ruleId === problem.ruleId) { - switch (directive.type) { - case "disable": - disableDirectivesForProblem.push(directive); - break; - - case "enable": - disableDirectivesForProblem = []; - break; - - // no default - } - } - } - - if (disableDirectivesForProblem.length > 0) { - const suppressions = disableDirectivesForProblem.map(directive => ({ - kind: "directive", - justification: directive.unprocessedDirective.justification - })); - - if (problem.suppressions) { - problem.suppressions = problem.suppressions.concat(suppressions); - } else { - problem.suppressions = suppressions; - usedDisableDirectives.add(disableDirectivesForProblem.at(-1)); - } - } - - problems.push(problem); - } - - const unusedDisableDirectivesToReport = options.directives - .filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive) && !options.rulesToIgnore.has(directive.ruleId)); - - - const unusedEnableDirectivesToReport = new Set( - options.directives.filter(directive => directive.unprocessedDirective.type === "enable" && !options.rulesToIgnore.has(directive.ruleId)) - ); - - /* - * If directives has the eslint-enable directive, - * check whether the eslint-enable comment is used. - */ - if (unusedEnableDirectivesToReport.size > 0) { - for (const directive of collectUsedEnableDirectives(options.directives)) { - unusedEnableDirectivesToReport.delete(directive); - } - } - - const processed = processUnusedDirectives(unusedDisableDirectivesToReport, sourceCode) - .concat(processUnusedDirectives(unusedEnableDirectivesToReport, sourceCode)); - const columnOffset = options.language.columnStart === 1 ? 0 : 1; - const lineOffset = options.language.lineStart === 1 ? 0 : 1; - - const unusedDirectives = processed - .map(({ description, fix, unprocessedDirective }) => { - const { parentDirective, type, line, column } = unprocessedDirective; - - let message; - - if (type === "enable") { - message = description - ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).` - : "Unused eslint-enable directive (no matching eslint-disable directives were found)."; - } else { - message = description - ? `Unused eslint-disable directive (no problems were reported from ${description}).` - : "Unused eslint-disable directive (no problems were reported)."; - } - - const loc = sourceCode.getLoc(parentDirective.node); - - return { - ruleId: null, - message, - line: type === "disable-next-line" ? loc.start.line + lineOffset : line, - column: type === "disable-next-line" ? loc.start.column + columnOffset : column, - severity: options.reportUnusedDisableDirectives === "warn" ? 1 : 2, - nodeType: null, - ...options.disableFixes ? {} : { fix } - }; - }); - - return { problems, unusedDirectives }; + const problems = []; + const usedDisableDirectives = new Set(); + const { sourceCode } = options; + + for (const problem of options.problems) { + let disableDirectivesForProblem = []; + let nextDirectiveIndex = 0; + + while ( + nextDirectiveIndex < options.directives.length && + compareLocations(options.directives[nextDirectiveIndex], problem) <= + 0 + ) { + const directive = options.directives[nextDirectiveIndex++]; + + if ( + directive.ruleId === null || + directive.ruleId === problem.ruleId + ) { + switch (directive.type) { + case "disable": + disableDirectivesForProblem.push(directive); + break; + + case "enable": + disableDirectivesForProblem = []; + break; + + // no default + } + } + } + + if (disableDirectivesForProblem.length > 0) { + const suppressions = disableDirectivesForProblem.map(directive => ({ + kind: "directive", + justification: directive.unprocessedDirective.justification, + })); + + if (problem.suppressions) { + problem.suppressions = + problem.suppressions.concat(suppressions); + } else { + problem.suppressions = suppressions; + usedDisableDirectives.add(disableDirectivesForProblem.at(-1)); + } + } + + problems.push(problem); + } + + const unusedDisableDirectivesToReport = options.directives.filter( + directive => + directive.type === "disable" && + !usedDisableDirectives.has(directive) && + !options.rulesToIgnore.has(directive.ruleId), + ); + + const unusedEnableDirectivesToReport = new Set( + options.directives.filter( + directive => + directive.unprocessedDirective.type === "enable" && + !options.rulesToIgnore.has(directive.ruleId), + ), + ); + + /* + * If directives has the eslint-enable directive, + * check whether the eslint-enable comment is used. + */ + if (unusedEnableDirectivesToReport.size > 0) { + for (const directive of collectUsedEnableDirectives( + options.directives, + )) { + unusedEnableDirectivesToReport.delete(directive); + } + } + + const processed = processUnusedDirectives( + unusedDisableDirectivesToReport, + sourceCode, + ).concat( + processUnusedDirectives(unusedEnableDirectivesToReport, sourceCode), + ); + const columnOffset = options.language.columnStart === 1 ? 0 : 1; + const lineOffset = options.language.lineStart === 1 ? 0 : 1; + + const unusedDirectives = processed.map( + ({ description, fix, unprocessedDirective }) => { + const { parentDirective, type, line, column } = + unprocessedDirective; + + let message; + + if (type === "enable") { + message = description + ? `Unused eslint-enable directive (no matching eslint-disable directives were found for ${description}).` + : "Unused eslint-enable directive (no matching eslint-disable directives were found)."; + } else { + message = description + ? `Unused eslint-disable directive (no problems were reported from ${description}).` + : "Unused eslint-disable directive (no problems were reported)."; + } + + const loc = sourceCode.getLoc(parentDirective.node); + + return { + ruleId: null, + message, + line: + type === "disable-next-line" + ? loc.start.line + lineOffset + : line, + column: + type === "disable-next-line" + ? loc.start.column + columnOffset + : column, + severity: + options.reportUnusedDisableDirectives === "warn" ? 1 : 2, + nodeType: null, + ...(options.disableFixes ? {} : { fix }), + }; + }, + ); + + return { problems, unusedDirectives }; } /** @@ -426,77 +463,124 @@ function applyDirectives(options) { * @returns {{ruleId: (string|null), line: number, column: number, suppressions?: {kind: string, justification: string}}[]} * An object with a list of reported problems, the suppressed of which contain the suppression information. */ -module.exports = ({ language, sourceCode, directives, disableFixes, problems, configuredRules, ruleFilter, reportUnusedDisableDirectives = "off" }) => { - const blockDirectives = directives - .filter(directive => directive.type === "disable" || directive.type === "enable") - .map(directive => Object.assign({}, directive, { unprocessedDirective: directive })) - .sort(compareLocations); - - const lineDirectives = directives.flatMap(directive => { - switch (directive.type) { - case "disable": - case "enable": - return []; - - case "disable-line": - return [ - { type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, - { type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } - ]; - - case "disable-next-line": - return [ - { type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive }, - { type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive } - ]; - - default: - throw new TypeError(`Unrecognized directive type '${directive.type}'`); - } - }).sort(compareLocations); - - // This determines a list of rules that are not being run by the given ruleFilter, if present. - const rulesToIgnore = configuredRules && ruleFilter - ? new Set(Object.keys(configuredRules).filter(ruleId => { - const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); - - // Ignore for disabled rules. - if (severity === 0) { - return false; - } - - return !ruleFilter({ severity, ruleId }); - })) - : new Set(); - - // If no ruleId is supplied that means this directive is applied to all rules, so we can't determine if it's unused if any rules are filtered out. - if (rulesToIgnore.size > 0) { - rulesToIgnore.add(null); - } - - const blockDirectivesResult = applyDirectives({ - language, - sourceCode, - problems, - directives: blockDirectives, - disableFixes, - reportUnusedDisableDirectives, - rulesToIgnore - }); - const lineDirectivesResult = applyDirectives({ - language, - sourceCode, - problems: blockDirectivesResult.problems, - directives: lineDirectives, - disableFixes, - reportUnusedDisableDirectives, - rulesToIgnore - }); - - return reportUnusedDisableDirectives !== "off" - ? lineDirectivesResult.problems - .concat(blockDirectivesResult.unusedDirectives) - .concat(lineDirectivesResult.unusedDirectives) - .sort(compareLocations) - : lineDirectivesResult.problems; +module.exports = ({ + language, + sourceCode, + directives, + disableFixes, + problems, + configuredRules, + ruleFilter, + reportUnusedDisableDirectives = "off", +}) => { + const blockDirectives = directives + .filter( + directive => + directive.type === "disable" || directive.type === "enable", + ) + .map(directive => + Object.assign({}, directive, { unprocessedDirective: directive }), + ) + .sort(compareLocations); + + const lineDirectives = directives + .flatMap(directive => { + switch (directive.type) { + case "disable": + case "enable": + return []; + + case "disable-line": + return [ + { + type: "disable", + line: directive.line, + column: 1, + ruleId: directive.ruleId, + unprocessedDirective: directive, + }, + { + type: "enable", + line: directive.line + 1, + column: 0, + ruleId: directive.ruleId, + unprocessedDirective: directive, + }, + ]; + + case "disable-next-line": + return [ + { + type: "disable", + line: directive.line + 1, + column: 1, + ruleId: directive.ruleId, + unprocessedDirective: directive, + }, + { + type: "enable", + line: directive.line + 2, + column: 0, + ruleId: directive.ruleId, + unprocessedDirective: directive, + }, + ]; + + default: + throw new TypeError( + `Unrecognized directive type '${directive.type}'`, + ); + } + }) + .sort(compareLocations); + + // This determines a list of rules that are not being run by the given ruleFilter, if present. + const rulesToIgnore = + configuredRules && ruleFilter + ? new Set( + Object.keys(configuredRules).filter(ruleId => { + const severity = ConfigOps.getRuleSeverity( + configuredRules[ruleId], + ); + + // Ignore for disabled rules. + if (severity === 0) { + return false; + } + + return !ruleFilter({ severity, ruleId }); + }), + ) + : new Set(); + + // If no ruleId is supplied that means this directive is applied to all rules, so we can't determine if it's unused if any rules are filtered out. + if (rulesToIgnore.size > 0) { + rulesToIgnore.add(null); + } + + const blockDirectivesResult = applyDirectives({ + language, + sourceCode, + problems, + directives: blockDirectives, + disableFixes, + reportUnusedDisableDirectives, + rulesToIgnore, + }); + const lineDirectivesResult = applyDirectives({ + language, + sourceCode, + problems: blockDirectivesResult.problems, + directives: lineDirectives, + disableFixes, + reportUnusedDisableDirectives, + rulesToIgnore, + }); + + return reportUnusedDisableDirectives !== "off" + ? lineDirectivesResult.problems + .concat(blockDirectivesResult.unusedDirectives) + .concat(lineDirectivesResult.unusedDirectives) + .sort(compareLocations) + : lineDirectivesResult.problems; }; diff --git a/lib/linter/code-path-analysis/code-path-analyzer.js b/lib/linter/code-path-analysis/code-path-analyzer.js index baa6e997e89e..642af5ffb39a 100644 --- a/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/lib/linter/code-path-analysis/code-path-analyzer.js @@ -10,11 +10,11 @@ //------------------------------------------------------------------------------ const assert = require("../../shared/assert"), - { breakableTypePattern } = require("../../shared/ast-utils"), - CodePath = require("./code-path"), - CodePathSegment = require("./code-path-segment"), - IdGenerator = require("./id-generator"), - debug = require("./debug-helpers"); + { breakableTypePattern } = require("../../shared/ast-utils"), + CodePath = require("./code-path"), + CodePathSegment = require("./code-path-segment"), + IdGenerator = require("./id-generator"), + debug = require("./debug-helpers"); //------------------------------------------------------------------------------ // Helpers @@ -26,7 +26,7 @@ const assert = require("../../shared/assert"), * @returns {boolean} `true` if the node is a `case` node (not `default` node). */ function isCaseNode(node) { - return Boolean(node.test); + return Boolean(node.test); } /** @@ -36,9 +36,11 @@ function isCaseNode(node) { * false if not. */ function isPropertyDefinitionValue(node) { - const parent = node.parent; + const parent = node.parent; - return parent && parent.type === "PropertyDefinition" && parent.value === node; + return ( + parent && parent.type === "PropertyDefinition" && parent.value === node + ); } /** @@ -48,7 +50,7 @@ function isPropertyDefinitionValue(node) { * @returns {boolean} `true` if the operator is "&&" or "||" or "??" */ function isHandledLogicalOperator(operator) { - return operator === "&&" || operator === "||" || operator === "??"; + return operator === "&&" || operator === "||" || operator === "??"; } /** @@ -59,7 +61,7 @@ function isHandledLogicalOperator(operator) { * @returns {boolean} `true` if the operator is "&&=" or "||=" or "??=" */ function isLogicalAssignmentOperator(operator) { - return operator === "&&=" || operator === "||=" || operator === "??="; + return operator === "&&=" || operator === "||=" || operator === "??="; } /** @@ -68,10 +70,10 @@ function isLogicalAssignmentOperator(operator) { * @returns {string|null} The label or `null`. */ function getLabel(node) { - if (node.parent.type === "LabeledStatement") { - return node.parent.label.name; - } - return null; + if (node.parent.type === "LabeledStatement") { + return node.parent.label.name; + } + return null; } /** @@ -81,25 +83,25 @@ function getLabel(node) { * @returns {boolean} `true` if the node is a test of a choice statement. */ function isForkingByTrueOrFalse(node) { - const parent = node.parent; + const parent = node.parent; - switch (parent.type) { - case "ConditionalExpression": - case "IfStatement": - case "WhileStatement": - case "DoWhileStatement": - case "ForStatement": - return parent.test === node; + switch (parent.type) { + case "ConditionalExpression": + case "IfStatement": + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + return parent.test === node; - case "LogicalExpression": - return isHandledLogicalOperator(parent.operator); + case "LogicalExpression": + return isHandledLogicalOperator(parent.operator); - case "AssignmentExpression": - return isLogicalAssignmentOperator(parent.operator); + case "AssignmentExpression": + return isLogicalAssignmentOperator(parent.operator); - default: - return false; - } + default: + return false; + } } /** @@ -113,10 +115,10 @@ function isForkingByTrueOrFalse(node) { * otherwise `undefined`. */ function getBooleanValueIfSimpleConstant(node) { - if (node.type === "Literal") { - return Boolean(node.value); - } - return void 0; + if (node.type === "Literal") { + return Boolean(node.value); + } + return void 0; } /** @@ -127,43 +129,39 @@ function getBooleanValueIfSimpleConstant(node) { * @returns {boolean} `true` if the node is a reference. */ function isIdentifierReference(node) { - const parent = node.parent; - - switch (parent.type) { - case "LabeledStatement": - case "BreakStatement": - case "ContinueStatement": - case "ArrayPattern": - case "RestElement": - case "ImportSpecifier": - case "ImportDefaultSpecifier": - case "ImportNamespaceSpecifier": - case "CatchClause": - return false; - - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - case "ClassDeclaration": - case "ClassExpression": - case "VariableDeclarator": - return parent.id !== node; - - case "Property": - case "PropertyDefinition": - case "MethodDefinition": - return ( - parent.key !== node || - parent.computed || - parent.shorthand - ); - - case "AssignmentPattern": - return parent.key !== node; - - default: - return true; - } + const parent = node.parent; + + switch (parent.type) { + case "LabeledStatement": + case "BreakStatement": + case "ContinueStatement": + case "ArrayPattern": + case "RestElement": + case "ImportSpecifier": + case "ImportDefaultSpecifier": + case "ImportNamespaceSpecifier": + case "CatchClause": + return false; + + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "ClassDeclaration": + case "ClassExpression": + case "VariableDeclarator": + return parent.id !== node; + + case "Property": + case "PropertyDefinition": + case "MethodDefinition": + return parent.key !== node || parent.computed || parent.shorthand; + + case "AssignmentPattern": + return parent.key !== node; + + default: + return true; + } } /** @@ -179,58 +177,47 @@ function isIdentifierReference(node) { * @returns {void} */ function forwardCurrentToHead(analyzer, node) { - const codePath = analyzer.codePath; - const state = CodePath.getState(codePath); - const currentSegments = state.currentSegments; - const headSegments = state.headSegments; - const end = Math.max(currentSegments.length, headSegments.length); - let i, currentSegment, headSegment; - - // Fires leaving events. - for (i = 0; i < end; ++i) { - currentSegment = currentSegments[i]; - headSegment = headSegments[i]; - - if (currentSegment !== headSegment && currentSegment) { - - const eventName = currentSegment.reachable - ? "onCodePathSegmentEnd" - : "onUnreachableCodePathSegmentEnd"; - - debug.dump(`${eventName} ${currentSegment.id}`); - - analyzer.emitter.emit( - eventName, - currentSegment, - node - ); - } - } - - // Update state. - state.currentSegments = headSegments; - - // Fires entering events. - for (i = 0; i < end; ++i) { - currentSegment = currentSegments[i]; - headSegment = headSegments[i]; - - if (currentSegment !== headSegment && headSegment) { - - const eventName = headSegment.reachable - ? "onCodePathSegmentStart" - : "onUnreachableCodePathSegmentStart"; - - debug.dump(`${eventName} ${headSegment.id}`); - CodePathSegment.markUsed(headSegment); - analyzer.emitter.emit( - eventName, - headSegment, - node - ); - } - } - + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + const currentSegments = state.currentSegments; + const headSegments = state.headSegments; + const end = Math.max(currentSegments.length, headSegments.length); + let i, currentSegment, headSegment; + + // Fires leaving events. + for (i = 0; i < end; ++i) { + currentSegment = currentSegments[i]; + headSegment = headSegments[i]; + + if (currentSegment !== headSegment && currentSegment) { + const eventName = currentSegment.reachable + ? "onCodePathSegmentEnd" + : "onUnreachableCodePathSegmentEnd"; + + debug.dump(`${eventName} ${currentSegment.id}`); + + analyzer.emitter.emit(eventName, currentSegment, node); + } + } + + // Update state. + state.currentSegments = headSegments; + + // Fires entering events. + for (i = 0; i < end; ++i) { + currentSegment = currentSegments[i]; + headSegment = headSegments[i]; + + if (currentSegment !== headSegment && headSegment) { + const eventName = headSegment.reachable + ? "onCodePathSegmentStart" + : "onUnreachableCodePathSegmentStart"; + + debug.dump(`${eventName} ${headSegment.id}`); + CodePathSegment.markUsed(headSegment); + analyzer.emitter.emit(eventName, headSegment, node); + } + } } /** @@ -241,25 +228,21 @@ function forwardCurrentToHead(analyzer, node) { * @returns {void} */ function leaveFromCurrentSegment(analyzer, node) { - const state = CodePath.getState(analyzer.codePath); - const currentSegments = state.currentSegments; + const state = CodePath.getState(analyzer.codePath); + const currentSegments = state.currentSegments; - for (let i = 0; i < currentSegments.length; ++i) { - const currentSegment = currentSegments[i]; - const eventName = currentSegment.reachable - ? "onCodePathSegmentEnd" - : "onUnreachableCodePathSegmentEnd"; + for (let i = 0; i < currentSegments.length; ++i) { + const currentSegment = currentSegments[i]; + const eventName = currentSegment.reachable + ? "onCodePathSegmentEnd" + : "onUnreachableCodePathSegmentEnd"; - debug.dump(`${eventName} ${currentSegment.id}`); + debug.dump(`${eventName} ${currentSegment.id}`); - analyzer.emitter.emit( - eventName, - currentSegment, - node - ); - } + analyzer.emitter.emit(eventName, currentSegment, node); + } - state.currentSegments = []; + state.currentSegments = []; } /** @@ -273,128 +256,129 @@ function leaveFromCurrentSegment(analyzer, node) { * @returns {void} */ function preprocess(analyzer, node) { - const codePath = analyzer.codePath; - const state = CodePath.getState(codePath); - const parent = node.parent; - - switch (parent.type) { - - // The `arguments.length == 0` case is in `postprocess` function. - case "CallExpression": - if (parent.optional === true && parent.arguments.length >= 1 && parent.arguments[0] === node) { - state.makeOptionalRight(); - } - break; - case "MemberExpression": - if (parent.optional === true && parent.property === node) { - state.makeOptionalRight(); - } - break; - - case "LogicalExpression": - if ( - parent.right === node && - isHandledLogicalOperator(parent.operator) - ) { - state.makeLogicalRight(); - } - break; - - case "AssignmentExpression": - if ( - parent.right === node && - isLogicalAssignmentOperator(parent.operator) - ) { - state.makeLogicalRight(); - } - break; - - case "ConditionalExpression": - case "IfStatement": - - /* - * Fork if this node is at `consequent`/`alternate`. - * `popForkContext()` exists at `IfStatement:exit` and - * `ConditionalExpression:exit`. - */ - if (parent.consequent === node) { - state.makeIfConsequent(); - } else if (parent.alternate === node) { - state.makeIfAlternate(); - } - break; - - case "SwitchCase": - if (parent.consequent[0] === node) { - state.makeSwitchCaseBody(false, !parent.test); - } - break; - - case "TryStatement": - if (parent.handler === node) { - state.makeCatchBlock(); - } else if (parent.finalizer === node) { - state.makeFinallyBlock(); - } - break; - - case "WhileStatement": - if (parent.test === node) { - state.makeWhileTest(getBooleanValueIfSimpleConstant(node)); - } else { - assert(parent.body === node); - state.makeWhileBody(); - } - break; - - case "DoWhileStatement": - if (parent.body === node) { - state.makeDoWhileBody(); - } else { - assert(parent.test === node); - state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node)); - } - break; - - case "ForStatement": - if (parent.test === node) { - state.makeForTest(getBooleanValueIfSimpleConstant(node)); - } else if (parent.update === node) { - state.makeForUpdate(); - } else if (parent.body === node) { - state.makeForBody(); - } - break; - - case "ForInStatement": - case "ForOfStatement": - if (parent.left === node) { - state.makeForInOfLeft(); - } else if (parent.right === node) { - state.makeForInOfRight(); - } else { - assert(parent.body === node); - state.makeForInOfBody(); - } - break; - - case "AssignmentPattern": - - /* - * Fork if this node is at `right`. - * `left` is executed always, so it uses the current path. - * `popForkContext()` exists at `AssignmentPattern:exit`. - */ - if (parent.right === node) { - state.pushForkContext(); - state.forkBypassPath(); - state.forkPath(); - } - break; - - default: - break; - } + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + const parent = node.parent; + + switch (parent.type) { + // The `arguments.length == 0` case is in `postprocess` function. + case "CallExpression": + if ( + parent.optional === true && + parent.arguments.length >= 1 && + parent.arguments[0] === node + ) { + state.makeOptionalRight(); + } + break; + case "MemberExpression": + if (parent.optional === true && parent.property === node) { + state.makeOptionalRight(); + } + break; + + case "LogicalExpression": + if ( + parent.right === node && + isHandledLogicalOperator(parent.operator) + ) { + state.makeLogicalRight(); + } + break; + + case "AssignmentExpression": + if ( + parent.right === node && + isLogicalAssignmentOperator(parent.operator) + ) { + state.makeLogicalRight(); + } + break; + + case "ConditionalExpression": + case "IfStatement": + /* + * Fork if this node is at `consequent`/`alternate`. + * `popForkContext()` exists at `IfStatement:exit` and + * `ConditionalExpression:exit`. + */ + if (parent.consequent === node) { + state.makeIfConsequent(); + } else if (parent.alternate === node) { + state.makeIfAlternate(); + } + break; + + case "SwitchCase": + if (parent.consequent[0] === node) { + state.makeSwitchCaseBody(false, !parent.test); + } + break; + + case "TryStatement": + if (parent.handler === node) { + state.makeCatchBlock(); + } else if (parent.finalizer === node) { + state.makeFinallyBlock(); + } + break; + + case "WhileStatement": + if (parent.test === node) { + state.makeWhileTest(getBooleanValueIfSimpleConstant(node)); + } else { + assert(parent.body === node); + state.makeWhileBody(); + } + break; + + case "DoWhileStatement": + if (parent.body === node) { + state.makeDoWhileBody(); + } else { + assert(parent.test === node); + state.makeDoWhileTest(getBooleanValueIfSimpleConstant(node)); + } + break; + + case "ForStatement": + if (parent.test === node) { + state.makeForTest(getBooleanValueIfSimpleConstant(node)); + } else if (parent.update === node) { + state.makeForUpdate(); + } else if (parent.body === node) { + state.makeForBody(); + } + break; + + case "ForInStatement": + case "ForOfStatement": + if (parent.left === node) { + state.makeForInOfLeft(); + } else if (parent.right === node) { + state.makeForInOfRight(); + } else { + assert(parent.body === node); + state.makeForInOfBody(); + } + break; + + case "AssignmentPattern": + /* + * Fork if this node is at `right`. + * `left` is executed always, so it uses the current path. + * `popForkContext()` exists at `AssignmentPattern:exit`. + */ + if (parent.right === node) { + state.pushForkContext(); + state.forkBypassPath(); + state.forkPath(); + } + break; + + default: + break; + } } /** @@ -404,155 +388,152 @@ function preprocess(analyzer, node) { * @returns {void} */ function processCodePathToEnter(analyzer, node) { - let codePath = analyzer.codePath; - let state = codePath && CodePath.getState(codePath); - const parent = node.parent; - - /** - * Creates a new code path and trigger the onCodePathStart event - * based on the currently selected node. - * @param {string} origin The reason the code path was started. - * @returns {void} - */ - function startCodePath(origin) { - if (codePath) { - - // Emits onCodePathSegmentStart events if updated. - forwardCurrentToHead(analyzer, node); - debug.dumpState(node, state, false); - } - - // Create the code path of this scope. - codePath = analyzer.codePath = new CodePath({ - id: analyzer.idGenerator.next(), - origin, - upper: codePath, - onLooped: analyzer.onLooped - }); - state = CodePath.getState(codePath); - - // Emits onCodePathStart events. - debug.dump(`onCodePathStart ${codePath.id}`); - analyzer.emitter.emit("onCodePathStart", codePath, node); - } - - /* - * Special case: The right side of class field initializer is considered - * to be its own function, so we need to start a new code path in this - * case. - */ - if (isPropertyDefinitionValue(node)) { - startCodePath("class-field-initializer"); - - /* - * Intentional fall through because `node` needs to also be - * processed by the code below. For example, if we have: - * - * class Foo { - * a = () => {} - * } - * - * In this case, we also need start a second code path. - */ - - } - - switch (node.type) { - case "Program": - startCodePath("program"); - break; - - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - startCodePath("function"); - break; - - case "StaticBlock": - startCodePath("class-static-block"); - break; - - case "ChainExpression": - state.pushChainContext(); - break; - case "CallExpression": - if (node.optional === true) { - state.makeOptionalNode(); - } - break; - case "MemberExpression": - if (node.optional === true) { - state.makeOptionalNode(); - } - break; - - case "LogicalExpression": - if (isHandledLogicalOperator(node.operator)) { - state.pushChoiceContext( - node.operator, - isForkingByTrueOrFalse(node) - ); - } - break; - - case "AssignmentExpression": - if (isLogicalAssignmentOperator(node.operator)) { - state.pushChoiceContext( - node.operator.slice(0, -1), // removes `=` from the end - isForkingByTrueOrFalse(node) - ); - } - break; - - case "ConditionalExpression": - case "IfStatement": - state.pushChoiceContext("test", false); - break; - - case "SwitchStatement": - state.pushSwitchContext( - node.cases.some(isCaseNode), - getLabel(node) - ); - break; - - case "TryStatement": - state.pushTryContext(Boolean(node.finalizer)); - break; - - case "SwitchCase": - - /* - * Fork if this node is after the 2st node in `cases`. - * It's similar to `else` blocks. - * The next `test` node is processed in this path. - */ - if (parent.discriminant !== node && parent.cases[0] !== node) { - state.forkPath(); - } - break; - - case "WhileStatement": - case "DoWhileStatement": - case "ForStatement": - case "ForInStatement": - case "ForOfStatement": - state.pushLoopContext(node.type, getLabel(node)); - break; - - case "LabeledStatement": - if (!breakableTypePattern.test(node.body.type)) { - state.pushBreakContext(false, node.label.name); - } - break; - - default: - break; - } - - // Emits onCodePathSegmentStart events if updated. - forwardCurrentToHead(analyzer, node); - debug.dumpState(node, state, false); + let codePath = analyzer.codePath; + let state = codePath && CodePath.getState(codePath); + const parent = node.parent; + + /** + * Creates a new code path and trigger the onCodePathStart event + * based on the currently selected node. + * @param {string} origin The reason the code path was started. + * @returns {void} + */ + function startCodePath(origin) { + if (codePath) { + // Emits onCodePathSegmentStart events if updated. + forwardCurrentToHead(analyzer, node); + debug.dumpState(node, state, false); + } + + // Create the code path of this scope. + codePath = analyzer.codePath = new CodePath({ + id: analyzer.idGenerator.next(), + origin, + upper: codePath, + onLooped: analyzer.onLooped, + }); + state = CodePath.getState(codePath); + + // Emits onCodePathStart events. + debug.dump(`onCodePathStart ${codePath.id}`); + analyzer.emitter.emit("onCodePathStart", codePath, node); + } + + /* + * Special case: The right side of class field initializer is considered + * to be its own function, so we need to start a new code path in this + * case. + */ + if (isPropertyDefinitionValue(node)) { + startCodePath("class-field-initializer"); + + /* + * Intentional fall through because `node` needs to also be + * processed by the code below. For example, if we have: + * + * class Foo { + * a = () => {} + * } + * + * In this case, we also need start a second code path. + */ + } + + switch (node.type) { + case "Program": + startCodePath("program"); + break; + + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + startCodePath("function"); + break; + + case "StaticBlock": + startCodePath("class-static-block"); + break; + + case "ChainExpression": + state.pushChainContext(); + break; + case "CallExpression": + if (node.optional === true) { + state.makeOptionalNode(); + } + break; + case "MemberExpression": + if (node.optional === true) { + state.makeOptionalNode(); + } + break; + + case "LogicalExpression": + if (isHandledLogicalOperator(node.operator)) { + state.pushChoiceContext( + node.operator, + isForkingByTrueOrFalse(node), + ); + } + break; + + case "AssignmentExpression": + if (isLogicalAssignmentOperator(node.operator)) { + state.pushChoiceContext( + node.operator.slice(0, -1), // removes `=` from the end + isForkingByTrueOrFalse(node), + ); + } + break; + + case "ConditionalExpression": + case "IfStatement": + state.pushChoiceContext("test", false); + break; + + case "SwitchStatement": + state.pushSwitchContext( + node.cases.some(isCaseNode), + getLabel(node), + ); + break; + + case "TryStatement": + state.pushTryContext(Boolean(node.finalizer)); + break; + + case "SwitchCase": + /* + * Fork if this node is after the 2st node in `cases`. + * It's similar to `else` blocks. + * The next `test` node is processed in this path. + */ + if (parent.discriminant !== node && parent.cases[0] !== node) { + state.forkPath(); + } + break; + + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + state.pushLoopContext(node.type, getLabel(node)); + break; + + case "LabeledStatement": + if (!breakableTypePattern.test(node.body.type)) { + state.pushBreakContext(false, node.label.name); + } + break; + + default: + break; + } + + // Emits onCodePathSegmentStart events if updated. + forwardCurrentToHead(analyzer, node); + debug.dumpState(node, state, false); } /** @@ -562,122 +543,120 @@ function processCodePathToEnter(analyzer, node) { * @returns {void} */ function processCodePathToExit(analyzer, node) { - - const codePath = analyzer.codePath; - const state = CodePath.getState(codePath); - let dontForward = false; - - switch (node.type) { - case "ChainExpression": - state.popChainContext(); - break; - - case "IfStatement": - case "ConditionalExpression": - state.popChoiceContext(); - break; - - case "LogicalExpression": - if (isHandledLogicalOperator(node.operator)) { - state.popChoiceContext(); - } - break; - - case "AssignmentExpression": - if (isLogicalAssignmentOperator(node.operator)) { - state.popChoiceContext(); - } - break; - - case "SwitchStatement": - state.popSwitchContext(); - break; - - case "SwitchCase": - - /* - * This is the same as the process at the 1st `consequent` node in - * `preprocess` function. - * Must do if this `consequent` is empty. - */ - if (node.consequent.length === 0) { - state.makeSwitchCaseBody(true, !node.test); - } - if (state.forkContext.reachable) { - dontForward = true; - } - break; - - case "TryStatement": - state.popTryContext(); - break; - - case "BreakStatement": - forwardCurrentToHead(analyzer, node); - state.makeBreak(node.label && node.label.name); - dontForward = true; - break; - - case "ContinueStatement": - forwardCurrentToHead(analyzer, node); - state.makeContinue(node.label && node.label.name); - dontForward = true; - break; - - case "ReturnStatement": - forwardCurrentToHead(analyzer, node); - state.makeReturn(); - dontForward = true; - break; - - case "ThrowStatement": - forwardCurrentToHead(analyzer, node); - state.makeThrow(); - dontForward = true; - break; - - case "Identifier": - if (isIdentifierReference(node)) { - state.makeFirstThrowablePathInTryBlock(); - dontForward = true; - } - break; - - case "CallExpression": - case "ImportExpression": - case "MemberExpression": - case "NewExpression": - case "YieldExpression": - state.makeFirstThrowablePathInTryBlock(); - break; - - case "WhileStatement": - case "DoWhileStatement": - case "ForStatement": - case "ForInStatement": - case "ForOfStatement": - state.popLoopContext(); - break; - - case "AssignmentPattern": - state.popForkContext(); - break; - - case "LabeledStatement": - if (!breakableTypePattern.test(node.body.type)) { - state.popBreakContext(); - } - break; - - default: - break; - } - - // Emits onCodePathSegmentStart events if updated. - if (!dontForward) { - forwardCurrentToHead(analyzer, node); - } - debug.dumpState(node, state, true); + const codePath = analyzer.codePath; + const state = CodePath.getState(codePath); + let dontForward = false; + + switch (node.type) { + case "ChainExpression": + state.popChainContext(); + break; + + case "IfStatement": + case "ConditionalExpression": + state.popChoiceContext(); + break; + + case "LogicalExpression": + if (isHandledLogicalOperator(node.operator)) { + state.popChoiceContext(); + } + break; + + case "AssignmentExpression": + if (isLogicalAssignmentOperator(node.operator)) { + state.popChoiceContext(); + } + break; + + case "SwitchStatement": + state.popSwitchContext(); + break; + + case "SwitchCase": + /* + * This is the same as the process at the 1st `consequent` node in + * `preprocess` function. + * Must do if this `consequent` is empty. + */ + if (node.consequent.length === 0) { + state.makeSwitchCaseBody(true, !node.test); + } + if (state.forkContext.reachable) { + dontForward = true; + } + break; + + case "TryStatement": + state.popTryContext(); + break; + + case "BreakStatement": + forwardCurrentToHead(analyzer, node); + state.makeBreak(node.label && node.label.name); + dontForward = true; + break; + + case "ContinueStatement": + forwardCurrentToHead(analyzer, node); + state.makeContinue(node.label && node.label.name); + dontForward = true; + break; + + case "ReturnStatement": + forwardCurrentToHead(analyzer, node); + state.makeReturn(); + dontForward = true; + break; + + case "ThrowStatement": + forwardCurrentToHead(analyzer, node); + state.makeThrow(); + dontForward = true; + break; + + case "Identifier": + if (isIdentifierReference(node)) { + state.makeFirstThrowablePathInTryBlock(); + dontForward = true; + } + break; + + case "CallExpression": + case "ImportExpression": + case "MemberExpression": + case "NewExpression": + case "YieldExpression": + state.makeFirstThrowablePathInTryBlock(); + break; + + case "WhileStatement": + case "DoWhileStatement": + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + state.popLoopContext(); + break; + + case "AssignmentPattern": + state.popForkContext(); + break; + + case "LabeledStatement": + if (!breakableTypePattern.test(node.body.type)) { + state.popBreakContext(); + } + break; + + default: + break; + } + + // Emits onCodePathSegmentStart events if updated. + if (!dontForward) { + forwardCurrentToHead(analyzer, node); + } + debug.dumpState(node, state, true); } /** @@ -687,73 +666,71 @@ function processCodePathToExit(analyzer, node) { * @returns {void} */ function postprocess(analyzer, node) { - - /** - * Ends the code path for the current node. - * @returns {void} - */ - function endCodePath() { - let codePath = analyzer.codePath; - - // Mark the current path as the final node. - CodePath.getState(codePath).makeFinal(); - - // Emits onCodePathSegmentEnd event of the current segments. - leaveFromCurrentSegment(analyzer, node); - - // Emits onCodePathEnd event of this code path. - debug.dump(`onCodePathEnd ${codePath.id}`); - analyzer.emitter.emit("onCodePathEnd", codePath, node); - debug.dumpDot(codePath); - - codePath = analyzer.codePath = analyzer.codePath.upper; - if (codePath) { - debug.dumpState(node, CodePath.getState(codePath), true); - } - - } - - switch (node.type) { - case "Program": - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - case "StaticBlock": { - endCodePath(); - break; - } - - // The `arguments.length >= 1` case is in `preprocess` function. - case "CallExpression": - if (node.optional === true && node.arguments.length === 0) { - CodePath.getState(analyzer.codePath).makeOptionalRight(); - } - break; - - default: - break; - } - - /* - * Special case: The right side of class field initializer is considered - * to be its own function, so we need to end a code path in this - * case. - * - * We need to check after the other checks in order to close the - * code paths in the correct order for code like this: - * - * - * class Foo { - * a = () => {} - * } - * - * In this case, The ArrowFunctionExpression code path is closed first - * and then we need to close the code path for the PropertyDefinition - * value. - */ - if (isPropertyDefinitionValue(node)) { - endCodePath(); - } + /** + * Ends the code path for the current node. + * @returns {void} + */ + function endCodePath() { + let codePath = analyzer.codePath; + + // Mark the current path as the final node. + CodePath.getState(codePath).makeFinal(); + + // Emits onCodePathSegmentEnd event of the current segments. + leaveFromCurrentSegment(analyzer, node); + + // Emits onCodePathEnd event of this code path. + debug.dump(`onCodePathEnd ${codePath.id}`); + analyzer.emitter.emit("onCodePathEnd", codePath, node); + debug.dumpDot(codePath); + + codePath = analyzer.codePath = analyzer.codePath.upper; + if (codePath) { + debug.dumpState(node, CodePath.getState(codePath), true); + } + } + + switch (node.type) { + case "Program": + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + case "StaticBlock": { + endCodePath(); + break; + } + + // The `arguments.length >= 1` case is in `preprocess` function. + case "CallExpression": + if (node.optional === true && node.arguments.length === 0) { + CodePath.getState(analyzer.codePath).makeOptionalRight(); + } + break; + + default: + break; + } + + /* + * Special case: The right side of class field initializer is considered + * to be its own function, so we need to end a code path in this + * case. + * + * We need to check after the other checks in order to close the + * code paths in the correct order for code like this: + * + * + * class Foo { + * a = () => {} + * } + * + * In this case, The ArrowFunctionExpression code path is closed first + * and then we need to close the code path for the PropertyDefinition + * value. + */ + if (isPropertyDefinitionValue(node)) { + endCodePath(); + } } //------------------------------------------------------------------------------ @@ -765,87 +742,88 @@ function postprocess(analyzer, node) { * This class implements the EventGenerator interface. */ class CodePathAnalyzer { - - /** - * @param {EventGenerator} eventGenerator An event generator to wrap. - */ - constructor(eventGenerator) { - this.original = eventGenerator; - this.emitter = eventGenerator.emitter; - this.codePath = null; - this.idGenerator = new IdGenerator("s"); - this.currentNode = null; - this.onLooped = this.onLooped.bind(this); - } - - /** - * Does the process to enter a given AST node. - * This updates state of analysis and calls `enterNode` of the wrapped. - * @param {ASTNode} node A node which is entering. - * @returns {void} - */ - enterNode(node) { - this.currentNode = node; - - // Updates the code path due to node's position in its parent node. - if (node.parent) { - preprocess(this, node); - } - - /* - * Updates the code path. - * And emits onCodePathStart/onCodePathSegmentStart events. - */ - processCodePathToEnter(this, node); - - // Emits node events. - this.original.enterNode(node); - - this.currentNode = null; - } - - /** - * Does the process to leave a given AST node. - * This updates state of analysis and calls `leaveNode` of the wrapped. - * @param {ASTNode} node A node which is leaving. - * @returns {void} - */ - leaveNode(node) { - this.currentNode = node; - - /* - * Updates the code path. - * And emits onCodePathStart/onCodePathSegmentStart events. - */ - processCodePathToExit(this, node); - - // Emits node events. - this.original.leaveNode(node); - - // Emits the last onCodePathStart/onCodePathSegmentStart events. - postprocess(this, node); - - this.currentNode = null; - } - - /** - * This is called on a code path looped. - * Then this raises a looped event. - * @param {CodePathSegment} fromSegment A segment of prev. - * @param {CodePathSegment} toSegment A segment of next. - * @returns {void} - */ - onLooped(fromSegment, toSegment) { - if (fromSegment.reachable && toSegment.reachable) { - debug.dump(`onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`); - this.emitter.emit( - "onCodePathSegmentLoop", - fromSegment, - toSegment, - this.currentNode - ); - } - } + /** + * @param {EventGenerator} eventGenerator An event generator to wrap. + */ + constructor(eventGenerator) { + this.original = eventGenerator; + this.emitter = eventGenerator.emitter; + this.codePath = null; + this.idGenerator = new IdGenerator("s"); + this.currentNode = null; + this.onLooped = this.onLooped.bind(this); + } + + /** + * Does the process to enter a given AST node. + * This updates state of analysis and calls `enterNode` of the wrapped. + * @param {ASTNode} node A node which is entering. + * @returns {void} + */ + enterNode(node) { + this.currentNode = node; + + // Updates the code path due to node's position in its parent node. + if (node.parent) { + preprocess(this, node); + } + + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ + processCodePathToEnter(this, node); + + // Emits node events. + this.original.enterNode(node); + + this.currentNode = null; + } + + /** + * Does the process to leave a given AST node. + * This updates state of analysis and calls `leaveNode` of the wrapped. + * @param {ASTNode} node A node which is leaving. + * @returns {void} + */ + leaveNode(node) { + this.currentNode = node; + + /* + * Updates the code path. + * And emits onCodePathStart/onCodePathSegmentStart events. + */ + processCodePathToExit(this, node); + + // Emits node events. + this.original.leaveNode(node); + + // Emits the last onCodePathStart/onCodePathSegmentStart events. + postprocess(this, node); + + this.currentNode = null; + } + + /** + * This is called on a code path looped. + * Then this raises a looped event. + * @param {CodePathSegment} fromSegment A segment of prev. + * @param {CodePathSegment} toSegment A segment of next. + * @returns {void} + */ + onLooped(fromSegment, toSegment) { + if (fromSegment.reachable && toSegment.reachable) { + debug.dump( + `onCodePathSegmentLoop ${fromSegment.id} -> ${toSegment.id}`, + ); + this.emitter.emit( + "onCodePathSegmentLoop", + fromSegment, + toSegment, + this.currentNode, + ); + } + } } module.exports = CodePathAnalyzer; diff --git a/lib/linter/code-path-analysis/code-path-segment.js b/lib/linter/code-path-analysis/code-path-segment.js index 3b8dbb41be64..fa4950185749 100644 --- a/lib/linter/code-path-analysis/code-path-segment.js +++ b/lib/linter/code-path-analysis/code-path-segment.js @@ -21,7 +21,7 @@ const debug = require("./debug-helpers"); * @returns {boolean} `true` if the segment is reachable. */ function isReachable(segment) { - return segment.reachable; + return segment.reachable; } //------------------------------------------------------------------------------ @@ -43,221 +43,220 @@ function isReachable(segment) { * that this new segment follows. */ class CodePathSegment { - - /** - * Creates a new instance. - * @param {string} id An identifier. - * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. - * This array includes unreachable segments. - * @param {boolean} reachable A flag which shows this is reachable. - */ - constructor(id, allPrevSegments, reachable) { - - /** - * The identifier of this code path. - * Rules use it to store additional information of each rule. - * @type {string} - */ - this.id = id; - - /** - * An array of the next reachable segments. - * @type {CodePathSegment[]} - */ - this.nextSegments = []; - - /** - * An array of the previous reachable segments. - * @type {CodePathSegment[]} - */ - this.prevSegments = allPrevSegments.filter(isReachable); - - /** - * An array of all next segments including reachable and unreachable. - * @type {CodePathSegment[]} - */ - this.allNextSegments = []; - - /** - * An array of all previous segments including reachable and unreachable. - * @type {CodePathSegment[]} - */ - this.allPrevSegments = allPrevSegments; - - /** - * A flag which shows this is reachable. - * @type {boolean} - */ - this.reachable = reachable; - - // Internal data. - Object.defineProperty(this, "internal", { - value: { - - // determines if the segment has been attached to the code path - used: false, - - // array of previous segments coming from the end of a loop - loopedPrevSegments: [] - } - }); - - /* c8 ignore start */ - if (debug.enabled) { - this.internal.nodes = []; - }/* c8 ignore stop */ - } - - /** - * Checks a given previous segment is coming from the end of a loop. - * @param {CodePathSegment} segment A previous segment to check. - * @returns {boolean} `true` if the segment is coming from the end of a loop. - */ - isLoopedPrevSegment(segment) { - return this.internal.loopedPrevSegments.includes(segment); - } - - /** - * Creates the root segment. - * @param {string} id An identifier. - * @returns {CodePathSegment} The created segment. - */ - static newRoot(id) { - return new CodePathSegment(id, [], true); - } - - /** - * Creates a new segment and appends it after the given segments. - * @param {string} id An identifier. - * @param {CodePathSegment[]} allPrevSegments An array of the previous segments - * to append to. - * @returns {CodePathSegment} The created segment. - */ - static newNext(id, allPrevSegments) { - return new CodePathSegment( - id, - CodePathSegment.flattenUnusedSegments(allPrevSegments), - allPrevSegments.some(isReachable) - ); - } - - /** - * Creates an unreachable segment and appends it after the given segments. - * @param {string} id An identifier. - * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. - * @returns {CodePathSegment} The created segment. - */ - static newUnreachable(id, allPrevSegments) { - const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false); - - /* - * In `if (a) return a; foo();` case, the unreachable segment preceded by - * the return statement is not used but must not be removed. - */ - CodePathSegment.markUsed(segment); - - return segment; - } - - /** - * Creates a segment that follows given segments. - * This factory method does not connect with `allPrevSegments`. - * But this inherits `reachable` flag. - * @param {string} id An identifier. - * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. - * @returns {CodePathSegment} The created segment. - */ - static newDisconnected(id, allPrevSegments) { - return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); - } - - /** - * Marks a given segment as used. - * - * And this function registers the segment into the previous segments as a next. - * @param {CodePathSegment} segment A segment to mark. - * @returns {void} - */ - static markUsed(segment) { - if (segment.internal.used) { - return; - } - segment.internal.used = true; - - let i; - - if (segment.reachable) { - - /* - * If the segment is reachable, then it's officially part of the - * code path. This loops through all previous segments to update - * their list of next segments. Because the segment is reachable, - * it's added to both `nextSegments` and `allNextSegments`. - */ - for (i = 0; i < segment.allPrevSegments.length; ++i) { - const prevSegment = segment.allPrevSegments[i]; - - prevSegment.allNextSegments.push(segment); - prevSegment.nextSegments.push(segment); - } - } else { - - /* - * If the segment is not reachable, then it's not officially part of the - * code path. This loops through all previous segments to update - * their list of next segments. Because the segment is not reachable, - * it's added only to `allNextSegments`. - */ - for (i = 0; i < segment.allPrevSegments.length; ++i) { - segment.allPrevSegments[i].allNextSegments.push(segment); - } - } - } - - /** - * Marks a previous segment as looped. - * @param {CodePathSegment} segment A segment. - * @param {CodePathSegment} prevSegment A previous segment to mark. - * @returns {void} - */ - static markPrevSegmentAsLooped(segment, prevSegment) { - segment.internal.loopedPrevSegments.push(prevSegment); - } - - /** - * Creates a new array based on an array of segments. If any segment in the - * array is unused, then it is replaced by all of its previous segments. - * All used segments are returned as-is without replacement. - * @param {CodePathSegment[]} segments The array of segments to flatten. - * @returns {CodePathSegment[]} The flattened array. - */ - static flattenUnusedSegments(segments) { - const done = new Set(); - - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; - - // Ignores duplicated. - if (done.has(segment)) { - continue; - } - - // Use previous segments if unused. - if (!segment.internal.used) { - for (let j = 0; j < segment.allPrevSegments.length; ++j) { - const prevSegment = segment.allPrevSegments[j]; - - if (!done.has(prevSegment)) { - done.add(prevSegment); - } - } - } else { - done.add(segment); - } - } - - return [...done]; - } + /** + * Creates a new instance. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * This array includes unreachable segments. + * @param {boolean} reachable A flag which shows this is reachable. + */ + constructor(id, allPrevSegments, reachable) { + /** + * The identifier of this code path. + * Rules use it to store additional information of each rule. + * @type {string} + */ + this.id = id; + + /** + * An array of the next reachable segments. + * @type {CodePathSegment[]} + */ + this.nextSegments = []; + + /** + * An array of the previous reachable segments. + * @type {CodePathSegment[]} + */ + this.prevSegments = allPrevSegments.filter(isReachable); + + /** + * An array of all next segments including reachable and unreachable. + * @type {CodePathSegment[]} + */ + this.allNextSegments = []; + + /** + * An array of all previous segments including reachable and unreachable. + * @type {CodePathSegment[]} + */ + this.allPrevSegments = allPrevSegments; + + /** + * A flag which shows this is reachable. + * @type {boolean} + */ + this.reachable = reachable; + + // Internal data. + Object.defineProperty(this, "internal", { + value: { + // determines if the segment has been attached to the code path + used: false, + + // array of previous segments coming from the end of a loop + loopedPrevSegments: [], + }, + }); + + /* c8 ignore start */ + if (debug.enabled) { + this.internal.nodes = []; + } /* c8 ignore stop */ + } + + /** + * Checks a given previous segment is coming from the end of a loop. + * @param {CodePathSegment} segment A previous segment to check. + * @returns {boolean} `true` if the segment is coming from the end of a loop. + */ + isLoopedPrevSegment(segment) { + return this.internal.loopedPrevSegments.includes(segment); + } + + /** + * Creates the root segment. + * @param {string} id An identifier. + * @returns {CodePathSegment} The created segment. + */ + static newRoot(id) { + return new CodePathSegment(id, [], true); + } + + /** + * Creates a new segment and appends it after the given segments. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments + * to append to. + * @returns {CodePathSegment} The created segment. + */ + static newNext(id, allPrevSegments) { + return new CodePathSegment( + id, + CodePathSegment.flattenUnusedSegments(allPrevSegments), + allPrevSegments.some(isReachable), + ); + } + + /** + * Creates an unreachable segment and appends it after the given segments. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @returns {CodePathSegment} The created segment. + */ + static newUnreachable(id, allPrevSegments) { + const segment = new CodePathSegment( + id, + CodePathSegment.flattenUnusedSegments(allPrevSegments), + false, + ); + + /* + * In `if (a) return a; foo();` case, the unreachable segment preceded by + * the return statement is not used but must not be removed. + */ + CodePathSegment.markUsed(segment); + + return segment; + } + + /** + * Creates a segment that follows given segments. + * This factory method does not connect with `allPrevSegments`. + * But this inherits `reachable` flag. + * @param {string} id An identifier. + * @param {CodePathSegment[]} allPrevSegments An array of the previous segments. + * @returns {CodePathSegment} The created segment. + */ + static newDisconnected(id, allPrevSegments) { + return new CodePathSegment(id, [], allPrevSegments.some(isReachable)); + } + + /** + * Marks a given segment as used. + * + * And this function registers the segment into the previous segments as a next. + * @param {CodePathSegment} segment A segment to mark. + * @returns {void} + */ + static markUsed(segment) { + if (segment.internal.used) { + return; + } + segment.internal.used = true; + + let i; + + if (segment.reachable) { + /* + * If the segment is reachable, then it's officially part of the + * code path. This loops through all previous segments to update + * their list of next segments. Because the segment is reachable, + * it's added to both `nextSegments` and `allNextSegments`. + */ + for (i = 0; i < segment.allPrevSegments.length; ++i) { + const prevSegment = segment.allPrevSegments[i]; + + prevSegment.allNextSegments.push(segment); + prevSegment.nextSegments.push(segment); + } + } else { + /* + * If the segment is not reachable, then it's not officially part of the + * code path. This loops through all previous segments to update + * their list of next segments. Because the segment is not reachable, + * it's added only to `allNextSegments`. + */ + for (i = 0; i < segment.allPrevSegments.length; ++i) { + segment.allPrevSegments[i].allNextSegments.push(segment); + } + } + } + + /** + * Marks a previous segment as looped. + * @param {CodePathSegment} segment A segment. + * @param {CodePathSegment} prevSegment A previous segment to mark. + * @returns {void} + */ + static markPrevSegmentAsLooped(segment, prevSegment) { + segment.internal.loopedPrevSegments.push(prevSegment); + } + + /** + * Creates a new array based on an array of segments. If any segment in the + * array is unused, then it is replaced by all of its previous segments. + * All used segments are returned as-is without replacement. + * @param {CodePathSegment[]} segments The array of segments to flatten. + * @returns {CodePathSegment[]} The flattened array. + */ + static flattenUnusedSegments(segments) { + const done = new Set(); + + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + // Ignores duplicated. + if (done.has(segment)) { + continue; + } + + // Use previous segments if unused. + if (!segment.internal.used) { + for (let j = 0; j < segment.allPrevSegments.length; ++j) { + const prevSegment = segment.allPrevSegments[j]; + + if (!done.has(prevSegment)) { + done.add(prevSegment); + } + } + } else { + done.add(segment); + } + } + + return [...done]; + } } module.exports = CodePathSegment; diff --git a/lib/linter/code-path-analysis/code-path-state.js b/lib/linter/code-path-analysis/code-path-state.js index 2b0dc2bfca04..a3d7fda1520c 100644 --- a/lib/linter/code-path-analysis/code-path-state.js +++ b/lib/linter/code-path-analysis/code-path-state.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const CodePathSegment = require("./code-path-segment"), - ForkContext = require("./fork-context"); + ForkContext = require("./fork-context"); //----------------------------------------------------------------------------- // Contexts @@ -35,68 +35,63 @@ const CodePathSegment = require("./code-path-segment"), * that `break` without a label is invalid. */ class BreakContext { - - /** - * Creates a new instance. - * @param {BreakContext} upperContext The previous `BreakContext`. - * @param {boolean} breakable Indicates if we are inside a statement where - * `break` without a label will exit the statement. - * @param {string|null} label The label for the statement. - * @param {ForkContext} forkContext The current fork context. - */ - constructor(upperContext, breakable, label, forkContext) { - - /** - * The previous `BreakContext` - * @type {BreakContext} - */ - this.upper = upperContext; - - /** - * Indicates if we are inside a statement where `break` without a label - * will exit the statement. - * @type {boolean} - */ - this.breakable = breakable; - - /** - * The label associated with the statement. - * @type {string|null} - */ - this.label = label; - - /** - * The fork context for the `break`. - * @type {ForkContext} - */ - this.brokenForkContext = ForkContext.newEmpty(forkContext); - } + /** + * Creates a new instance. + * @param {BreakContext} upperContext The previous `BreakContext`. + * @param {boolean} breakable Indicates if we are inside a statement where + * `break` without a label will exit the statement. + * @param {string|null} label The label for the statement. + * @param {ForkContext} forkContext The current fork context. + */ + constructor(upperContext, breakable, label, forkContext) { + /** + * The previous `BreakContext` + * @type {BreakContext} + */ + this.upper = upperContext; + + /** + * Indicates if we are inside a statement where `break` without a label + * will exit the statement. + * @type {boolean} + */ + this.breakable = breakable; + + /** + * The label associated with the statement. + * @type {string|null} + */ + this.label = label; + + /** + * The fork context for the `break`. + * @type {ForkContext} + */ + this.brokenForkContext = ForkContext.newEmpty(forkContext); + } } /** * Represents the context for `ChainExpression` nodes. */ class ChainContext { - - /** - * Creates a new instance. - * @param {ChainContext} upperContext The previous `ChainContext`. - */ - constructor(upperContext) { - - /** - * The previous `ChainContext` - * @type {ChainContext} - */ - this.upper = upperContext; - - /** - * The number of choice contexts inside of the `ChainContext`. - * @type {number} - */ - this.choiceContextCount = 0; - - } + /** + * Creates a new instance. + * @param {ChainContext} upperContext The previous `ChainContext`. + */ + constructor(upperContext) { + /** + * The previous `ChainContext` + * @type {ChainContext} + */ + this.upper = upperContext; + + /** + * The number of choice contexts inside of the `ChainContext`. + * @type {number} + */ + this.choiceContextCount = 0; + } } /** @@ -124,249 +119,241 @@ class ChainContext { * we fork to the right expression (tracked by `nullishForkContext`). */ class ChoiceContext { - - /** - * Creates a new instance. - * @param {ChoiceContext} upperContext The previous `ChoiceContext`. - * @param {string} kind The kind of choice. If it's a logical or assignment expression, this - * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or - * conditional expression, this is `"test"`; otherwise, this is `"loop"`. - * @param {boolean} isForkingAsResult Indicates if the result of the choice - * creates a fork. - * @param {ForkContext} forkContext The containing `ForkContext`. - */ - constructor(upperContext, kind, isForkingAsResult, forkContext) { - - /** - * The previous `ChoiceContext` - * @type {ChoiceContext} - */ - this.upper = upperContext; - - /** - * The kind of choice. If it's a logical or assignment expression, this - * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or - * conditional expression, this is `"test"`; otherwise, this is `"loop"`. - * @type {string} - */ - this.kind = kind; - - /** - * Indicates if the result of the choice forks the code path. - * @type {boolean} - */ - this.isForkingAsResult = isForkingAsResult; - - /** - * The fork context for the `true` path of the choice. - * @type {ForkContext} - */ - this.trueForkContext = ForkContext.newEmpty(forkContext); - - /** - * The fork context for the `false` path of the choice. - * @type {ForkContext} - */ - this.falseForkContext = ForkContext.newEmpty(forkContext); - - /** - * The fork context for when the choice result is `null` or `undefined`. - * @type {ForkContext} - */ - this.nullishForkContext = ForkContext.newEmpty(forkContext); - - /** - * Indicates if any of `trueForkContext`, `falseForkContext`, or - * `nullishForkContext` have been updated with segments from a child context. - * @type {boolean} - */ - this.processed = false; - } - + /** + * Creates a new instance. + * @param {ChoiceContext} upperContext The previous `ChoiceContext`. + * @param {string} kind The kind of choice. If it's a logical or assignment expression, this + * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or + * conditional expression, this is `"test"`; otherwise, this is `"loop"`. + * @param {boolean} isForkingAsResult Indicates if the result of the choice + * creates a fork. + * @param {ForkContext} forkContext The containing `ForkContext`. + */ + constructor(upperContext, kind, isForkingAsResult, forkContext) { + /** + * The previous `ChoiceContext` + * @type {ChoiceContext} + */ + this.upper = upperContext; + + /** + * The kind of choice. If it's a logical or assignment expression, this + * is `"&&"` or `"||"` or `"??"`; if it's an `if` statement or + * conditional expression, this is `"test"`; otherwise, this is `"loop"`. + * @type {string} + */ + this.kind = kind; + + /** + * Indicates if the result of the choice forks the code path. + * @type {boolean} + */ + this.isForkingAsResult = isForkingAsResult; + + /** + * The fork context for the `true` path of the choice. + * @type {ForkContext} + */ + this.trueForkContext = ForkContext.newEmpty(forkContext); + + /** + * The fork context for the `false` path of the choice. + * @type {ForkContext} + */ + this.falseForkContext = ForkContext.newEmpty(forkContext); + + /** + * The fork context for when the choice result is `null` or `undefined`. + * @type {ForkContext} + */ + this.nullishForkContext = ForkContext.newEmpty(forkContext); + + /** + * Indicates if any of `trueForkContext`, `falseForkContext`, or + * `nullishForkContext` have been updated with segments from a child context. + * @type {boolean} + */ + this.processed = false; + } } /** * Base class for all loop contexts. */ class LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string} type The AST node's `type` for the loop. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - */ - constructor(upperContext, type, label, breakContext) { - - /** - * The previous `LoopContext`. - * @type {LoopContext} - */ - this.upper = upperContext; - - /** - * The AST node's `type` for the loop. - * @type {string} - */ - this.type = type; - - /** - * The label for the loop from an enclosing `LabeledStatement`. - * @type {string|null} - */ - this.label = label; - - /** - * The fork context for when `break` is encountered. - * @type {ForkContext} - */ - this.brokenForkContext = breakContext.brokenForkContext; - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string} type The AST node's `type` for the loop. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, type, label, breakContext) { + /** + * The previous `LoopContext`. + * @type {LoopContext} + */ + this.upper = upperContext; + + /** + * The AST node's `type` for the loop. + * @type {string} + */ + this.type = type; + + /** + * The label for the loop from an enclosing `LabeledStatement`. + * @type {string|null} + */ + this.label = label; + + /** + * The fork context for when `break` is encountered. + * @type {ForkContext} + */ + this.brokenForkContext = breakContext.brokenForkContext; + } } /** * Represents the context for a `while` loop. */ class WhileLoopContext extends LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - */ - constructor(upperContext, label, breakContext) { - super(upperContext, "WhileStatement", label, breakContext); - - /** - * The hardcoded literal boolean test condition for - * the loop. Used to catch infinite or skipped loops. - * @type {boolean|undefined} - */ - this.test = void 0; - - /** - * The segments representing the test condition where `continue` will - * jump to. The test condition will typically have just one segment but - * it's possible for there to be more than one. - * @type {Array|null} - */ - this.continueDestSegments = null; - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "WhileStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The segments representing the test condition where `continue` will + * jump to. The test condition will typically have just one segment but + * it's possible for there to be more than one. + * @type {Array|null} + */ + this.continueDestSegments = null; + } } /** * Represents the context for a `do-while` loop. */ class DoWhileLoopContext extends LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - * @param {ForkContext} forkContext The enclosing fork context. - */ - constructor(upperContext, label, breakContext, forkContext) { - super(upperContext, "DoWhileStatement", label, breakContext); - - /** - * The hardcoded literal boolean test condition for - * the loop. Used to catch infinite or skipped loops. - * @type {boolean|undefined} - */ - this.test = void 0; - - /** - * The segments at the start of the loop body. This is the only loop - * where the test comes at the end, so the first iteration always - * happens and we need a reference to the first statements. - * @type {Array|null} - */ - this.entrySegments = null; - - /** - * The fork context to follow when a `continue` is found. - * @type {ForkContext} - */ - this.continueForkContext = ForkContext.newEmpty(forkContext); - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + * @param {ForkContext} forkContext The enclosing fork context. + */ + constructor(upperContext, label, breakContext, forkContext) { + super(upperContext, "DoWhileStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The segments at the start of the loop body. This is the only loop + * where the test comes at the end, so the first iteration always + * happens and we need a reference to the first statements. + * @type {Array|null} + */ + this.entrySegments = null; + + /** + * The fork context to follow when a `continue` is found. + * @type {ForkContext} + */ + this.continueForkContext = ForkContext.newEmpty(forkContext); + } } /** * Represents the context for a `for` loop. */ class ForLoopContext extends LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - */ - constructor(upperContext, label, breakContext) { - super(upperContext, "ForStatement", label, breakContext); - - /** - * The hardcoded literal boolean test condition for - * the loop. Used to catch infinite or skipped loops. - * @type {boolean|undefined} - */ - this.test = void 0; - - /** - * The end of the init expression. This may change during the lifetime - * of the instance as we traverse the loop because some loops don't have - * an init expression. - * @type {Array|null} - */ - this.endOfInitSegments = null; - - /** - * The start of the test expression. This may change during the lifetime - * of the instance as we traverse the loop because some loops don't have - * a test expression. - * @type {Array|null} - */ - this.testSegments = null; - - /** - * The end of the test expression. This may change during the lifetime - * of the instance as we traverse the loop because some loops don't have - * a test expression. - * @type {Array|null} - */ - this.endOfTestSegments = null; - - /** - * The start of the update expression. This may change during the lifetime - * of the instance as we traverse the loop because some loops don't have - * an update expression. - * @type {Array|null} - */ - this.updateSegments = null; - - /** - * The end of the update expresion. This may change during the lifetime - * of the instance as we traverse the loop because some loops don't have - * an update expression. - * @type {Array|null} - */ - this.endOfUpdateSegments = null; - - /** - * The segments representing the test condition where `continue` will - * jump to. The test condition will typically have just one segment but - * it's possible for there to be more than one. This may change during the - * lifetime of the instance as we traverse the loop because some loops - * don't have an update expression. When there is an update expression, this - * will end up pointing to that expression; otherwise it will end up pointing - * to the test expression. - * @type {Array|null} - */ - this.continueDestSegments = null; - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForStatement", label, breakContext); + + /** + * The hardcoded literal boolean test condition for + * the loop. Used to catch infinite or skipped loops. + * @type {boolean|undefined} + */ + this.test = void 0; + + /** + * The end of the init expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an init expression. + * @type {Array|null} + */ + this.endOfInitSegments = null; + + /** + * The start of the test expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * a test expression. + * @type {Array|null} + */ + this.testSegments = null; + + /** + * The end of the test expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * a test expression. + * @type {Array|null} + */ + this.endOfTestSegments = null; + + /** + * The start of the update expression. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an update expression. + * @type {Array|null} + */ + this.updateSegments = null; + + /** + * The end of the update expresion. This may change during the lifetime + * of the instance as we traverse the loop because some loops don't have + * an update expression. + * @type {Array|null} + */ + this.endOfUpdateSegments = null; + + /** + * The segments representing the test condition where `continue` will + * jump to. The test condition will typically have just one segment but + * it's possible for there to be more than one. This may change during the + * lifetime of the instance as we traverse the loop because some loops + * don't have an update expression. When there is an update expression, this + * will end up pointing to that expression; otherwise it will end up pointing + * to the test expression. + * @type {Array|null} + */ + this.continueDestSegments = null; + } } /** @@ -379,114 +366,112 @@ class ForLoopContext extends LoopContextBase { * example, in `for (var x in y)`, the right is `y`. */ class ForInLoopContext extends LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - */ - constructor(upperContext, label, breakContext) { - super(upperContext, "ForInStatement", label, breakContext); - - /** - * The segments that came immediately before the start of the loop. - * This allows you to traverse backwards out of the loop into the - * surrounding code. This is necessary to evaluate the right expression - * correctly, as it must be evaluated in the same way as the left - * expression, but the pointer to these segments would otherwise be - * lost if not stored on the instance. Once the right expression has - * been evaluated, this property is no longer used. - * @type {Array|null} - */ - this.prevSegments = null; - - /** - * Segments representing the start of everything to the left of the - * `in` keyword. This can be used to move forward towards - * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are - * effectively the head and tail of a doubly-linked list. - * @type {Array|null} - */ - this.leftSegments = null; - - /** - * Segments representing the end of everything to the left of the - * `in` keyword. This can be used to move backward towards `leftSegments`. - * `leftSegments` and `endOfLeftSegments` are effectively the head - * and tail of a doubly-linked list. - * @type {Array|null} - */ - this.endOfLeftSegments = null; - - /** - * The segments representing the left expression where `continue` will - * jump to. In `for-in` loops, `continue` must always re-execute the - * left expression each time through the loop. This contains the same - * segments as `leftSegments`, but is duplicated here so each loop - * context has the same property pointing to where `continue` should - * end up. - * @type {Array|null} - */ - this.continueDestSegments = null; - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForInStatement", label, breakContext); + + /** + * The segments that came immediately before the start of the loop. + * This allows you to traverse backwards out of the loop into the + * surrounding code. This is necessary to evaluate the right expression + * correctly, as it must be evaluated in the same way as the left + * expression, but the pointer to these segments would otherwise be + * lost if not stored on the instance. Once the right expression has + * been evaluated, this property is no longer used. + * @type {Array|null} + */ + this.prevSegments = null; + + /** + * Segments representing the start of everything to the left of the + * `in` keyword. This can be used to move forward towards + * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are + * effectively the head and tail of a doubly-linked list. + * @type {Array|null} + */ + this.leftSegments = null; + + /** + * Segments representing the end of everything to the left of the + * `in` keyword. This can be used to move backward towards `leftSegments`. + * `leftSegments` and `endOfLeftSegments` are effectively the head + * and tail of a doubly-linked list. + * @type {Array|null} + */ + this.endOfLeftSegments = null; + + /** + * The segments representing the left expression where `continue` will + * jump to. In `for-in` loops, `continue` must always re-execute the + * left expression each time through the loop. This contains the same + * segments as `leftSegments`, but is duplicated here so each loop + * context has the same property pointing to where `continue` should + * end up. + * @type {Array|null} + */ + this.continueDestSegments = null; + } } /** * Represents the context for a `for-of` loop. */ class ForOfLoopContext extends LoopContextBase { - - /** - * Creates a new instance. - * @param {LoopContext|null} upperContext The previous `LoopContext`. - * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. - * @param {BreakContext} breakContext The context for breaking the loop. - */ - constructor(upperContext, label, breakContext) { - super(upperContext, "ForOfStatement", label, breakContext); - - /** - * The segments that came immediately before the start of the loop. - * This allows you to traverse backwards out of the loop into the - * surrounding code. This is necessary to evaluate the right expression - * correctly, as it must be evaluated in the same way as the left - * expression, but the pointer to these segments would otherwise be - * lost if not stored on the instance. Once the right expression has - * been evaluated, this property is no longer used. - * @type {Array|null} - */ - this.prevSegments = null; - - /** - * Segments representing the start of everything to the left of the - * `of` keyword. This can be used to move forward towards - * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are - * effectively the head and tail of a doubly-linked list. - * @type {Array|null} - */ - this.leftSegments = null; - - /** - * Segments representing the end of everything to the left of the - * `of` keyword. This can be used to move backward towards `leftSegments`. - * `leftSegments` and `endOfLeftSegments` are effectively the head - * and tail of a doubly-linked list. - * @type {Array|null} - */ - this.endOfLeftSegments = null; - - /** - * The segments representing the left expression where `continue` will - * jump to. In `for-in` loops, `continue` must always re-execute the - * left expression each time through the loop. This contains the same - * segments as `leftSegments`, but is duplicated here so each loop - * context has the same property pointing to where `continue` should - * end up. - * @type {Array|null} - */ - this.continueDestSegments = null; - } + /** + * Creates a new instance. + * @param {LoopContext|null} upperContext The previous `LoopContext`. + * @param {string|null} label The label for the loop from an enclosing `LabeledStatement`. + * @param {BreakContext} breakContext The context for breaking the loop. + */ + constructor(upperContext, label, breakContext) { + super(upperContext, "ForOfStatement", label, breakContext); + + /** + * The segments that came immediately before the start of the loop. + * This allows you to traverse backwards out of the loop into the + * surrounding code. This is necessary to evaluate the right expression + * correctly, as it must be evaluated in the same way as the left + * expression, but the pointer to these segments would otherwise be + * lost if not stored on the instance. Once the right expression has + * been evaluated, this property is no longer used. + * @type {Array|null} + */ + this.prevSegments = null; + + /** + * Segments representing the start of everything to the left of the + * `of` keyword. This can be used to move forward towards + * `endOfLeftSegments`. `leftSegments` and `endOfLeftSegments` are + * effectively the head and tail of a doubly-linked list. + * @type {Array|null} + */ + this.leftSegments = null; + + /** + * Segments representing the end of everything to the left of the + * `of` keyword. This can be used to move backward towards `leftSegments`. + * `leftSegments` and `endOfLeftSegments` are effectively the head + * and tail of a doubly-linked list. + * @type {Array|null} + */ + this.endOfLeftSegments = null; + + /** + * The segments representing the left expression where `continue` will + * jump to. In `for-in` loops, `continue` must always re-execute the + * left expression each time through the loop. This contains the same + * segments as `leftSegments`, but is duplicated here so each loop + * context has the same property pointing to where `continue` should + * end up. + * @type {Array|null} + */ + this.continueDestSegments = null; + } } /** @@ -498,127 +483,123 @@ class ForOfLoopContext extends LoopContextBase { * Represents the context for a `switch` statement. */ class SwitchContext { - - /** - * Creates a new instance. - * @param {SwitchContext} upperContext The previous context. - * @param {boolean} hasCase Indicates if there is at least one `case` statement. - * `default` doesn't count. - */ - constructor(upperContext, hasCase) { - - /** - * The previous context. - * @type {SwitchContext} - */ - this.upper = upperContext; - - /** - * Indicates if there is at least one `case` statement. `default` doesn't count. - * @type {boolean} - */ - this.hasCase = hasCase; - - /** - * The `default` keyword. - * @type {Array|null} - */ - this.defaultSegments = null; - - /** - * The default case body starting segments. - * @type {Array|null} - */ - this.defaultBodySegments = null; - - /** - * Indicates if a `default` case and is empty exists. - * @type {boolean} - */ - this.foundEmptyDefault = false; - - /** - * Indicates that a `default` exists and is the last case. - * @type {boolean} - */ - this.lastIsDefault = false; - - /** - * The number of fork contexts created. This is equivalent to the - * number of `case` statements plus a `default` statement (if present). - * @type {number} - */ - this.forkCount = 0; - } + /** + * Creates a new instance. + * @param {SwitchContext} upperContext The previous context. + * @param {boolean} hasCase Indicates if there is at least one `case` statement. + * `default` doesn't count. + */ + constructor(upperContext, hasCase) { + /** + * The previous context. + * @type {SwitchContext} + */ + this.upper = upperContext; + + /** + * Indicates if there is at least one `case` statement. `default` doesn't count. + * @type {boolean} + */ + this.hasCase = hasCase; + + /** + * The `default` keyword. + * @type {Array|null} + */ + this.defaultSegments = null; + + /** + * The default case body starting segments. + * @type {Array|null} + */ + this.defaultBodySegments = null; + + /** + * Indicates if a `default` case and is empty exists. + * @type {boolean} + */ + this.foundEmptyDefault = false; + + /** + * Indicates that a `default` exists and is the last case. + * @type {boolean} + */ + this.lastIsDefault = false; + + /** + * The number of fork contexts created. This is equivalent to the + * number of `case` statements plus a `default` statement (if present). + * @type {number} + */ + this.forkCount = 0; + } } /** * Represents the context for a `try` statement. */ class TryContext { - - /** - * Creates a new instance. - * @param {TryContext} upperContext The previous context. - * @param {boolean} hasFinalizer Indicates if the `try` statement has a - * `finally` block. - * @param {ForkContext} forkContext The enclosing fork context. - */ - constructor(upperContext, hasFinalizer, forkContext) { - - /** - * The previous context. - * @type {TryContext} - */ - this.upper = upperContext; - - /** - * Indicates if the `try` statement has a `finally` block. - * @type {boolean} - */ - this.hasFinalizer = hasFinalizer; - - /** - * Tracks the traversal position inside of the `try` statement. This is - * used to help determine the context necessary to create paths because - * a `try` statement may or may not have `catch` or `finally` blocks, - * and code paths behave differently in those blocks. - * @type {"try"|"catch"|"finally"} - */ - this.position = "try"; - - /** - * If the `try` statement has a `finally` block, this affects how a - * `return` statement behaves in the `try` block. Without `finally`, - * `return` behaves as usual and doesn't require a fork; with `finally`, - * `return` forks into the `finally` block, so we need a fork context - * to track it. - * @type {ForkContext|null} - */ - this.returnedForkContext = hasFinalizer - ? ForkContext.newEmpty(forkContext) - : null; - - /** - * When a `throw` occurs inside of a `try` block, the code path forks - * into the `catch` or `finally` blocks, and this fork context tracks - * that path. - * @type {ForkContext} - */ - this.thrownForkContext = ForkContext.newEmpty(forkContext); - - /** - * Indicates if the last segment in the `try` block is reachable. - * @type {boolean} - */ - this.lastOfTryIsReachable = false; - - /** - * Indicates if the last segment in the `catch` block is reachable. - * @type {boolean} - */ - this.lastOfCatchIsReachable = false; - } + /** + * Creates a new instance. + * @param {TryContext} upperContext The previous context. + * @param {boolean} hasFinalizer Indicates if the `try` statement has a + * `finally` block. + * @param {ForkContext} forkContext The enclosing fork context. + */ + constructor(upperContext, hasFinalizer, forkContext) { + /** + * The previous context. + * @type {TryContext} + */ + this.upper = upperContext; + + /** + * Indicates if the `try` statement has a `finally` block. + * @type {boolean} + */ + this.hasFinalizer = hasFinalizer; + + /** + * Tracks the traversal position inside of the `try` statement. This is + * used to help determine the context necessary to create paths because + * a `try` statement may or may not have `catch` or `finally` blocks, + * and code paths behave differently in those blocks. + * @type {"try"|"catch"|"finally"} + */ + this.position = "try"; + + /** + * If the `try` statement has a `finally` block, this affects how a + * `return` statement behaves in the `try` block. Without `finally`, + * `return` behaves as usual and doesn't require a fork; with `finally`, + * `return` forks into the `finally` block, so we need a fork context + * to track it. + * @type {ForkContext|null} + */ + this.returnedForkContext = hasFinalizer + ? ForkContext.newEmpty(forkContext) + : null; + + /** + * When a `throw` occurs inside of a `try` block, the code path forks + * into the `catch` or `finally` blocks, and this fork context tracks + * that path. + * @type {ForkContext} + */ + this.thrownForkContext = ForkContext.newEmpty(forkContext); + + /** + * Indicates if the last segment in the `try` block is reachable. + * @type {boolean} + */ + this.lastOfTryIsReachable = false; + + /** + * Indicates if the last segment in the `catch` block is reachable. + * @type {boolean} + */ + this.lastOfCatchIsReachable = false; + } } //------------------------------------------------------------------------------ @@ -638,14 +619,14 @@ class TryContext { * @returns {void} */ function addToReturnedOrThrown(dest, others, all, segments) { - for (let i = 0; i < segments.length; ++i) { - const segment = segments[i]; - - dest.push(segment); - if (!others.includes(segment)) { - all.push(segment); - } - } + for (let i = 0; i < segments.length; ++i) { + const segment = segments[i]; + + dest.push(segment); + if (!others.includes(segment)) { + all.push(segment); + } + } } /** @@ -655,21 +636,21 @@ function addToReturnedOrThrown(dest, others, all, segments) { * @returns {LoopContext} A loop-context for a `continue` statement. */ function getContinueContext(state, label) { - if (!label) { - return state.loopContext; - } + if (!label) { + return state.loopContext; + } - let context = state.loopContext; + let context = state.loopContext; - while (context) { - if (context.label === label) { - return context; - } - context = context.upper; - } + while (context) { + if (context.label === label) { + return context; + } + context = context.upper; + } - /* c8 ignore next */ - return null; + /* c8 ignore next */ + return null; } /** @@ -679,17 +660,17 @@ function getContinueContext(state, label) { * @returns {BreakContext} A context for a `break` statement. */ function getBreakContext(state, label) { - let context = state.breakContext; + let context = state.breakContext; - while (context) { - if (label ? context.label === label : context.breakable) { - return context; - } - context = context.upper; - } + while (context) { + if (label ? context.label === label : context.breakable) { + return context; + } + context = context.upper; + } - /* c8 ignore next */ - return null; + /* c8 ignore next */ + return null; } /** @@ -700,16 +681,16 @@ function getBreakContext(state, label) { * @returns {TryContext|CodePathState} A context for a `return` statement. */ function getReturnContext(state) { - let context = state.tryContext; + let context = state.tryContext; - while (context) { - if (context.hasFinalizer && context.position !== "finally") { - return context; - } - context = context.upper; - } + while (context) { + if (context.hasFinalizer && context.position !== "finally") { + return context; + } + context = context.upper; + } - return state; + return state; } /** @@ -721,18 +702,19 @@ function getReturnContext(state) { * @returns {TryContext|CodePathState} A context for a `throw` statement. */ function getThrowContext(state) { - let context = state.tryContext; - - while (context) { - if (context.position === "try" || - (context.hasFinalizer && context.position === "catch") - ) { - return context; - } - context = context.upper; - } - - return state; + let context = state.tryContext; + + while (context) { + if ( + context.position === "try" || + (context.hasFinalizer && context.position === "catch") + ) { + return context; + } + context = context.upper; + } + + return state; } /** @@ -742,7 +724,7 @@ function getThrowContext(state) { * @returns {void} */ function removeFromArray(elements, value) { - elements.splice(elements.indexOf(value), 1); + elements.splice(elements.indexOf(value), 1); } /** @@ -756,15 +738,15 @@ function removeFromArray(elements, value) { * @returns {void} */ function disconnectSegments(prevSegments, nextSegments) { - for (let i = 0; i < prevSegments.length; ++i) { - const prevSegment = prevSegments[i]; - const nextSegment = nextSegments[i]; - - removeFromArray(prevSegment.nextSegments, nextSegment); - removeFromArray(prevSegment.allNextSegments, nextSegment); - removeFromArray(nextSegment.prevSegments, prevSegment); - removeFromArray(nextSegment.allPrevSegments, prevSegment); - } + for (let i = 0; i < prevSegments.length; ++i) { + const prevSegment = prevSegments[i]; + const nextSegment = nextSegments[i]; + + removeFromArray(prevSegment.nextSegments, nextSegment); + removeFromArray(prevSegment.allNextSegments, nextSegment); + removeFromArray(nextSegment.prevSegments, prevSegment); + removeFromArray(nextSegment.allPrevSegments, prevSegment); + } } /** @@ -776,58 +758,60 @@ function disconnectSegments(prevSegments, nextSegments) { * @returns {void} */ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { - - const fromSegments = CodePathSegment.flattenUnusedSegments(unflattenedFromSegments); - const toSegments = CodePathSegment.flattenUnusedSegments(unflattenedToSegments); - const end = Math.min(fromSegments.length, toSegments.length); - - /* - * This loop effectively updates a doubly-linked list between two collections - * of segments making sure that segments in the same array indices are - * combined to create a path. - */ - for (let i = 0; i < end; ++i) { - - // get the segments in matching array indices - const fromSegment = fromSegments[i]; - const toSegment = toSegments[i]; - - /* - * If the destination segment is reachable, then create a path from the - * source segment to the destination segment. - */ - if (toSegment.reachable) { - fromSegment.nextSegments.push(toSegment); - } - - /* - * If the source segment is reachable, then create a path from the - * destination segment back to the source segment. - */ - if (fromSegment.reachable) { - toSegment.prevSegments.push(fromSegment); - } - - /* - * Also update the arrays that don't care if the segments are reachable - * or not. This should always happen regardless of anything else. - */ - fromSegment.allNextSegments.push(toSegment); - toSegment.allPrevSegments.push(fromSegment); - - /* - * If the destination segment has at least two previous segments in its - * path then that means there was one previous segment before this iteration - * of the loop was executed. So, we need to mark the source segment as - * looped. - */ - if (toSegment.allPrevSegments.length >= 2) { - CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment); - } - - // let the code path analyzer know that there's been a loop created - state.notifyLooped(fromSegment, toSegment); - } + const fromSegments = CodePathSegment.flattenUnusedSegments( + unflattenedFromSegments, + ); + const toSegments = CodePathSegment.flattenUnusedSegments( + unflattenedToSegments, + ); + const end = Math.min(fromSegments.length, toSegments.length); + + /* + * This loop effectively updates a doubly-linked list between two collections + * of segments making sure that segments in the same array indices are + * combined to create a path. + */ + for (let i = 0; i < end; ++i) { + // get the segments in matching array indices + const fromSegment = fromSegments[i]; + const toSegment = toSegments[i]; + + /* + * If the destination segment is reachable, then create a path from the + * source segment to the destination segment. + */ + if (toSegment.reachable) { + fromSegment.nextSegments.push(toSegment); + } + + /* + * If the source segment is reachable, then create a path from the + * destination segment back to the source segment. + */ + if (fromSegment.reachable) { + toSegment.prevSegments.push(fromSegment); + } + + /* + * Also update the arrays that don't care if the segments are reachable + * or not. This should always happen regardless of anything else. + */ + fromSegment.allNextSegments.push(toSegment); + toSegment.allPrevSegments.push(fromSegment); + + /* + * If the destination segment has at least two previous segments in its + * path then that means there was one previous segment before this iteration + * of the loop was executed. So, we need to mark the source segment as + * looped. + */ + if (toSegment.allPrevSegments.length >= 2) { + CodePathSegment.markPrevSegmentAsLooped(toSegment, fromSegment); + } + + // let the code path analyzer know that there's been a loop created + state.notifyLooped(fromSegment, toSegment); + } } /** @@ -841,28 +825,27 @@ function makeLooped(state, unflattenedFromSegments, unflattenedToSegments) { * @returns {void} */ function finalizeTestSegmentsOfFor(context, choiceContext, head) { - - /* - * If this choice context doesn't already contain paths from a - * child context, then add the current head to each potential path. - */ - if (!choiceContext.processed) { - choiceContext.trueForkContext.add(head); - choiceContext.falseForkContext.add(head); - choiceContext.nullishForkContext.add(head); - } - - /* - * If the test condition isn't a hardcoded truthy value, then `break` - * must follow the same path as if the test condition is false. To represent - * that, we append the path for when the loop test is false (represented by - * `falseForkContext`) to the `brokenForkContext`. - */ - if (context.test !== true) { - context.brokenForkContext.addAll(choiceContext.falseForkContext); - } - - context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1); + /* + * If this choice context doesn't already contain paths from a + * child context, then add the current head to each potential path. + */ + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(head); + choiceContext.falseForkContext.add(head); + choiceContext.nullishForkContext.add(head); + } + + /* + * If the test condition isn't a hardcoded truthy value, then `break` + * must follow the same path as if the test condition is false. To represent + * that, we append the path for when the loop test is false (represented by + * `falseForkContext`) to the `brokenForkContext`. + */ + if (context.test !== true) { + context.brokenForkContext.addAll(choiceContext.falseForkContext); + } + + context.endOfTestSegments = choiceContext.trueForkContext.makeNext(0, -1); } //------------------------------------------------------------------------------ @@ -873,1476 +856,1515 @@ function finalizeTestSegmentsOfFor(context, choiceContext, head) { * A class which manages state to analyze code paths. */ class CodePathState { - - /** - * Creates a new instance. - * @param {IdGenerator} idGenerator An id generator to generate id for code - * path segments. - * @param {Function} onLooped A callback function to notify looping. - */ - constructor(idGenerator, onLooped) { - - /** - * The ID generator to use when creating new segments. - * @type {IdGenerator} - */ - this.idGenerator = idGenerator; - - /** - * A callback function to call when there is a loop. - * @type {Function} - */ - this.notifyLooped = onLooped; - - /** - * The root fork context for this state. - * @type {ForkContext} - */ - this.forkContext = ForkContext.newRoot(idGenerator); - - /** - * Context for logical expressions, conditional expressions, `if` statements, - * and loops. - * @type {ChoiceContext} - */ - this.choiceContext = null; - - /** - * Context for `switch` statements. - * @type {SwitchContext} - */ - this.switchContext = null; - - /** - * Context for `try` statements. - * @type {TryContext} - */ - this.tryContext = null; - - /** - * Context for loop statements. - * @type {LoopContext} - */ - this.loopContext = null; - - /** - * Context for `break` statements. - * @type {BreakContext} - */ - this.breakContext = null; - - /** - * Context for `ChainExpression` nodes. - * @type {ChainContext} - */ - this.chainContext = null; - - /** - * An array that tracks the current segments in the state. The array - * starts empty and segments are added with each `onCodePathSegmentStart` - * event and removed with each `onCodePathSegmentEnd` event. Effectively, - * this is tracking the code path segment traversal as the state is - * modified. - * @type {Array} - */ - this.currentSegments = []; - - /** - * Tracks the starting segment for this path. This value never changes. - * @type {CodePathSegment} - */ - this.initialSegment = this.forkContext.head[0]; - - /** - * The final segments of the code path which are either `return` or `throw`. - * This is a union of the segments in `returnedForkContext` and `thrownForkContext`. - * @type {Array} - */ - this.finalSegments = []; - - /** - * The final segments of the code path which are `return`. These - * segments are also contained in `finalSegments`. - * @type {Array} - */ - this.returnedForkContext = []; - - /** - * The final segments of the code path which are `throw`. These - * segments are also contained in `finalSegments`. - * @type {Array} - */ - this.thrownForkContext = []; - - /* - * We add an `add` method so that these look more like fork contexts and - * can be used interchangeably when a fork context is needed to add more - * segments to a path. - * - * Ultimately, we want anything added to `returned` or `thrown` to also - * be added to `final`. We only add reachable and used segments to these - * arrays. - */ - const final = this.finalSegments; - const returned = this.returnedForkContext; - const thrown = this.thrownForkContext; - - returned.add = addToReturnedOrThrown.bind(null, returned, thrown, final); - thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); - } - - /** - * A passthrough property exposing the current pointer as part of the API. - * @type {CodePathSegment[]} - */ - get headSegments() { - return this.forkContext.head; - } - - /** - * The parent forking context. - * This is used for the root of new forks. - * @type {ForkContext} - */ - get parentForkContext() { - const current = this.forkContext; - - return current && current.upper; - } - - /** - * Creates and stacks new forking context. - * @param {boolean} forkLeavingPath A flag which shows being in a - * "finally" block. - * @returns {ForkContext} The created context. - */ - pushForkContext(forkLeavingPath) { - this.forkContext = ForkContext.newEmpty( - this.forkContext, - forkLeavingPath - ); - - return this.forkContext; - } - - /** - * Pops and merges the last forking context. - * @returns {ForkContext} The last context. - */ - popForkContext() { - const lastContext = this.forkContext; - - this.forkContext = lastContext.upper; - this.forkContext.replaceHead(lastContext.makeNext(0, -1)); - - return lastContext; - } - - /** - * Creates a new path. - * @returns {void} - */ - forkPath() { - this.forkContext.add(this.parentForkContext.makeNext(-1, -1)); - } - - /** - * Creates a bypass path. - * This is used for such as IfStatement which does not have "else" chunk. - * @returns {void} - */ - forkBypassPath() { - this.forkContext.add(this.parentForkContext.head); - } - - //-------------------------------------------------------------------------- - // ConditionalExpression, LogicalExpression, IfStatement - //-------------------------------------------------------------------------- - - /** - * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only), - * IfStatement, WhileStatement, DoWhileStatement, or ForStatement. - * - * LogicalExpressions have cases that it goes different paths between the - * `true` case and the `false` case. - * - * For Example: - * - * if (a || b) { - * foo(); - * } else { - * bar(); - * } - * - * In this case, `b` is evaluated always in the code path of the `else` - * block, but it's not so in the code path of the `if` block. - * So there are 3 paths. - * - * a -> foo(); - * a -> b -> foo(); - * a -> b -> bar(); - * @param {string} kind A kind string. - * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`. - * If it's IfStatement's or ConditionalExpression's, this is `"test"`. - * Otherwise, this is `"loop"`. - * @param {boolean} isForkingAsResult Indicates if the result of the choice - * creates a fork. - * @returns {void} - */ - pushChoiceContext(kind, isForkingAsResult) { - this.choiceContext = new ChoiceContext(this.choiceContext, kind, isForkingAsResult, this.forkContext); - } - - /** - * Pops the last choice context and finalizes it. - * This is called upon leaving a node that represents a choice. - * @throws {Error} (Unreachable.) - * @returns {ChoiceContext} The popped context. - */ - popChoiceContext() { - const poppedChoiceContext = this.choiceContext; - const forkContext = this.forkContext; - const head = forkContext.head; - - this.choiceContext = poppedChoiceContext.upper; - - switch (poppedChoiceContext.kind) { - case "&&": - case "||": - case "??": - - /* - * The `head` are the path of the right-hand operand. - * If we haven't previously added segments from child contexts, - * then we add these segments to all possible forks. - */ - if (!poppedChoiceContext.processed) { - poppedChoiceContext.trueForkContext.add(head); - poppedChoiceContext.falseForkContext.add(head); - poppedChoiceContext.nullishForkContext.add(head); - } - - /* - * If this context is the left (test) expression for another choice - * context, such as `a || b` in the expression `a || b || c`, - * then we take the segments for this context and move them up - * to the parent context. - */ - if (poppedChoiceContext.isForkingAsResult) { - const parentContext = this.choiceContext; - - parentContext.trueForkContext.addAll(poppedChoiceContext.trueForkContext); - parentContext.falseForkContext.addAll(poppedChoiceContext.falseForkContext); - parentContext.nullishForkContext.addAll(poppedChoiceContext.nullishForkContext); - parentContext.processed = true; - - // Exit early so we don't collapse all paths into one. - return poppedChoiceContext; - } - - break; - - case "test": - if (!poppedChoiceContext.processed) { - - /* - * The head segments are the path of the `if` block here. - * Updates the `true` path with the end of the `if` block. - */ - poppedChoiceContext.trueForkContext.clear(); - poppedChoiceContext.trueForkContext.add(head); - } else { - - /* - * The head segments are the path of the `else` block here. - * Updates the `false` path with the end of the `else` - * block. - */ - poppedChoiceContext.falseForkContext.clear(); - poppedChoiceContext.falseForkContext.add(head); - } - - break; - - case "loop": - - /* - * Loops are addressed in `popLoopContext()` so just return - * the context without modification. - */ - return poppedChoiceContext; - - /* c8 ignore next */ - default: - throw new Error("unreachable"); - } - - /* - * Merge the true path with the false path to create a single path. - */ - const combinedForkContext = poppedChoiceContext.trueForkContext; - - combinedForkContext.addAll(poppedChoiceContext.falseForkContext); - forkContext.replaceHead(combinedForkContext.makeNext(0, -1)); - - return poppedChoiceContext; - } - - /** - * Creates a code path segment to represent right-hand operand of a logical - * expression. - * This is called in the preprocessing phase when entering a node. - * @throws {Error} (Unreachable.) - * @returns {void} - */ - makeLogicalRight() { - const currentChoiceContext = this.choiceContext; - const forkContext = this.forkContext; - - if (currentChoiceContext.processed) { - - /* - * This context was already assigned segments from a child - * choice context. In this case, we are concerned only about - * the path that does not short-circuit and so ends up on the - * right-hand operand of the logical expression. - */ - let prevForkContext; - - switch (currentChoiceContext.kind) { - case "&&": // if true then go to the right-hand side. - prevForkContext = currentChoiceContext.trueForkContext; - break; - case "||": // if false then go to the right-hand side. - prevForkContext = currentChoiceContext.falseForkContext; - break; - case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext. - prevForkContext = currentChoiceContext.nullishForkContext; - break; - default: - throw new Error("unreachable"); - } - - /* - * Create the segment for the right-hand operand of the logical expression - * and adjust the fork context pointer to point there. The right-hand segment - * is added at the end of all segments in `prevForkContext`. - */ - forkContext.replaceHead(prevForkContext.makeNext(0, -1)); - - /* - * We no longer need this list of segments. - * - * Reset `processed` because we've removed the segments from the child - * choice context. This allows `popChoiceContext()` to continue adding - * segments later. - */ - prevForkContext.clear(); - currentChoiceContext.processed = false; - - } else { - - /* - * This choice context was not assigned segments from a child - * choice context, which means that it's a terminal logical - * expression. - * - * `head` is the segments for the left-hand operand of the - * logical expression. - * - * Each of the fork contexts below are empty at this point. We choose - * the path(s) that will short-circuit and add the segment for the - * left-hand operand to it. Ultimately, this will be the only segment - * in that path due to the short-circuting, so we are just seeding - * these paths to start. - */ - switch (currentChoiceContext.kind) { - case "&&": - - /* - * In most contexts, when a && expression evaluates to false, - * it short circuits, so we need to account for that by setting - * the `falseForkContext` to the left operand. - * - * When a && expression is the left-hand operand for a ?? - * expression, such as `(a && b) ?? c`, a nullish value will - * also short-circuit in a different way than a false value, - * so we also set the `nullishForkContext` to the left operand. - * This path is only used with a ?? expression and is thrown - * away for any other type of logical expression, so it's safe - * to always add. - */ - currentChoiceContext.falseForkContext.add(forkContext.head); - currentChoiceContext.nullishForkContext.add(forkContext.head); - break; - case "||": // the true path can short-circuit. - currentChoiceContext.trueForkContext.add(forkContext.head); - break; - case "??": // both can short-circuit. - currentChoiceContext.trueForkContext.add(forkContext.head); - currentChoiceContext.falseForkContext.add(forkContext.head); - break; - default: - throw new Error("unreachable"); - } - - /* - * Create the segment for the right-hand operand of the logical expression - * and adjust the fork context pointer to point there. - */ - forkContext.replaceHead(forkContext.makeNext(-1, -1)); - } - } - - /** - * Makes a code path segment of the `if` block. - * @returns {void} - */ - makeIfConsequent() { - const context = this.choiceContext; - const forkContext = this.forkContext; - - /* - * If any result were not transferred from child contexts, - * this sets the head segments to both cases. - * The head segments are the path of the test expression. - */ - if (!context.processed) { - context.trueForkContext.add(forkContext.head); - context.falseForkContext.add(forkContext.head); - context.nullishForkContext.add(forkContext.head); - } - - context.processed = false; - - // Creates new path from the `true` case. - forkContext.replaceHead( - context.trueForkContext.makeNext(0, -1) - ); - } - - /** - * Makes a code path segment of the `else` block. - * @returns {void} - */ - makeIfAlternate() { - const context = this.choiceContext; - const forkContext = this.forkContext; - - /* - * The head segments are the path of the `if` block. - * Updates the `true` path with the end of the `if` block. - */ - context.trueForkContext.clear(); - context.trueForkContext.add(forkContext.head); - context.processed = true; - - // Creates new path from the `false` case. - forkContext.replaceHead( - context.falseForkContext.makeNext(0, -1) - ); - } - - //-------------------------------------------------------------------------- - // ChainExpression - //-------------------------------------------------------------------------- - - /** - * Pushes a new `ChainExpression` context to the stack. This method is - * called when entering a `ChainExpression` node. A chain context is used to - * count forking in the optional chain then merge them on the exiting from the - * `ChainExpression` node. - * @returns {void} - */ - pushChainContext() { - this.chainContext = new ChainContext(this.chainContext); - } - - /** - * Pop a `ChainExpression` context from the stack. This method is called on - * exiting from each `ChainExpression` node. This merges all forks of the - * last optional chaining. - * @returns {void} - */ - popChainContext() { - const context = this.chainContext; - - this.chainContext = context.upper; - - // pop all choice contexts of this. - for (let i = context.choiceContextCount; i > 0; --i) { - this.popChoiceContext(); - } - } - - /** - * Create a choice context for optional access. - * This method is called on entering to each `(Call|Member)Expression[optional=true]` node. - * This creates a choice context as similar to `LogicalExpression[operator="??"]` node. - * @returns {void} - */ - makeOptionalNode() { - if (this.chainContext) { - this.chainContext.choiceContextCount += 1; - this.pushChoiceContext("??", false); - } - } - - /** - * Create a fork. - * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node. - * @returns {void} - */ - makeOptionalRight() { - if (this.chainContext) { - this.makeLogicalRight(); - } - } - - //-------------------------------------------------------------------------- - // SwitchStatement - //-------------------------------------------------------------------------- - - /** - * Creates a context object of SwitchStatement and stacks it. - * @param {boolean} hasCase `true` if the switch statement has one or more - * case parts. - * @param {string|null} label The label text. - * @returns {void} - */ - pushSwitchContext(hasCase, label) { - this.switchContext = new SwitchContext(this.switchContext, hasCase); - this.pushBreakContext(true, label); - } - - /** - * Pops the last context of SwitchStatement and finalizes it. - * - * - Disposes all forking stack for `case` and `default`. - * - Creates the next code path segment from `context.brokenForkContext`. - * - If the last `SwitchCase` node is not a `default` part, creates a path - * to the `default` body. - * @returns {void} - */ - popSwitchContext() { - const context = this.switchContext; - - this.switchContext = context.upper; - - const forkContext = this.forkContext; - const brokenForkContext = this.popBreakContext().brokenForkContext; - - if (context.forkCount === 0) { - - /* - * When there is only one `default` chunk and there is one or more - * `break` statements, even if forks are nothing, it needs to merge - * those. - */ - if (!brokenForkContext.empty) { - brokenForkContext.add(forkContext.makeNext(-1, -1)); - forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); - } - - return; - } - - const lastSegments = forkContext.head; - - this.forkBypassPath(); - const lastCaseSegments = forkContext.head; - - /* - * `brokenForkContext` is used to make the next segment. - * It must add the last segment into `brokenForkContext`. - */ - brokenForkContext.add(lastSegments); - - /* - * Any value that doesn't match a `case` test should flow to the default - * case. That happens normally when the default case is last in the `switch`, - * but if it's not, we need to rewire some of the paths to be correct. - */ - if (!context.lastIsDefault) { - if (context.defaultBodySegments) { - - /* - * There is a non-empty default case, so remove the path from the `default` - * label to its body for an accurate representation. - */ - disconnectSegments(context.defaultSegments, context.defaultBodySegments); - - /* - * Connect the path from the last non-default case to the body of the - * default case. - */ - makeLooped(this, lastCaseSegments, context.defaultBodySegments); - - } else { - - /* - * There is no default case, so we treat this as if the last case - * had a `break` in it. - */ - brokenForkContext.add(lastCaseSegments); - } - } - - // Traverse up to the original fork context for the `switch` statement - for (let i = 0; i < context.forkCount; ++i) { - this.forkContext = this.forkContext.upper; - } - - /* - * Creates a path from all `brokenForkContext` paths. - * This is a path after `switch` statement. - */ - this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); - } - - /** - * Makes a code path segment for a `SwitchCase` node. - * @param {boolean} isCaseBodyEmpty `true` if the body is empty. - * @param {boolean} isDefaultCase `true` if the body is the default case. - * @returns {void} - */ - makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) { - const context = this.switchContext; - - if (!context.hasCase) { - return; - } - - /* - * Merge forks. - * The parent fork context has two segments. - * Those are from the current `case` and the body of the previous case. - */ - const parentForkContext = this.forkContext; - const forkContext = this.pushForkContext(); - - forkContext.add(parentForkContext.makeNext(0, -1)); - - /* - * Add information about the default case. - * - * The purpose of this is to identify the starting segments for the - * default case to make sure there is a path there. - */ - if (isDefaultCase) { - - /* - * This is the default case in the `switch`. - * - * We first save the current pointer as `defaultSegments` to point - * to the `default` keyword. - */ - context.defaultSegments = parentForkContext.head; - - /* - * If the body of the case is empty then we just set - * `foundEmptyDefault` to true; otherwise, we save a reference - * to the current pointer as `defaultBodySegments`. - */ - if (isCaseBodyEmpty) { - context.foundEmptyDefault = true; - } else { - context.defaultBodySegments = forkContext.head; - } - - } else { - - /* - * This is not the default case in the `switch`. - * - * If it's not empty and there is already an empty default case found, - * that means the default case actually comes before this case, - * and that it will fall through to this case. So, we can now - * ignore the previous default case (reset `foundEmptyDefault` to false) - * and set `defaultBodySegments` to the current segments because this is - * effectively the new default case. - */ - if (!isCaseBodyEmpty && context.foundEmptyDefault) { - context.foundEmptyDefault = false; - context.defaultBodySegments = forkContext.head; - } - } - - // keep track if the default case ends up last - context.lastIsDefault = isDefaultCase; - context.forkCount += 1; - } - - //-------------------------------------------------------------------------- - // TryStatement - //-------------------------------------------------------------------------- - - /** - * Creates a context object of TryStatement and stacks it. - * @param {boolean} hasFinalizer `true` if the try statement has a - * `finally` block. - * @returns {void} - */ - pushTryContext(hasFinalizer) { - this.tryContext = new TryContext(this.tryContext, hasFinalizer, this.forkContext); - } - - /** - * Pops the last context of TryStatement and finalizes it. - * @returns {void} - */ - popTryContext() { - const context = this.tryContext; - - this.tryContext = context.upper; - - /* - * If we're inside the `catch` block, that means there is no `finally`, - * so we can process the `try` and `catch` blocks the simple way and - * merge their two paths. - */ - if (context.position === "catch") { - this.popForkContext(); - return; - } - - /* - * The following process is executed only when there is a `finally` - * block. - */ - - const originalReturnedForkContext = context.returnedForkContext; - const originalThrownForkContext = context.thrownForkContext; - - // no `return` or `throw` in `try` or `catch` so there's nothing left to do - if (originalReturnedForkContext.empty && originalThrownForkContext.empty) { - return; - } - - /* - * The following process is executed only when there is a `finally` - * block and there was a `return` or `throw` in the `try` or `catch` - * blocks. - */ - - // Separate head to normal paths and leaving paths. - const headSegments = this.forkContext.head; - - this.forkContext = this.forkContext.upper; - const normalSegments = headSegments.slice(0, headSegments.length / 2 | 0); - const leavingSegments = headSegments.slice(headSegments.length / 2 | 0); - - // Forwards the leaving path to upper contexts. - if (!originalReturnedForkContext.empty) { - getReturnContext(this).returnedForkContext.add(leavingSegments); - } - if (!originalThrownForkContext.empty) { - getThrowContext(this).thrownForkContext.add(leavingSegments); - } - - // Sets the normal path as the next. - this.forkContext.replaceHead(normalSegments); - - /* - * If both paths of the `try` block and the `catch` block are - * unreachable, the next path becomes unreachable as well. - */ - if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) { - this.forkContext.makeUnreachable(); - } - } - - /** - * Makes a code path segment for a `catch` block. - * @returns {void} - */ - makeCatchBlock() { - const context = this.tryContext; - const forkContext = this.forkContext; - const originalThrownForkContext = context.thrownForkContext; - - /* - * We are now in a catch block so we need to update the context - * with that information. This includes creating a new fork - * context in case we encounter any `throw` statements here. - */ - context.position = "catch"; - context.thrownForkContext = ForkContext.newEmpty(forkContext); - context.lastOfTryIsReachable = forkContext.reachable; - - // Merge the thrown paths from the `try` and `catch` blocks - originalThrownForkContext.add(forkContext.head); - const thrownSegments = originalThrownForkContext.makeNext(0, -1); - - // Fork to a bypass and the merged thrown path. - this.pushForkContext(); - this.forkBypassPath(); - this.forkContext.add(thrownSegments); - } - - /** - * Makes a code path segment for a `finally` block. - * - * In the `finally` block, parallel paths are created. The parallel paths - * are used as leaving-paths. The leaving-paths are paths from `return` - * statements and `throw` statements in a `try` block or a `catch` block. - * @returns {void} - */ - makeFinallyBlock() { - const context = this.tryContext; - let forkContext = this.forkContext; - const originalReturnedForkContext = context.returnedForkContext; - const originalThrownForContext = context.thrownForkContext; - const headOfLeavingSegments = forkContext.head; - - // Update state. - if (context.position === "catch") { - - // Merges two paths from the `try` block and `catch` block. - this.popForkContext(); - forkContext = this.forkContext; - - context.lastOfCatchIsReachable = forkContext.reachable; - } else { - context.lastOfTryIsReachable = forkContext.reachable; - } - - - context.position = "finally"; - - /* - * If there was no `return` or `throw` in either the `try` or `catch` - * blocks, then there's no further code paths to create for `finally`. - */ - if (originalReturnedForkContext.empty && originalThrownForContext.empty) { - - // This path does not leave. - return; - } - - /* - * Create a parallel segment from merging returned and thrown. - * This segment will leave at the end of this `finally` block. - */ - const segments = forkContext.makeNext(-1, -1); - - for (let i = 0; i < forkContext.count; ++i) { - const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; - - for (let j = 0; j < originalReturnedForkContext.segmentsList.length; ++j) { - prevSegsOfLeavingSegment.push(originalReturnedForkContext.segmentsList[j][i]); - } - for (let j = 0; j < originalThrownForContext.segmentsList.length; ++j) { - prevSegsOfLeavingSegment.push(originalThrownForContext.segmentsList[j][i]); - } - - segments.push( - CodePathSegment.newNext( - this.idGenerator.next(), - prevSegsOfLeavingSegment - ) - ); - } - - this.pushForkContext(true); - this.forkContext.add(segments); - } - - /** - * Makes a code path segment from the first throwable node to the `catch` - * block or the `finally` block. - * @returns {void} - */ - makeFirstThrowablePathInTryBlock() { - const forkContext = this.forkContext; - - if (!forkContext.reachable) { - return; - } - - const context = getThrowContext(this); - - if (context === this || - context.position !== "try" || - !context.thrownForkContext.empty - ) { - return; - } - - context.thrownForkContext.add(forkContext.head); - forkContext.replaceHead(forkContext.makeNext(-1, -1)); - } - - //-------------------------------------------------------------------------- - // Loop Statements - //-------------------------------------------------------------------------- - - /** - * Creates a context object of a loop statement and stacks it. - * @param {string} type The type of the node which was triggered. One of - * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`, - * and `ForStatement`. - * @param {string|null} label A label of the node which was triggered. - * @throws {Error} (Unreachable - unknown type.) - * @returns {void} - */ - pushLoopContext(type, label) { - const forkContext = this.forkContext; - - // All loops need a path to account for `break` statements - const breakContext = this.pushBreakContext(true, label); - - switch (type) { - case "WhileStatement": - this.pushChoiceContext("loop", false); - this.loopContext = new WhileLoopContext(this.loopContext, label, breakContext); - break; - - case "DoWhileStatement": - this.pushChoiceContext("loop", false); - this.loopContext = new DoWhileLoopContext(this.loopContext, label, breakContext, forkContext); - break; - - case "ForStatement": - this.pushChoiceContext("loop", false); - this.loopContext = new ForLoopContext(this.loopContext, label, breakContext); - break; - - case "ForInStatement": - this.loopContext = new ForInLoopContext(this.loopContext, label, breakContext); - break; - - case "ForOfStatement": - this.loopContext = new ForOfLoopContext(this.loopContext, label, breakContext); - break; - - /* c8 ignore next */ - default: - throw new Error(`unknown type: "${type}"`); - } - } - - /** - * Pops the last context of a loop statement and finalizes it. - * @throws {Error} (Unreachable - unknown type.) - * @returns {void} - */ - popLoopContext() { - const context = this.loopContext; - - this.loopContext = context.upper; - - const forkContext = this.forkContext; - const brokenForkContext = this.popBreakContext().brokenForkContext; - - // Creates a looped path. - switch (context.type) { - case "WhileStatement": - case "ForStatement": - this.popChoiceContext(); - - /* - * Creates the path from the end of the loop body up to the - * location where `continue` would jump to. - */ - makeLooped( - this, - forkContext.head, - context.continueDestSegments - ); - break; - - case "DoWhileStatement": { - const choiceContext = this.popChoiceContext(); - - if (!choiceContext.processed) { - choiceContext.trueForkContext.add(forkContext.head); - choiceContext.falseForkContext.add(forkContext.head); - } - - /* - * If this isn't a hardcoded `true` condition, then `break` - * should continue down the path as if the condition evaluated - * to false. - */ - if (context.test !== true) { - brokenForkContext.addAll(choiceContext.falseForkContext); - } - - /* - * When the condition is true, the loop continues back to the top, - * so create a path from each possible true condition back to the - * top of the loop. - */ - const segmentsList = choiceContext.trueForkContext.segmentsList; - - for (let i = 0; i < segmentsList.length; ++i) { - makeLooped( - this, - segmentsList[i], - context.entrySegments - ); - } - break; - } - - case "ForInStatement": - case "ForOfStatement": - brokenForkContext.add(forkContext.head); - - /* - * Creates the path from the end of the loop body up to the - * left expression (left of `in` or `of`) of the loop. - */ - makeLooped( - this, - forkContext.head, - context.leftSegments - ); - break; - - /* c8 ignore next */ - default: - throw new Error("unreachable"); - } - - /* - * If there wasn't a `break` statement in the loop, then we're at - * the end of the loop's path, so we make an unreachable segment - * to mark that. - * - * If there was a `break` statement, then we continue on into the - * `brokenForkContext`. - */ - if (brokenForkContext.empty) { - forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); - } else { - forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); - } - } - - /** - * Makes a code path segment for the test part of a WhileStatement. - * @param {boolean|undefined} test The test value (only when constant). - * @returns {void} - */ - makeWhileTest(test) { - const context = this.loopContext; - const forkContext = this.forkContext; - const testSegments = forkContext.makeNext(0, -1); - - // Update state. - context.test = test; - context.continueDestSegments = testSegments; - forkContext.replaceHead(testSegments); - } - - /** - * Makes a code path segment for the body part of a WhileStatement. - * @returns {void} - */ - makeWhileBody() { - const context = this.loopContext; - const choiceContext = this.choiceContext; - const forkContext = this.forkContext; - - if (!choiceContext.processed) { - choiceContext.trueForkContext.add(forkContext.head); - choiceContext.falseForkContext.add(forkContext.head); - } - - /* - * If this isn't a hardcoded `true` condition, then `break` - * should continue down the path as if the condition evaluated - * to false. - */ - if (context.test !== true) { - context.brokenForkContext.addAll(choiceContext.falseForkContext); - } - forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1)); - } - - /** - * Makes a code path segment for the body part of a DoWhileStatement. - * @returns {void} - */ - makeDoWhileBody() { - const context = this.loopContext; - const forkContext = this.forkContext; - const bodySegments = forkContext.makeNext(-1, -1); - - // Update state. - context.entrySegments = bodySegments; - forkContext.replaceHead(bodySegments); - } - - /** - * Makes a code path segment for the test part of a DoWhileStatement. - * @param {boolean|undefined} test The test value (only when constant). - * @returns {void} - */ - makeDoWhileTest(test) { - const context = this.loopContext; - const forkContext = this.forkContext; - - context.test = test; - - /* - * If there is a `continue` statement in the loop then `continueForkContext` - * won't be empty. We wire up the path from `continue` to the loop - * test condition and then continue the traversal in the root fork context. - */ - if (!context.continueForkContext.empty) { - context.continueForkContext.add(forkContext.head); - const testSegments = context.continueForkContext.makeNext(0, -1); - - forkContext.replaceHead(testSegments); - } - } - - /** - * Makes a code path segment for the test part of a ForStatement. - * @param {boolean|undefined} test The test value (only when constant). - * @returns {void} - */ - makeForTest(test) { - const context = this.loopContext; - const forkContext = this.forkContext; - const endOfInitSegments = forkContext.head; - const testSegments = forkContext.makeNext(-1, -1); - - /* - * Update the state. - * - * The `continueDestSegments` are set to `testSegments` because we - * don't yet know if there is an update expression in this loop. So, - * from what we already know at this point, a `continue` statement - * will jump back to the test expression. - */ - context.test = test; - context.endOfInitSegments = endOfInitSegments; - context.continueDestSegments = context.testSegments = testSegments; - forkContext.replaceHead(testSegments); - } - - /** - * Makes a code path segment for the update part of a ForStatement. - * @returns {void} - */ - makeForUpdate() { - const context = this.loopContext; - const choiceContext = this.choiceContext; - const forkContext = this.forkContext; - - // Make the next paths of the test. - if (context.testSegments) { - finalizeTestSegmentsOfFor( - context, - choiceContext, - forkContext.head - ); - } else { - context.endOfInitSegments = forkContext.head; - } - - /* - * Update the state. - * - * The `continueDestSegments` are now set to `updateSegments` because we - * know there is an update expression in this loop. So, a `continue` statement - * in the loop will jump to the update expression first, and then to any - * test expression the loop might have. - */ - const updateSegments = forkContext.makeDisconnected(-1, -1); - - context.continueDestSegments = context.updateSegments = updateSegments; - forkContext.replaceHead(updateSegments); - } - - /** - * Makes a code path segment for the body part of a ForStatement. - * @returns {void} - */ - makeForBody() { - const context = this.loopContext; - const choiceContext = this.choiceContext; - const forkContext = this.forkContext; - - /* - * Determine what to do based on which part of the `for` loop are present. - * 1. If there is an update expression, then `updateSegments` is not null and - * we need to assign `endOfUpdateSegments`, and if there is a test - * expression, we then need to create the looped path to get back to - * the test condition. - * 2. If there is no update expression but there is a test expression, - * then we only need to update the test segment information. - * 3. If there is no update expression and no test expression, then we - * just save `endOfInitSegments`. - */ - if (context.updateSegments) { - context.endOfUpdateSegments = forkContext.head; - - /* - * In a `for` loop that has both an update expression and a test - * condition, execution flows from the test expression into the - * loop body, to the update expression, and then back to the test - * expression to determine if the loop should continue. - * - * To account for that, we need to make a path from the end of the - * update expression to the start of the test expression. This is - * effectively what creates the loop in the code path. - */ - if (context.testSegments) { - makeLooped( - this, - context.endOfUpdateSegments, - context.testSegments - ); - } - } else if (context.testSegments) { - finalizeTestSegmentsOfFor( - context, - choiceContext, - forkContext.head - ); - } else { - context.endOfInitSegments = forkContext.head; - } - - let bodySegments = context.endOfTestSegments; - - /* - * If there is a test condition, then there `endOfTestSegments` is also - * the start of the loop body. If there isn't a test condition then - * `bodySegments` will be null and we need to look elsewhere to find - * the start of the body. - * - * The body starts at the end of the init expression and ends at the end - * of the update expression, so we use those locations to determine the - * body segments. - */ - if (!bodySegments) { - - const prevForkContext = ForkContext.newEmpty(forkContext); - - prevForkContext.add(context.endOfInitSegments); - if (context.endOfUpdateSegments) { - prevForkContext.add(context.endOfUpdateSegments); - } - - bodySegments = prevForkContext.makeNext(0, -1); - } - - /* - * If there was no test condition and no update expression, then - * `continueDestSegments` will be null. In that case, a - * `continue` should skip directly to the body of the loop. - * Otherwise, we want to keep the current `continueDestSegments`. - */ - context.continueDestSegments = context.continueDestSegments || bodySegments; - - // move pointer to the body - forkContext.replaceHead(bodySegments); - } - - /** - * Makes a code path segment for the left part of a ForInStatement and a - * ForOfStatement. - * @returns {void} - */ - makeForInOfLeft() { - const context = this.loopContext; - const forkContext = this.forkContext; - const leftSegments = forkContext.makeDisconnected(-1, -1); - - // Update state. - context.prevSegments = forkContext.head; - context.leftSegments = context.continueDestSegments = leftSegments; - forkContext.replaceHead(leftSegments); - } - - /** - * Makes a code path segment for the right part of a ForInStatement and a - * ForOfStatement. - * @returns {void} - */ - makeForInOfRight() { - const context = this.loopContext; - const forkContext = this.forkContext; - const temp = ForkContext.newEmpty(forkContext); - - temp.add(context.prevSegments); - const rightSegments = temp.makeNext(-1, -1); - - // Update state. - context.endOfLeftSegments = forkContext.head; - forkContext.replaceHead(rightSegments); - } - - /** - * Makes a code path segment for the body part of a ForInStatement and a - * ForOfStatement. - * @returns {void} - */ - makeForInOfBody() { - const context = this.loopContext; - const forkContext = this.forkContext; - const temp = ForkContext.newEmpty(forkContext); - - temp.add(context.endOfLeftSegments); - const bodySegments = temp.makeNext(-1, -1); - - // Make a path: `right` -> `left`. - makeLooped(this, forkContext.head, context.leftSegments); - - // Update state. - context.brokenForkContext.add(forkContext.head); - forkContext.replaceHead(bodySegments); - } - - //-------------------------------------------------------------------------- - // Control Statements - //-------------------------------------------------------------------------- - - /** - * Creates new context in which a `break` statement can be used. This occurs inside of a loop, - * labeled statement, or switch statement. - * @param {boolean} breakable Indicates if we are inside a statement where - * `break` without a label will exit the statement. - * @param {string|null} label The label associated with the statement. - * @returns {BreakContext} The new context. - */ - pushBreakContext(breakable, label) { - this.breakContext = new BreakContext(this.breakContext, breakable, label, this.forkContext); - return this.breakContext; - } - - /** - * Removes the top item of the break context stack. - * @returns {Object} The removed context. - */ - popBreakContext() { - const context = this.breakContext; - const forkContext = this.forkContext; - - this.breakContext = context.upper; - - // Process this context here for other than switches and loops. - if (!context.breakable) { - const brokenForkContext = context.brokenForkContext; - - if (!brokenForkContext.empty) { - brokenForkContext.add(forkContext.head); - forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); - } - } - - return context; - } - - /** - * Makes a path for a `break` statement. - * - * It registers the head segment to a context of `break`. - * It makes new unreachable segment, then it set the head with the segment. - * @param {string|null} label A label of the break statement. - * @returns {void} - */ - makeBreak(label) { - const forkContext = this.forkContext; - - if (!forkContext.reachable) { - return; - } - - const context = getBreakContext(this, label); - - - if (context) { - context.brokenForkContext.add(forkContext.head); - } - - /* c8 ignore next */ - forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); - } - - /** - * Makes a path for a `continue` statement. - * - * It makes a looping path. - * It makes new unreachable segment, then it set the head with the segment. - * @param {string|null} label A label of the continue statement. - * @returns {void} - */ - makeContinue(label) { - const forkContext = this.forkContext; - - if (!forkContext.reachable) { - return; - } - - const context = getContinueContext(this, label); - - if (context) { - if (context.continueDestSegments) { - makeLooped(this, forkContext.head, context.continueDestSegments); - - // If the context is a for-in/of loop, this affects a break also. - if (context.type === "ForInStatement" || - context.type === "ForOfStatement" - ) { - context.brokenForkContext.add(forkContext.head); - } - } else { - context.continueForkContext.add(forkContext.head); - } - } - forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); - } - - /** - * Makes a path for a `return` statement. - * - * It registers the head segment to a context of `return`. - * It makes new unreachable segment, then it set the head with the segment. - * @returns {void} - */ - makeReturn() { - const forkContext = this.forkContext; - - if (forkContext.reachable) { - getReturnContext(this).returnedForkContext.add(forkContext.head); - forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); - } - } - - /** - * Makes a path for a `throw` statement. - * - * It registers the head segment to a context of `throw`. - * It makes new unreachable segment, then it set the head with the segment. - * @returns {void} - */ - makeThrow() { - const forkContext = this.forkContext; - - if (forkContext.reachable) { - getThrowContext(this).thrownForkContext.add(forkContext.head); - forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); - } - } - - /** - * Makes the final path. - * @returns {void} - */ - makeFinal() { - const segments = this.currentSegments; - - if (segments.length > 0 && segments[0].reachable) { - this.returnedForkContext.add(segments); - } - } + /** + * Creates a new instance. + * @param {IdGenerator} idGenerator An id generator to generate id for code + * path segments. + * @param {Function} onLooped A callback function to notify looping. + */ + constructor(idGenerator, onLooped) { + /** + * The ID generator to use when creating new segments. + * @type {IdGenerator} + */ + this.idGenerator = idGenerator; + + /** + * A callback function to call when there is a loop. + * @type {Function} + */ + this.notifyLooped = onLooped; + + /** + * The root fork context for this state. + * @type {ForkContext} + */ + this.forkContext = ForkContext.newRoot(idGenerator); + + /** + * Context for logical expressions, conditional expressions, `if` statements, + * and loops. + * @type {ChoiceContext} + */ + this.choiceContext = null; + + /** + * Context for `switch` statements. + * @type {SwitchContext} + */ + this.switchContext = null; + + /** + * Context for `try` statements. + * @type {TryContext} + */ + this.tryContext = null; + + /** + * Context for loop statements. + * @type {LoopContext} + */ + this.loopContext = null; + + /** + * Context for `break` statements. + * @type {BreakContext} + */ + this.breakContext = null; + + /** + * Context for `ChainExpression` nodes. + * @type {ChainContext} + */ + this.chainContext = null; + + /** + * An array that tracks the current segments in the state. The array + * starts empty and segments are added with each `onCodePathSegmentStart` + * event and removed with each `onCodePathSegmentEnd` event. Effectively, + * this is tracking the code path segment traversal as the state is + * modified. + * @type {Array} + */ + this.currentSegments = []; + + /** + * Tracks the starting segment for this path. This value never changes. + * @type {CodePathSegment} + */ + this.initialSegment = this.forkContext.head[0]; + + /** + * The final segments of the code path which are either `return` or `throw`. + * This is a union of the segments in `returnedForkContext` and `thrownForkContext`. + * @type {Array} + */ + this.finalSegments = []; + + /** + * The final segments of the code path which are `return`. These + * segments are also contained in `finalSegments`. + * @type {Array} + */ + this.returnedForkContext = []; + + /** + * The final segments of the code path which are `throw`. These + * segments are also contained in `finalSegments`. + * @type {Array} + */ + this.thrownForkContext = []; + + /* + * We add an `add` method so that these look more like fork contexts and + * can be used interchangeably when a fork context is needed to add more + * segments to a path. + * + * Ultimately, we want anything added to `returned` or `thrown` to also + * be added to `final`. We only add reachable and used segments to these + * arrays. + */ + const final = this.finalSegments; + const returned = this.returnedForkContext; + const thrown = this.thrownForkContext; + + returned.add = addToReturnedOrThrown.bind( + null, + returned, + thrown, + final, + ); + thrown.add = addToReturnedOrThrown.bind(null, thrown, returned, final); + } + + /** + * A passthrough property exposing the current pointer as part of the API. + * @type {CodePathSegment[]} + */ + get headSegments() { + return this.forkContext.head; + } + + /** + * The parent forking context. + * This is used for the root of new forks. + * @type {ForkContext} + */ + get parentForkContext() { + const current = this.forkContext; + + return current && current.upper; + } + + /** + * Creates and stacks new forking context. + * @param {boolean} forkLeavingPath A flag which shows being in a + * "finally" block. + * @returns {ForkContext} The created context. + */ + pushForkContext(forkLeavingPath) { + this.forkContext = ForkContext.newEmpty( + this.forkContext, + forkLeavingPath, + ); + + return this.forkContext; + } + + /** + * Pops and merges the last forking context. + * @returns {ForkContext} The last context. + */ + popForkContext() { + const lastContext = this.forkContext; + + this.forkContext = lastContext.upper; + this.forkContext.replaceHead(lastContext.makeNext(0, -1)); + + return lastContext; + } + + /** + * Creates a new path. + * @returns {void} + */ + forkPath() { + this.forkContext.add(this.parentForkContext.makeNext(-1, -1)); + } + + /** + * Creates a bypass path. + * This is used for such as IfStatement which does not have "else" chunk. + * @returns {void} + */ + forkBypassPath() { + this.forkContext.add(this.parentForkContext.head); + } + + //-------------------------------------------------------------------------- + // ConditionalExpression, LogicalExpression, IfStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context for ConditionalExpression, LogicalExpression, AssignmentExpression (logical assignments only), + * IfStatement, WhileStatement, DoWhileStatement, or ForStatement. + * + * LogicalExpressions have cases that it goes different paths between the + * `true` case and the `false` case. + * + * For Example: + * + * if (a || b) { + * foo(); + * } else { + * bar(); + * } + * + * In this case, `b` is evaluated always in the code path of the `else` + * block, but it's not so in the code path of the `if` block. + * So there are 3 paths. + * + * a -> foo(); + * a -> b -> foo(); + * a -> b -> bar(); + * @param {string} kind A kind string. + * If the new context is LogicalExpression's or AssignmentExpression's, this is `"&&"` or `"||"` or `"??"`. + * If it's IfStatement's or ConditionalExpression's, this is `"test"`. + * Otherwise, this is `"loop"`. + * @param {boolean} isForkingAsResult Indicates if the result of the choice + * creates a fork. + * @returns {void} + */ + pushChoiceContext(kind, isForkingAsResult) { + this.choiceContext = new ChoiceContext( + this.choiceContext, + kind, + isForkingAsResult, + this.forkContext, + ); + } + + /** + * Pops the last choice context and finalizes it. + * This is called upon leaving a node that represents a choice. + * @throws {Error} (Unreachable.) + * @returns {ChoiceContext} The popped context. + */ + popChoiceContext() { + const poppedChoiceContext = this.choiceContext; + const forkContext = this.forkContext; + const head = forkContext.head; + + this.choiceContext = poppedChoiceContext.upper; + + switch (poppedChoiceContext.kind) { + case "&&": + case "||": + case "??": + /* + * The `head` are the path of the right-hand operand. + * If we haven't previously added segments from child contexts, + * then we add these segments to all possible forks. + */ + if (!poppedChoiceContext.processed) { + poppedChoiceContext.trueForkContext.add(head); + poppedChoiceContext.falseForkContext.add(head); + poppedChoiceContext.nullishForkContext.add(head); + } + + /* + * If this context is the left (test) expression for another choice + * context, such as `a || b` in the expression `a || b || c`, + * then we take the segments for this context and move them up + * to the parent context. + */ + if (poppedChoiceContext.isForkingAsResult) { + const parentContext = this.choiceContext; + + parentContext.trueForkContext.addAll( + poppedChoiceContext.trueForkContext, + ); + parentContext.falseForkContext.addAll( + poppedChoiceContext.falseForkContext, + ); + parentContext.nullishForkContext.addAll( + poppedChoiceContext.nullishForkContext, + ); + parentContext.processed = true; + + // Exit early so we don't collapse all paths into one. + return poppedChoiceContext; + } + + break; + + case "test": + if (!poppedChoiceContext.processed) { + /* + * The head segments are the path of the `if` block here. + * Updates the `true` path with the end of the `if` block. + */ + poppedChoiceContext.trueForkContext.clear(); + poppedChoiceContext.trueForkContext.add(head); + } else { + /* + * The head segments are the path of the `else` block here. + * Updates the `false` path with the end of the `else` + * block. + */ + poppedChoiceContext.falseForkContext.clear(); + poppedChoiceContext.falseForkContext.add(head); + } + + break; + + case "loop": + /* + * Loops are addressed in `popLoopContext()` so just return + * the context without modification. + */ + return poppedChoiceContext; + + /* c8 ignore next */ + default: + throw new Error("unreachable"); + } + + /* + * Merge the true path with the false path to create a single path. + */ + const combinedForkContext = poppedChoiceContext.trueForkContext; + + combinedForkContext.addAll(poppedChoiceContext.falseForkContext); + forkContext.replaceHead(combinedForkContext.makeNext(0, -1)); + + return poppedChoiceContext; + } + + /** + * Creates a code path segment to represent right-hand operand of a logical + * expression. + * This is called in the preprocessing phase when entering a node. + * @throws {Error} (Unreachable.) + * @returns {void} + */ + makeLogicalRight() { + const currentChoiceContext = this.choiceContext; + const forkContext = this.forkContext; + + if (currentChoiceContext.processed) { + /* + * This context was already assigned segments from a child + * choice context. In this case, we are concerned only about + * the path that does not short-circuit and so ends up on the + * right-hand operand of the logical expression. + */ + let prevForkContext; + + switch (currentChoiceContext.kind) { + case "&&": // if true then go to the right-hand side. + prevForkContext = currentChoiceContext.trueForkContext; + break; + case "||": // if false then go to the right-hand side. + prevForkContext = currentChoiceContext.falseForkContext; + break; + case "??": // Both true/false can short-circuit, so needs the third path to go to the right-hand side. That's nullishForkContext. + prevForkContext = currentChoiceContext.nullishForkContext; + break; + default: + throw new Error("unreachable"); + } + + /* + * Create the segment for the right-hand operand of the logical expression + * and adjust the fork context pointer to point there. The right-hand segment + * is added at the end of all segments in `prevForkContext`. + */ + forkContext.replaceHead(prevForkContext.makeNext(0, -1)); + + /* + * We no longer need this list of segments. + * + * Reset `processed` because we've removed the segments from the child + * choice context. This allows `popChoiceContext()` to continue adding + * segments later. + */ + prevForkContext.clear(); + currentChoiceContext.processed = false; + } else { + /* + * This choice context was not assigned segments from a child + * choice context, which means that it's a terminal logical + * expression. + * + * `head` is the segments for the left-hand operand of the + * logical expression. + * + * Each of the fork contexts below are empty at this point. We choose + * the path(s) that will short-circuit and add the segment for the + * left-hand operand to it. Ultimately, this will be the only segment + * in that path due to the short-circuting, so we are just seeding + * these paths to start. + */ + switch (currentChoiceContext.kind) { + case "&&": + /* + * In most contexts, when a && expression evaluates to false, + * it short circuits, so we need to account for that by setting + * the `falseForkContext` to the left operand. + * + * When a && expression is the left-hand operand for a ?? + * expression, such as `(a && b) ?? c`, a nullish value will + * also short-circuit in a different way than a false value, + * so we also set the `nullishForkContext` to the left operand. + * This path is only used with a ?? expression and is thrown + * away for any other type of logical expression, so it's safe + * to always add. + */ + currentChoiceContext.falseForkContext.add(forkContext.head); + currentChoiceContext.nullishForkContext.add( + forkContext.head, + ); + break; + case "||": // the true path can short-circuit. + currentChoiceContext.trueForkContext.add(forkContext.head); + break; + case "??": // both can short-circuit. + currentChoiceContext.trueForkContext.add(forkContext.head); + currentChoiceContext.falseForkContext.add(forkContext.head); + break; + default: + throw new Error("unreachable"); + } + + /* + * Create the segment for the right-hand operand of the logical expression + * and adjust the fork context pointer to point there. + */ + forkContext.replaceHead(forkContext.makeNext(-1, -1)); + } + } + + /** + * Makes a code path segment of the `if` block. + * @returns {void} + */ + makeIfConsequent() { + const context = this.choiceContext; + const forkContext = this.forkContext; + + /* + * If any result were not transferred from child contexts, + * this sets the head segments to both cases. + * The head segments are the path of the test expression. + */ + if (!context.processed) { + context.trueForkContext.add(forkContext.head); + context.falseForkContext.add(forkContext.head); + context.nullishForkContext.add(forkContext.head); + } + + context.processed = false; + + // Creates new path from the `true` case. + forkContext.replaceHead(context.trueForkContext.makeNext(0, -1)); + } + + /** + * Makes a code path segment of the `else` block. + * @returns {void} + */ + makeIfAlternate() { + const context = this.choiceContext; + const forkContext = this.forkContext; + + /* + * The head segments are the path of the `if` block. + * Updates the `true` path with the end of the `if` block. + */ + context.trueForkContext.clear(); + context.trueForkContext.add(forkContext.head); + context.processed = true; + + // Creates new path from the `false` case. + forkContext.replaceHead(context.falseForkContext.makeNext(0, -1)); + } + + //-------------------------------------------------------------------------- + // ChainExpression + //-------------------------------------------------------------------------- + + /** + * Pushes a new `ChainExpression` context to the stack. This method is + * called when entering a `ChainExpression` node. A chain context is used to + * count forking in the optional chain then merge them on the exiting from the + * `ChainExpression` node. + * @returns {void} + */ + pushChainContext() { + this.chainContext = new ChainContext(this.chainContext); + } + + /** + * Pop a `ChainExpression` context from the stack. This method is called on + * exiting from each `ChainExpression` node. This merges all forks of the + * last optional chaining. + * @returns {void} + */ + popChainContext() { + const context = this.chainContext; + + this.chainContext = context.upper; + + // pop all choice contexts of this. + for (let i = context.choiceContextCount; i > 0; --i) { + this.popChoiceContext(); + } + } + + /** + * Create a choice context for optional access. + * This method is called on entering to each `(Call|Member)Expression[optional=true]` node. + * This creates a choice context as similar to `LogicalExpression[operator="??"]` node. + * @returns {void} + */ + makeOptionalNode() { + if (this.chainContext) { + this.chainContext.choiceContextCount += 1; + this.pushChoiceContext("??", false); + } + } + + /** + * Create a fork. + * This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node. + * @returns {void} + */ + makeOptionalRight() { + if (this.chainContext) { + this.makeLogicalRight(); + } + } + + //-------------------------------------------------------------------------- + // SwitchStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context object of SwitchStatement and stacks it. + * @param {boolean} hasCase `true` if the switch statement has one or more + * case parts. + * @param {string|null} label The label text. + * @returns {void} + */ + pushSwitchContext(hasCase, label) { + this.switchContext = new SwitchContext(this.switchContext, hasCase); + this.pushBreakContext(true, label); + } + + /** + * Pops the last context of SwitchStatement and finalizes it. + * + * - Disposes all forking stack for `case` and `default`. + * - Creates the next code path segment from `context.brokenForkContext`. + * - If the last `SwitchCase` node is not a `default` part, creates a path + * to the `default` body. + * @returns {void} + */ + popSwitchContext() { + const context = this.switchContext; + + this.switchContext = context.upper; + + const forkContext = this.forkContext; + const brokenForkContext = this.popBreakContext().brokenForkContext; + + if (context.forkCount === 0) { + /* + * When there is only one `default` chunk and there is one or more + * `break` statements, even if forks are nothing, it needs to merge + * those. + */ + if (!brokenForkContext.empty) { + brokenForkContext.add(forkContext.makeNext(-1, -1)); + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + + return; + } + + const lastSegments = forkContext.head; + + this.forkBypassPath(); + const lastCaseSegments = forkContext.head; + + /* + * `brokenForkContext` is used to make the next segment. + * It must add the last segment into `brokenForkContext`. + */ + brokenForkContext.add(lastSegments); + + /* + * Any value that doesn't match a `case` test should flow to the default + * case. That happens normally when the default case is last in the `switch`, + * but if it's not, we need to rewire some of the paths to be correct. + */ + if (!context.lastIsDefault) { + if (context.defaultBodySegments) { + /* + * There is a non-empty default case, so remove the path from the `default` + * label to its body for an accurate representation. + */ + disconnectSegments( + context.defaultSegments, + context.defaultBodySegments, + ); + + /* + * Connect the path from the last non-default case to the body of the + * default case. + */ + makeLooped(this, lastCaseSegments, context.defaultBodySegments); + } else { + /* + * There is no default case, so we treat this as if the last case + * had a `break` in it. + */ + brokenForkContext.add(lastCaseSegments); + } + } + + // Traverse up to the original fork context for the `switch` statement + for (let i = 0; i < context.forkCount; ++i) { + this.forkContext = this.forkContext.upper; + } + + /* + * Creates a path from all `brokenForkContext` paths. + * This is a path after `switch` statement. + */ + this.forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + + /** + * Makes a code path segment for a `SwitchCase` node. + * @param {boolean} isCaseBodyEmpty `true` if the body is empty. + * @param {boolean} isDefaultCase `true` if the body is the default case. + * @returns {void} + */ + makeSwitchCaseBody(isCaseBodyEmpty, isDefaultCase) { + const context = this.switchContext; + + if (!context.hasCase) { + return; + } + + /* + * Merge forks. + * The parent fork context has two segments. + * Those are from the current `case` and the body of the previous case. + */ + const parentForkContext = this.forkContext; + const forkContext = this.pushForkContext(); + + forkContext.add(parentForkContext.makeNext(0, -1)); + + /* + * Add information about the default case. + * + * The purpose of this is to identify the starting segments for the + * default case to make sure there is a path there. + */ + if (isDefaultCase) { + /* + * This is the default case in the `switch`. + * + * We first save the current pointer as `defaultSegments` to point + * to the `default` keyword. + */ + context.defaultSegments = parentForkContext.head; + + /* + * If the body of the case is empty then we just set + * `foundEmptyDefault` to true; otherwise, we save a reference + * to the current pointer as `defaultBodySegments`. + */ + if (isCaseBodyEmpty) { + context.foundEmptyDefault = true; + } else { + context.defaultBodySegments = forkContext.head; + } + } else { + /* + * This is not the default case in the `switch`. + * + * If it's not empty and there is already an empty default case found, + * that means the default case actually comes before this case, + * and that it will fall through to this case. So, we can now + * ignore the previous default case (reset `foundEmptyDefault` to false) + * and set `defaultBodySegments` to the current segments because this is + * effectively the new default case. + */ + if (!isCaseBodyEmpty && context.foundEmptyDefault) { + context.foundEmptyDefault = false; + context.defaultBodySegments = forkContext.head; + } + } + + // keep track if the default case ends up last + context.lastIsDefault = isDefaultCase; + context.forkCount += 1; + } + + //-------------------------------------------------------------------------- + // TryStatement + //-------------------------------------------------------------------------- + + /** + * Creates a context object of TryStatement and stacks it. + * @param {boolean} hasFinalizer `true` if the try statement has a + * `finally` block. + * @returns {void} + */ + pushTryContext(hasFinalizer) { + this.tryContext = new TryContext( + this.tryContext, + hasFinalizer, + this.forkContext, + ); + } + + /** + * Pops the last context of TryStatement and finalizes it. + * @returns {void} + */ + popTryContext() { + const context = this.tryContext; + + this.tryContext = context.upper; + + /* + * If we're inside the `catch` block, that means there is no `finally`, + * so we can process the `try` and `catch` blocks the simple way and + * merge their two paths. + */ + if (context.position === "catch") { + this.popForkContext(); + return; + } + + /* + * The following process is executed only when there is a `finally` + * block. + */ + + const originalReturnedForkContext = context.returnedForkContext; + const originalThrownForkContext = context.thrownForkContext; + + // no `return` or `throw` in `try` or `catch` so there's nothing left to do + if ( + originalReturnedForkContext.empty && + originalThrownForkContext.empty + ) { + return; + } + + /* + * The following process is executed only when there is a `finally` + * block and there was a `return` or `throw` in the `try` or `catch` + * blocks. + */ + + // Separate head to normal paths and leaving paths. + const headSegments = this.forkContext.head; + + this.forkContext = this.forkContext.upper; + const normalSegments = headSegments.slice( + 0, + (headSegments.length / 2) | 0, + ); + const leavingSegments = headSegments.slice( + (headSegments.length / 2) | 0, + ); + + // Forwards the leaving path to upper contexts. + if (!originalReturnedForkContext.empty) { + getReturnContext(this).returnedForkContext.add(leavingSegments); + } + if (!originalThrownForkContext.empty) { + getThrowContext(this).thrownForkContext.add(leavingSegments); + } + + // Sets the normal path as the next. + this.forkContext.replaceHead(normalSegments); + + /* + * If both paths of the `try` block and the `catch` block are + * unreachable, the next path becomes unreachable as well. + */ + if (!context.lastOfTryIsReachable && !context.lastOfCatchIsReachable) { + this.forkContext.makeUnreachable(); + } + } + + /** + * Makes a code path segment for a `catch` block. + * @returns {void} + */ + makeCatchBlock() { + const context = this.tryContext; + const forkContext = this.forkContext; + const originalThrownForkContext = context.thrownForkContext; + + /* + * We are now in a catch block so we need to update the context + * with that information. This includes creating a new fork + * context in case we encounter any `throw` statements here. + */ + context.position = "catch"; + context.thrownForkContext = ForkContext.newEmpty(forkContext); + context.lastOfTryIsReachable = forkContext.reachable; + + // Merge the thrown paths from the `try` and `catch` blocks + originalThrownForkContext.add(forkContext.head); + const thrownSegments = originalThrownForkContext.makeNext(0, -1); + + // Fork to a bypass and the merged thrown path. + this.pushForkContext(); + this.forkBypassPath(); + this.forkContext.add(thrownSegments); + } + + /** + * Makes a code path segment for a `finally` block. + * + * In the `finally` block, parallel paths are created. The parallel paths + * are used as leaving-paths. The leaving-paths are paths from `return` + * statements and `throw` statements in a `try` block or a `catch` block. + * @returns {void} + */ + makeFinallyBlock() { + const context = this.tryContext; + let forkContext = this.forkContext; + const originalReturnedForkContext = context.returnedForkContext; + const originalThrownForContext = context.thrownForkContext; + const headOfLeavingSegments = forkContext.head; + + // Update state. + if (context.position === "catch") { + // Merges two paths from the `try` block and `catch` block. + this.popForkContext(); + forkContext = this.forkContext; + + context.lastOfCatchIsReachable = forkContext.reachable; + } else { + context.lastOfTryIsReachable = forkContext.reachable; + } + + context.position = "finally"; + + /* + * If there was no `return` or `throw` in either the `try` or `catch` + * blocks, then there's no further code paths to create for `finally`. + */ + if ( + originalReturnedForkContext.empty && + originalThrownForContext.empty + ) { + // This path does not leave. + return; + } + + /* + * Create a parallel segment from merging returned and thrown. + * This segment will leave at the end of this `finally` block. + */ + const segments = forkContext.makeNext(-1, -1); + + for (let i = 0; i < forkContext.count; ++i) { + const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]]; + + for ( + let j = 0; + j < originalReturnedForkContext.segmentsList.length; + ++j + ) { + prevSegsOfLeavingSegment.push( + originalReturnedForkContext.segmentsList[j][i], + ); + } + for ( + let j = 0; + j < originalThrownForContext.segmentsList.length; + ++j + ) { + prevSegsOfLeavingSegment.push( + originalThrownForContext.segmentsList[j][i], + ); + } + + segments.push( + CodePathSegment.newNext( + this.idGenerator.next(), + prevSegsOfLeavingSegment, + ), + ); + } + + this.pushForkContext(true); + this.forkContext.add(segments); + } + + /** + * Makes a code path segment from the first throwable node to the `catch` + * block or the `finally` block. + * @returns {void} + */ + makeFirstThrowablePathInTryBlock() { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getThrowContext(this); + + if ( + context === this || + context.position !== "try" || + !context.thrownForkContext.empty + ) { + return; + } + + context.thrownForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeNext(-1, -1)); + } + + //-------------------------------------------------------------------------- + // Loop Statements + //-------------------------------------------------------------------------- + + /** + * Creates a context object of a loop statement and stacks it. + * @param {string} type The type of the node which was triggered. One of + * `WhileStatement`, `DoWhileStatement`, `ForStatement`, `ForInStatement`, + * and `ForStatement`. + * @param {string|null} label A label of the node which was triggered. + * @throws {Error} (Unreachable - unknown type.) + * @returns {void} + */ + pushLoopContext(type, label) { + const forkContext = this.forkContext; + + // All loops need a path to account for `break` statements + const breakContext = this.pushBreakContext(true, label); + + switch (type) { + case "WhileStatement": + this.pushChoiceContext("loop", false); + this.loopContext = new WhileLoopContext( + this.loopContext, + label, + breakContext, + ); + break; + + case "DoWhileStatement": + this.pushChoiceContext("loop", false); + this.loopContext = new DoWhileLoopContext( + this.loopContext, + label, + breakContext, + forkContext, + ); + break; + + case "ForStatement": + this.pushChoiceContext("loop", false); + this.loopContext = new ForLoopContext( + this.loopContext, + label, + breakContext, + ); + break; + + case "ForInStatement": + this.loopContext = new ForInLoopContext( + this.loopContext, + label, + breakContext, + ); + break; + + case "ForOfStatement": + this.loopContext = new ForOfLoopContext( + this.loopContext, + label, + breakContext, + ); + break; + + /* c8 ignore next */ + default: + throw new Error(`unknown type: "${type}"`); + } + } + + /** + * Pops the last context of a loop statement and finalizes it. + * @throws {Error} (Unreachable - unknown type.) + * @returns {void} + */ + popLoopContext() { + const context = this.loopContext; + + this.loopContext = context.upper; + + const forkContext = this.forkContext; + const brokenForkContext = this.popBreakContext().brokenForkContext; + + // Creates a looped path. + switch (context.type) { + case "WhileStatement": + case "ForStatement": + this.popChoiceContext(); + + /* + * Creates the path from the end of the loop body up to the + * location where `continue` would jump to. + */ + makeLooped( + this, + forkContext.head, + context.continueDestSegments, + ); + break; + + case "DoWhileStatement": { + const choiceContext = this.popChoiceContext(); + + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(forkContext.head); + choiceContext.falseForkContext.add(forkContext.head); + } + + /* + * If this isn't a hardcoded `true` condition, then `break` + * should continue down the path as if the condition evaluated + * to false. + */ + if (context.test !== true) { + brokenForkContext.addAll(choiceContext.falseForkContext); + } + + /* + * When the condition is true, the loop continues back to the top, + * so create a path from each possible true condition back to the + * top of the loop. + */ + const segmentsList = choiceContext.trueForkContext.segmentsList; + + for (let i = 0; i < segmentsList.length; ++i) { + makeLooped(this, segmentsList[i], context.entrySegments); + } + break; + } + + case "ForInStatement": + case "ForOfStatement": + brokenForkContext.add(forkContext.head); + + /* + * Creates the path from the end of the loop body up to the + * left expression (left of `in` or `of`) of the loop. + */ + makeLooped(this, forkContext.head, context.leftSegments); + break; + + /* c8 ignore next */ + default: + throw new Error("unreachable"); + } + + /* + * If there wasn't a `break` statement in the loop, then we're at + * the end of the loop's path, so we make an unreachable segment + * to mark that. + * + * If there was a `break` statement, then we continue on into the + * `brokenForkContext`. + */ + if (brokenForkContext.empty) { + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } else { + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + } + + /** + * Makes a code path segment for the test part of a WhileStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeWhileTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + const testSegments = forkContext.makeNext(0, -1); + + // Update state. + context.test = test; + context.continueDestSegments = testSegments; + forkContext.replaceHead(testSegments); + } + + /** + * Makes a code path segment for the body part of a WhileStatement. + * @returns {void} + */ + makeWhileBody() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + if (!choiceContext.processed) { + choiceContext.trueForkContext.add(forkContext.head); + choiceContext.falseForkContext.add(forkContext.head); + } + + /* + * If this isn't a hardcoded `true` condition, then `break` + * should continue down the path as if the condition evaluated + * to false. + */ + if (context.test !== true) { + context.brokenForkContext.addAll(choiceContext.falseForkContext); + } + forkContext.replaceHead(choiceContext.trueForkContext.makeNext(0, -1)); + } + + /** + * Makes a code path segment for the body part of a DoWhileStatement. + * @returns {void} + */ + makeDoWhileBody() { + const context = this.loopContext; + const forkContext = this.forkContext; + const bodySegments = forkContext.makeNext(-1, -1); + + // Update state. + context.entrySegments = bodySegments; + forkContext.replaceHead(bodySegments); + } + + /** + * Makes a code path segment for the test part of a DoWhileStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeDoWhileTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + + context.test = test; + + /* + * If there is a `continue` statement in the loop then `continueForkContext` + * won't be empty. We wire up the path from `continue` to the loop + * test condition and then continue the traversal in the root fork context. + */ + if (!context.continueForkContext.empty) { + context.continueForkContext.add(forkContext.head); + const testSegments = context.continueForkContext.makeNext(0, -1); + + forkContext.replaceHead(testSegments); + } + } + + /** + * Makes a code path segment for the test part of a ForStatement. + * @param {boolean|undefined} test The test value (only when constant). + * @returns {void} + */ + makeForTest(test) { + const context = this.loopContext; + const forkContext = this.forkContext; + const endOfInitSegments = forkContext.head; + const testSegments = forkContext.makeNext(-1, -1); + + /* + * Update the state. + * + * The `continueDestSegments` are set to `testSegments` because we + * don't yet know if there is an update expression in this loop. So, + * from what we already know at this point, a `continue` statement + * will jump back to the test expression. + */ + context.test = test; + context.endOfInitSegments = endOfInitSegments; + context.continueDestSegments = context.testSegments = testSegments; + forkContext.replaceHead(testSegments); + } + + /** + * Makes a code path segment for the update part of a ForStatement. + * @returns {void} + */ + makeForUpdate() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + // Make the next paths of the test. + if (context.testSegments) { + finalizeTestSegmentsOfFor(context, choiceContext, forkContext.head); + } else { + context.endOfInitSegments = forkContext.head; + } + + /* + * Update the state. + * + * The `continueDestSegments` are now set to `updateSegments` because we + * know there is an update expression in this loop. So, a `continue` statement + * in the loop will jump to the update expression first, and then to any + * test expression the loop might have. + */ + const updateSegments = forkContext.makeDisconnected(-1, -1); + + context.continueDestSegments = context.updateSegments = updateSegments; + forkContext.replaceHead(updateSegments); + } + + /** + * Makes a code path segment for the body part of a ForStatement. + * @returns {void} + */ + makeForBody() { + const context = this.loopContext; + const choiceContext = this.choiceContext; + const forkContext = this.forkContext; + + /* + * Determine what to do based on which part of the `for` loop are present. + * 1. If there is an update expression, then `updateSegments` is not null and + * we need to assign `endOfUpdateSegments`, and if there is a test + * expression, we then need to create the looped path to get back to + * the test condition. + * 2. If there is no update expression but there is a test expression, + * then we only need to update the test segment information. + * 3. If there is no update expression and no test expression, then we + * just save `endOfInitSegments`. + */ + if (context.updateSegments) { + context.endOfUpdateSegments = forkContext.head; + + /* + * In a `for` loop that has both an update expression and a test + * condition, execution flows from the test expression into the + * loop body, to the update expression, and then back to the test + * expression to determine if the loop should continue. + * + * To account for that, we need to make a path from the end of the + * update expression to the start of the test expression. This is + * effectively what creates the loop in the code path. + */ + if (context.testSegments) { + makeLooped( + this, + context.endOfUpdateSegments, + context.testSegments, + ); + } + } else if (context.testSegments) { + finalizeTestSegmentsOfFor(context, choiceContext, forkContext.head); + } else { + context.endOfInitSegments = forkContext.head; + } + + let bodySegments = context.endOfTestSegments; + + /* + * If there is a test condition, then there `endOfTestSegments` is also + * the start of the loop body. If there isn't a test condition then + * `bodySegments` will be null and we need to look elsewhere to find + * the start of the body. + * + * The body starts at the end of the init expression and ends at the end + * of the update expression, so we use those locations to determine the + * body segments. + */ + if (!bodySegments) { + const prevForkContext = ForkContext.newEmpty(forkContext); + + prevForkContext.add(context.endOfInitSegments); + if (context.endOfUpdateSegments) { + prevForkContext.add(context.endOfUpdateSegments); + } + + bodySegments = prevForkContext.makeNext(0, -1); + } + + /* + * If there was no test condition and no update expression, then + * `continueDestSegments` will be null. In that case, a + * `continue` should skip directly to the body of the loop. + * Otherwise, we want to keep the current `continueDestSegments`. + */ + context.continueDestSegments = + context.continueDestSegments || bodySegments; + + // move pointer to the body + forkContext.replaceHead(bodySegments); + } + + /** + * Makes a code path segment for the left part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfLeft() { + const context = this.loopContext; + const forkContext = this.forkContext; + const leftSegments = forkContext.makeDisconnected(-1, -1); + + // Update state. + context.prevSegments = forkContext.head; + context.leftSegments = context.continueDestSegments = leftSegments; + forkContext.replaceHead(leftSegments); + } + + /** + * Makes a code path segment for the right part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfRight() { + const context = this.loopContext; + const forkContext = this.forkContext; + const temp = ForkContext.newEmpty(forkContext); + + temp.add(context.prevSegments); + const rightSegments = temp.makeNext(-1, -1); + + // Update state. + context.endOfLeftSegments = forkContext.head; + forkContext.replaceHead(rightSegments); + } + + /** + * Makes a code path segment for the body part of a ForInStatement and a + * ForOfStatement. + * @returns {void} + */ + makeForInOfBody() { + const context = this.loopContext; + const forkContext = this.forkContext; + const temp = ForkContext.newEmpty(forkContext); + + temp.add(context.endOfLeftSegments); + const bodySegments = temp.makeNext(-1, -1); + + // Make a path: `right` -> `left`. + makeLooped(this, forkContext.head, context.leftSegments); + + // Update state. + context.brokenForkContext.add(forkContext.head); + forkContext.replaceHead(bodySegments); + } + + //-------------------------------------------------------------------------- + // Control Statements + //-------------------------------------------------------------------------- + + /** + * Creates new context in which a `break` statement can be used. This occurs inside of a loop, + * labeled statement, or switch statement. + * @param {boolean} breakable Indicates if we are inside a statement where + * `break` without a label will exit the statement. + * @param {string|null} label The label associated with the statement. + * @returns {BreakContext} The new context. + */ + pushBreakContext(breakable, label) { + this.breakContext = new BreakContext( + this.breakContext, + breakable, + label, + this.forkContext, + ); + return this.breakContext; + } + + /** + * Removes the top item of the break context stack. + * @returns {Object} The removed context. + */ + popBreakContext() { + const context = this.breakContext; + const forkContext = this.forkContext; + + this.breakContext = context.upper; + + // Process this context here for other than switches and loops. + if (!context.breakable) { + const brokenForkContext = context.brokenForkContext; + + if (!brokenForkContext.empty) { + brokenForkContext.add(forkContext.head); + forkContext.replaceHead(brokenForkContext.makeNext(0, -1)); + } + } + + return context; + } + + /** + * Makes a path for a `break` statement. + * + * It registers the head segment to a context of `break`. + * It makes new unreachable segment, then it set the head with the segment. + * @param {string|null} label A label of the break statement. + * @returns {void} + */ + makeBreak(label) { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getBreakContext(this, label); + + if (context) { + context.brokenForkContext.add(forkContext.head); + } + + /* c8 ignore next */ + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + + /** + * Makes a path for a `continue` statement. + * + * It makes a looping path. + * It makes new unreachable segment, then it set the head with the segment. + * @param {string|null} label A label of the continue statement. + * @returns {void} + */ + makeContinue(label) { + const forkContext = this.forkContext; + + if (!forkContext.reachable) { + return; + } + + const context = getContinueContext(this, label); + + if (context) { + if (context.continueDestSegments) { + makeLooped( + this, + forkContext.head, + context.continueDestSegments, + ); + + // If the context is a for-in/of loop, this affects a break also. + if ( + context.type === "ForInStatement" || + context.type === "ForOfStatement" + ) { + context.brokenForkContext.add(forkContext.head); + } + } else { + context.continueForkContext.add(forkContext.head); + } + } + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + + /** + * Makes a path for a `return` statement. + * + * It registers the head segment to a context of `return`. + * It makes new unreachable segment, then it set the head with the segment. + * @returns {void} + */ + makeReturn() { + const forkContext = this.forkContext; + + if (forkContext.reachable) { + getReturnContext(this).returnedForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + } + + /** + * Makes a path for a `throw` statement. + * + * It registers the head segment to a context of `throw`. + * It makes new unreachable segment, then it set the head with the segment. + * @returns {void} + */ + makeThrow() { + const forkContext = this.forkContext; + + if (forkContext.reachable) { + getThrowContext(this).thrownForkContext.add(forkContext.head); + forkContext.replaceHead(forkContext.makeUnreachable(-1, -1)); + } + } + + /** + * Makes the final path. + * @returns {void} + */ + makeFinal() { + const segments = this.currentSegments; + + if (segments.length > 0 && segments[0].reachable) { + this.returnedForkContext.add(segments); + } + } } module.exports = CodePathState; diff --git a/lib/linter/code-path-analysis/code-path.js b/lib/linter/code-path-analysis/code-path.js index 8c438e29a31e..36ff8c53ee05 100644 --- a/lib/linter/code-path-analysis/code-path.js +++ b/lib/linter/code-path-analysis/code-path.js @@ -20,325 +20,313 @@ const IdGenerator = require("./id-generator"); * A code path. */ class CodePath { - - /** - * Creates a new instance. - * @param {Object} options Options for the function (see below). - * @param {string} options.id An identifier. - * @param {string} options.origin The type of code path origin. - * @param {CodePath|null} options.upper The code path of the upper function scope. - * @param {Function} options.onLooped A callback function to notify looping. - */ - constructor({ id, origin, upper, onLooped }) { - - /** - * The identifier of this code path. - * Rules use it to store additional information of each rule. - * @type {string} - */ - this.id = id; - - /** - * The reason that this code path was started. May be "program", - * "function", "class-field-initializer", or "class-static-block". - * @type {string} - */ - this.origin = origin; - - /** - * The code path of the upper function scope. - * @type {CodePath|null} - */ - this.upper = upper; - - /** - * The code paths of nested function scopes. - * @type {CodePath[]} - */ - this.childCodePaths = []; - - // Initializes internal state. - Object.defineProperty( - this, - "internal", - { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) } - ); - - // Adds this into `childCodePaths` of `upper`. - if (upper) { - upper.childCodePaths.push(this); - } - } - - /** - * Gets the state of a given code path. - * @param {CodePath} codePath A code path to get. - * @returns {CodePathState} The state of the code path. - */ - static getState(codePath) { - return codePath.internal; - } - - /** - * The initial code path segment. This is the segment that is at the head - * of the code path. - * This is a passthrough to the underlying `CodePathState`. - * @type {CodePathSegment} - */ - get initialSegment() { - return this.internal.initialSegment; - } - - /** - * Final code path segments. These are the terminal (tail) segments in the - * code path, which is the combination of `returnedSegments` and `thrownSegments`. - * All segments in this array are reachable. - * This is a passthrough to the underlying `CodePathState`. - * @type {CodePathSegment[]} - */ - get finalSegments() { - return this.internal.finalSegments; - } - - /** - * Final code path segments that represent normal completion of the code path. - * For functions, this means both explicit `return` statements and implicit returns, - * such as the last reachable segment in a function that does not have an - * explicit `return` as this implicitly returns `undefined`. For scripts, - * modules, class field initializers, and class static blocks, this means - * all lines of code have been executed. - * These segments are also present in `finalSegments`. - * This is a passthrough to the underlying `CodePathState`. - * @type {CodePathSegment[]} - */ - get returnedSegments() { - return this.internal.returnedForkContext; - } - - /** - * Final code path segments that represent `throw` statements. - * This is a passthrough to the underlying `CodePathState`. - * These segments are also present in `finalSegments`. - * @type {CodePathSegment[]} - */ - get thrownSegments() { - return this.internal.thrownForkContext; - } - - /** - * Traverses all segments in this code path. - * - * codePath.traverseSegments((segment, controller) => { - * // do something. - * }); - * - * This method enumerates segments in order from the head. - * - * The `controller` argument has two methods: - * - * - `skip()` - skips the following segments in this branch - * - `break()` - skips all following segments in the traversal - * - * A note on the parameters: the `options` argument is optional. This means - * the first argument might be an options object or the callback function. - * @param {Object} [optionsOrCallback] Optional first and last segments to traverse. - * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse. - * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse. - * @param {Function} callback A callback function. - * @returns {void} - */ - traverseSegments(optionsOrCallback, callback) { - - // normalize the arguments into a callback and options - let resolvedOptions; - let resolvedCallback; - - if (typeof optionsOrCallback === "function") { - resolvedCallback = optionsOrCallback; - resolvedOptions = {}; - } else { - resolvedOptions = optionsOrCallback || {}; - resolvedCallback = callback; - } - - // determine where to start traversing from based on the options - const startSegment = resolvedOptions.first || this.internal.initialSegment; - const lastSegment = resolvedOptions.last; - - // set up initial location information - let record; - let index; - let end; - let segment = null; - - // segments that have already been visited during traversal - const visited = new Set(); - - // tracks the traversal steps - const stack = [[startSegment, 0]]; - - // segments that have been skipped during traversal - const skipped = new Set(); - - // indicates if we exited early from the traversal - let broken = false; - - /** - * Maintains traversal state. - */ - const controller = { - - /** - * Skip the following segments in this branch. - * @returns {void} - */ - skip() { - skipped.add(segment); - }, - - /** - * Stop traversal completely - do not traverse to any - * other segments. - * @returns {void} - */ - break() { - broken = true; - } - }; - - /** - * Checks if a given previous segment has been visited. - * @param {CodePathSegment} prevSegment A previous segment to check. - * @returns {boolean} `true` if the segment has been visited. - */ - function isVisited(prevSegment) { - return ( - visited.has(prevSegment) || - segment.isLoopedPrevSegment(prevSegment) - ); - } - - /** - * Checks if a given previous segment has been skipped. - * @param {CodePathSegment} prevSegment A previous segment to check. - * @returns {boolean} `true` if the segment has been skipped. - */ - function isSkipped(prevSegment) { - return ( - skipped.has(prevSegment) || - segment.isLoopedPrevSegment(prevSegment) - ); - } - - // the traversal - while (stack.length > 0) { - - /* - * This isn't a pure stack. We use the top record all the time - * but don't always pop it off. The record is popped only if - * one of the following is true: - * - * 1) We have already visited the segment. - * 2) We have not visited *all* of the previous segments. - * 3) We have traversed past the available next segments. - * - * Otherwise, we just read the value and sometimes modify the - * record as we traverse. - */ - record = stack.at(-1); - segment = record[0]; - index = record[1]; - - if (index === 0) { - - // Skip if this segment has been visited already. - if (visited.has(segment)) { - stack.pop(); - continue; - } - - // Skip if all previous segments have not been visited. - if (segment !== startSegment && - segment.prevSegments.length > 0 && - !segment.prevSegments.every(isVisited) - ) { - stack.pop(); - continue; - } - - visited.add(segment); - - - // Skips the segment if all previous segments have been skipped. - const shouldSkip = ( - skipped.size > 0 && - segment.prevSegments.length > 0 && - segment.prevSegments.every(isSkipped) - ); - - /* - * If the most recent segment hasn't been skipped, then we call - * the callback, passing in the segment and the controller. - */ - if (!shouldSkip) { - resolvedCallback.call(this, segment, controller); - - // exit if we're at the last segment - if (segment === lastSegment) { - controller.skip(); - } - - /* - * If the previous statement was executed, or if the callback - * called a method on the controller, we might need to exit the - * loop, so check for that and break accordingly. - */ - if (broken) { - break; - } - } else { - - // If the most recent segment has been skipped, then mark it as skipped. - skipped.add(segment); - } - } - - // Update the stack. - end = segment.nextSegments.length - 1; - if (index < end) { - - /* - * If we haven't yet visited all of the next segments, update - * the current top record on the stack to the next index to visit - * and then push a record for the current segment on top. - * - * Setting the current top record's index lets us know how many - * times we've been here and ensures that the segment won't be - * reprocessed (because we only process segments with an index - * of 0). - */ - record[1] += 1; - stack.push([segment.nextSegments[index], 0]); - } else if (index === end) { - - /* - * If we are at the last next segment, then reset the top record - * in the stack to next segment and set its index to 0 so it will - * be processed next. - */ - record[0] = segment.nextSegments[index]; - record[1] = 0; - } else { - - /* - * If index > end, that means we have no more segments that need - * processing. So, we pop that record off of the stack in order to - * continue traversing at the next level up. - */ - stack.pop(); - } - } - } + /** + * Creates a new instance. + * @param {Object} options Options for the function (see below). + * @param {string} options.id An identifier. + * @param {string} options.origin The type of code path origin. + * @param {CodePath|null} options.upper The code path of the upper function scope. + * @param {Function} options.onLooped A callback function to notify looping. + */ + constructor({ id, origin, upper, onLooped }) { + /** + * The identifier of this code path. + * Rules use it to store additional information of each rule. + * @type {string} + */ + this.id = id; + + /** + * The reason that this code path was started. May be "program", + * "function", "class-field-initializer", or "class-static-block". + * @type {string} + */ + this.origin = origin; + + /** + * The code path of the upper function scope. + * @type {CodePath|null} + */ + this.upper = upper; + + /** + * The code paths of nested function scopes. + * @type {CodePath[]} + */ + this.childCodePaths = []; + + // Initializes internal state. + Object.defineProperty(this, "internal", { + value: new CodePathState(new IdGenerator(`${id}_`), onLooped), + }); + + // Adds this into `childCodePaths` of `upper`. + if (upper) { + upper.childCodePaths.push(this); + } + } + + /** + * Gets the state of a given code path. + * @param {CodePath} codePath A code path to get. + * @returns {CodePathState} The state of the code path. + */ + static getState(codePath) { + return codePath.internal; + } + + /** + * The initial code path segment. This is the segment that is at the head + * of the code path. + * This is a passthrough to the underlying `CodePathState`. + * @type {CodePathSegment} + */ + get initialSegment() { + return this.internal.initialSegment; + } + + /** + * Final code path segments. These are the terminal (tail) segments in the + * code path, which is the combination of `returnedSegments` and `thrownSegments`. + * All segments in this array are reachable. + * This is a passthrough to the underlying `CodePathState`. + * @type {CodePathSegment[]} + */ + get finalSegments() { + return this.internal.finalSegments; + } + + /** + * Final code path segments that represent normal completion of the code path. + * For functions, this means both explicit `return` statements and implicit returns, + * such as the last reachable segment in a function that does not have an + * explicit `return` as this implicitly returns `undefined`. For scripts, + * modules, class field initializers, and class static blocks, this means + * all lines of code have been executed. + * These segments are also present in `finalSegments`. + * This is a passthrough to the underlying `CodePathState`. + * @type {CodePathSegment[]} + */ + get returnedSegments() { + return this.internal.returnedForkContext; + } + + /** + * Final code path segments that represent `throw` statements. + * This is a passthrough to the underlying `CodePathState`. + * These segments are also present in `finalSegments`. + * @type {CodePathSegment[]} + */ + get thrownSegments() { + return this.internal.thrownForkContext; + } + + /** + * Traverses all segments in this code path. + * + * codePath.traverseSegments((segment, controller) => { + * // do something. + * }); + * + * This method enumerates segments in order from the head. + * + * The `controller` argument has two methods: + * + * - `skip()` - skips the following segments in this branch + * - `break()` - skips all following segments in the traversal + * + * A note on the parameters: the `options` argument is optional. This means + * the first argument might be an options object or the callback function. + * @param {Object} [optionsOrCallback] Optional first and last segments to traverse. + * @param {CodePathSegment} [optionsOrCallback.first] The first segment to traverse. + * @param {CodePathSegment} [optionsOrCallback.last] The last segment to traverse. + * @param {Function} callback A callback function. + * @returns {void} + */ + traverseSegments(optionsOrCallback, callback) { + // normalize the arguments into a callback and options + let resolvedOptions; + let resolvedCallback; + + if (typeof optionsOrCallback === "function") { + resolvedCallback = optionsOrCallback; + resolvedOptions = {}; + } else { + resolvedOptions = optionsOrCallback || {}; + resolvedCallback = callback; + } + + // determine where to start traversing from based on the options + const startSegment = + resolvedOptions.first || this.internal.initialSegment; + const lastSegment = resolvedOptions.last; + + // set up initial location information + let record; + let index; + let end; + let segment = null; + + // segments that have already been visited during traversal + const visited = new Set(); + + // tracks the traversal steps + const stack = [[startSegment, 0]]; + + // segments that have been skipped during traversal + const skipped = new Set(); + + // indicates if we exited early from the traversal + let broken = false; + + /** + * Maintains traversal state. + */ + const controller = { + /** + * Skip the following segments in this branch. + * @returns {void} + */ + skip() { + skipped.add(segment); + }, + + /** + * Stop traversal completely - do not traverse to any + * other segments. + * @returns {void} + */ + break() { + broken = true; + }, + }; + + /** + * Checks if a given previous segment has been visited. + * @param {CodePathSegment} prevSegment A previous segment to check. + * @returns {boolean} `true` if the segment has been visited. + */ + function isVisited(prevSegment) { + return ( + visited.has(prevSegment) || + segment.isLoopedPrevSegment(prevSegment) + ); + } + + /** + * Checks if a given previous segment has been skipped. + * @param {CodePathSegment} prevSegment A previous segment to check. + * @returns {boolean} `true` if the segment has been skipped. + */ + function isSkipped(prevSegment) { + return ( + skipped.has(prevSegment) || + segment.isLoopedPrevSegment(prevSegment) + ); + } + + // the traversal + while (stack.length > 0) { + /* + * This isn't a pure stack. We use the top record all the time + * but don't always pop it off. The record is popped only if + * one of the following is true: + * + * 1) We have already visited the segment. + * 2) We have not visited *all* of the previous segments. + * 3) We have traversed past the available next segments. + * + * Otherwise, we just read the value and sometimes modify the + * record as we traverse. + */ + record = stack.at(-1); + segment = record[0]; + index = record[1]; + + if (index === 0) { + // Skip if this segment has been visited already. + if (visited.has(segment)) { + stack.pop(); + continue; + } + + // Skip if all previous segments have not been visited. + if ( + segment !== startSegment && + segment.prevSegments.length > 0 && + !segment.prevSegments.every(isVisited) + ) { + stack.pop(); + continue; + } + + visited.add(segment); + + // Skips the segment if all previous segments have been skipped. + const shouldSkip = + skipped.size > 0 && + segment.prevSegments.length > 0 && + segment.prevSegments.every(isSkipped); + + /* + * If the most recent segment hasn't been skipped, then we call + * the callback, passing in the segment and the controller. + */ + if (!shouldSkip) { + resolvedCallback.call(this, segment, controller); + + // exit if we're at the last segment + if (segment === lastSegment) { + controller.skip(); + } + + /* + * If the previous statement was executed, or if the callback + * called a method on the controller, we might need to exit the + * loop, so check for that and break accordingly. + */ + if (broken) { + break; + } + } else { + // If the most recent segment has been skipped, then mark it as skipped. + skipped.add(segment); + } + } + + // Update the stack. + end = segment.nextSegments.length - 1; + if (index < end) { + /* + * If we haven't yet visited all of the next segments, update + * the current top record on the stack to the next index to visit + * and then push a record for the current segment on top. + * + * Setting the current top record's index lets us know how many + * times we've been here and ensures that the segment won't be + * reprocessed (because we only process segments with an index + * of 0). + */ + record[1] += 1; + stack.push([segment.nextSegments[index], 0]); + } else if (index === end) { + /* + * If we are at the last next segment, then reset the top record + * in the stack to next segment and set its index to 0 so it will + * be processed next. + */ + record[0] = segment.nextSegments[index]; + record[1] = 0; + } else { + /* + * If index > end, that means we have no more segments that need + * processing. So, we pop that record off of the stack in order to + * continue traversing at the next level up. + */ + stack.pop(); + } + } + } } module.exports = CodePath; diff --git a/lib/linter/code-path-analysis/debug-helpers.js b/lib/linter/code-path-analysis/debug-helpers.js index c0e01a8248b4..6a65ca7b50d3 100644 --- a/lib/linter/code-path-analysis/debug-helpers.js +++ b/lib/linter/code-path-analysis/debug-helpers.js @@ -21,8 +21,9 @@ const debug = require("debug")("eslint:code-path"); * @returns {string} Id of the segment. */ /* c8 ignore next */ -function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring - return segment.id + (segment.reachable ? "" : "!"); +// eslint-disable-next-line jsdoc/require-jsdoc -- Ignoring +function getId(segment) { + return segment.id + (segment.reachable ? "" : "!"); } /** @@ -32,13 +33,16 @@ function getId(segment) { // eslint-disable-line jsdoc/require-jsdoc -- Ignoring * @returns {string} The string representation. */ function nodeToString(node, label) { - const suffix = label ? `:${label}` : ""; - - switch (node.type) { - case "Identifier": return `${node.type}${suffix} (${node.name})`; - case "Literal": return `${node.type}${suffix} (${node.value})`; - default: return `${node.type}${suffix}`; - } + const suffix = label ? `:${label}` : ""; + + switch (node.type) { + case "Identifier": + return `${node.type}${suffix} (${node.name})`; + case "Literal": + return `${node.type}${suffix} (${node.value})`; + default: + return `${node.type}${suffix}`; + } } //------------------------------------------------------------------------------ @@ -46,158 +50,174 @@ function nodeToString(node, label) { //------------------------------------------------------------------------------ module.exports = { - - /** - * A flag that debug dumping is enabled or not. - * @type {boolean} - */ - enabled: debug.enabled, - - /** - * Dumps given objects. - * @param {...any} args objects to dump. - * @returns {void} - */ - dump: debug, - - /** - * Dumps the current analyzing state. - * @param {ASTNode} node A node to dump. - * @param {CodePathState} state A state to dump. - * @param {boolean} leaving A flag whether or not it's leaving - * @returns {void} - */ - dumpState: !debug.enabled ? debug : /* c8 ignore next */ function(node, state, leaving) { - for (let i = 0; i < state.currentSegments.length; ++i) { - const segInternal = state.currentSegments[i].internal; - - if (leaving) { - const last = segInternal.nodes.length - 1; - - if (last >= 0 && segInternal.nodes[last] === nodeToString(node, "enter")) { - segInternal.nodes[last] = nodeToString(node, void 0); - } else { - segInternal.nodes.push(nodeToString(node, "exit")); - } - } else { - segInternal.nodes.push(nodeToString(node, "enter")); - } - } - - debug([ - `${state.currentSegments.map(getId).join(",")})`, - `${node.type}${leaving ? ":exit" : ""}` - ].join(" ")); - }, - - /** - * Dumps a DOT code of a given code path. - * The DOT code can be visualized with Graphvis. - * @param {CodePath} codePath A code path to dump. - * @returns {void} - * @see http://www.graphviz.org - * @see http://www.webgraphviz.com - */ - dumpDot: !debug.enabled ? debug : /* c8 ignore next */ function(codePath) { - let text = - "\n" + - "digraph {\n" + - "node[shape=box,style=\"rounded,filled\",fillcolor=white];\n" + - "initial[label=\"\",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; - - if (codePath.returnedSegments.length > 0) { - text += "final[label=\"\",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n"; - } - if (codePath.thrownSegments.length > 0) { - text += "thrown[label=\"✘\",shape=circle,width=0.3,height=0.3,fixedsize=true];\n"; - } - - const traceMap = Object.create(null); - const arrows = this.makeDotArrows(codePath, traceMap); - - for (const id in traceMap) { // eslint-disable-line guard-for-in -- Want ability to traverse prototype - const segment = traceMap[id]; - - text += `${id}[`; - - if (segment.reachable) { - text += "label=\""; - } else { - text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<>\\n"; - } - - if (segment.internal.nodes.length > 0) { - text += segment.internal.nodes.join("\\n"); - } else { - text += "????"; - } - - text += "\"];\n"; - } - - text += `${arrows}\n`; - text += "}"; - debug("DOT", text); - }, - - /** - * Makes a DOT code of a given code path. - * The DOT code can be visualized with Graphvis. - * @param {CodePath} codePath A code path to make DOT. - * @param {Object} traceMap Optional. A map to check whether or not segments had been done. - * @returns {string} A DOT code of the code path. - */ - makeDotArrows(codePath, traceMap) { - const stack = [[codePath.initialSegment, 0]]; - const done = traceMap || Object.create(null); - let lastId = codePath.initialSegment.id; - let text = `initial->${codePath.initialSegment.id}`; - - while (stack.length > 0) { - const item = stack.pop(); - const segment = item[0]; - const index = item[1]; - - if (done[segment.id] && index === 0) { - continue; - } - done[segment.id] = segment; - - const nextSegment = segment.allNextSegments[index]; - - if (!nextSegment) { - continue; - } - - if (lastId === segment.id) { - text += `->${nextSegment.id}`; - } else { - text += `;\n${segment.id}->${nextSegment.id}`; - } - lastId = nextSegment.id; - - stack.unshift([segment, 1 + index]); - stack.push([nextSegment, 0]); - } - - codePath.returnedSegments.forEach(finalSegment => { - if (lastId === finalSegment.id) { - text += "->final"; - } else { - text += `;\n${finalSegment.id}->final`; - } - lastId = null; - }); - - codePath.thrownSegments.forEach(finalSegment => { - if (lastId === finalSegment.id) { - text += "->thrown"; - } else { - text += `;\n${finalSegment.id}->thrown`; - } - lastId = null; - }); - - return `${text};`; - } + /** + * A flag that debug dumping is enabled or not. + * @type {boolean} + */ + enabled: debug.enabled, + + /** + * Dumps given objects. + * @param {...any} args objects to dump. + * @returns {void} + */ + dump: debug, + + /** + * Dumps the current analyzing state. + * @param {ASTNode} node A node to dump. + * @param {CodePathState} state A state to dump. + * @param {boolean} leaving A flag whether or not it's leaving + * @returns {void} + */ + dumpState: !debug.enabled + ? debug + : /* c8 ignore next */ function (node, state, leaving) { + for (let i = 0; i < state.currentSegments.length; ++i) { + const segInternal = state.currentSegments[i].internal; + + if (leaving) { + const last = segInternal.nodes.length - 1; + + if ( + last >= 0 && + segInternal.nodes[last] === + nodeToString(node, "enter") + ) { + segInternal.nodes[last] = nodeToString( + node, + void 0, + ); + } else { + segInternal.nodes.push(nodeToString(node, "exit")); + } + } else { + segInternal.nodes.push(nodeToString(node, "enter")); + } + } + + debug( + [ + `${state.currentSegments.map(getId).join(",")})`, + `${node.type}${leaving ? ":exit" : ""}`, + ].join(" "), + ); + }, + + /** + * Dumps a DOT code of a given code path. + * The DOT code can be visualized with Graphvis. + * @param {CodePath} codePath A code path to dump. + * @returns {void} + * @see http://www.graphviz.org + * @see http://www.webgraphviz.com + */ + dumpDot: !debug.enabled + ? debug + : /* c8 ignore next */ function (codePath) { + let text = + "\n" + + "digraph {\n" + + 'node[shape=box,style="rounded,filled",fillcolor=white];\n' + + 'initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];\n'; + + if (codePath.returnedSegments.length > 0) { + text += + 'final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];\n'; + } + if (codePath.thrownSegments.length > 0) { + text += + 'thrown[label="✘",shape=circle,width=0.3,height=0.3,fixedsize=true];\n'; + } + + const traceMap = Object.create(null); + const arrows = this.makeDotArrows(codePath, traceMap); + + // eslint-disable-next-line guard-for-in -- Want ability to traverse prototype + for (const id in traceMap) { + const segment = traceMap[id]; + + text += `${id}[`; + + if (segment.reachable) { + text += 'label="'; + } else { + text += + 'style="rounded,dashed,filled",fillcolor="#FF9800",label="<>\\n'; + } + + if (segment.internal.nodes.length > 0) { + text += segment.internal.nodes.join("\\n"); + } else { + text += "????"; + } + + text += '"];\n'; + } + + text += `${arrows}\n`; + text += "}"; + debug("DOT", text); + }, + + /** + * Makes a DOT code of a given code path. + * The DOT code can be visualized with Graphvis. + * @param {CodePath} codePath A code path to make DOT. + * @param {Object} traceMap Optional. A map to check whether or not segments had been done. + * @returns {string} A DOT code of the code path. + */ + makeDotArrows(codePath, traceMap) { + const stack = [[codePath.initialSegment, 0]]; + const done = traceMap || Object.create(null); + let lastId = codePath.initialSegment.id; + let text = `initial->${codePath.initialSegment.id}`; + + while (stack.length > 0) { + const item = stack.pop(); + const segment = item[0]; + const index = item[1]; + + if (done[segment.id] && index === 0) { + continue; + } + done[segment.id] = segment; + + const nextSegment = segment.allNextSegments[index]; + + if (!nextSegment) { + continue; + } + + if (lastId === segment.id) { + text += `->${nextSegment.id}`; + } else { + text += `;\n${segment.id}->${nextSegment.id}`; + } + lastId = nextSegment.id; + + stack.unshift([segment, 1 + index]); + stack.push([nextSegment, 0]); + } + + codePath.returnedSegments.forEach(finalSegment => { + if (lastId === finalSegment.id) { + text += "->final"; + } else { + text += `;\n${finalSegment.id}->final`; + } + lastId = null; + }); + + codePath.thrownSegments.forEach(finalSegment => { + if (lastId === finalSegment.id) { + text += "->thrown"; + } else { + text += `;\n${finalSegment.id}->thrown`; + } + lastId = null; + }); + + return `${text};`; + }, }; diff --git a/lib/linter/code-path-analysis/fork-context.js b/lib/linter/code-path-analysis/fork-context.js index d6598be93bc6..93d331625428 100644 --- a/lib/linter/code-path-analysis/fork-context.js +++ b/lib/linter/code-path-analysis/fork-context.js @@ -14,7 +14,7 @@ //------------------------------------------------------------------------------ const assert = require("../../shared/assert"), - CodePathSegment = require("./code-path-segment"); + CodePathSegment = require("./code-path-segment"); //------------------------------------------------------------------------------ // Helpers @@ -26,7 +26,7 @@ const assert = require("../../shared/assert"), * @returns {boolean} `true` if the segment is reachable. */ function isReachable(segment) { - return segment.reachable; + return segment.reachable; } /** @@ -54,44 +54,43 @@ function isReachable(segment) { * @returns {Array} An array of the newly-created segments. */ function createSegments(context, startIndex, endIndex, create) { - - /** @type {Array>} */ - const list = context.segmentsList; - - /* - * Both `startIndex` and `endIndex` work the same way: if the number is zero - * or more, then the number is used as-is. If the number is negative, - * then that number is added to the length of the segments list to - * determine the index to use. That means -1 for either argument - * is the last element, -2 is the second to last, and so on. - * - * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the - * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function - * will include items at indices 0, 1, and 2. - * - * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only - * be using the last segment in `list`. - */ - const normalizedBegin = startIndex >= 0 ? startIndex : list.length + startIndex; - const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; - - /** @type {Array} */ - const segments = []; - - for (let i = 0; i < context.count; ++i) { - - // this is passed into `new CodePathSegment` to add to code path. - const allPrevSegments = []; - - for (let j = normalizedBegin; j <= normalizedEnd; ++j) { - allPrevSegments.push(list[j][i]); - } - - // note: `create` is just a wrapper that augments `new CodePathSegment`. - segments.push(create(context.idGenerator.next(), allPrevSegments)); - } - - return segments; + /** @type {Array>} */ + const list = context.segmentsList; + + /* + * Both `startIndex` and `endIndex` work the same way: if the number is zero + * or more, then the number is used as-is. If the number is negative, + * then that number is added to the length of the segments list to + * determine the index to use. That means -1 for either argument + * is the last element, -2 is the second to last, and so on. + * + * So if `startIndex` is 0, `endIndex` is -1, and `list.length` is 3, the + * effective `startIndex` is 0 and the effective `endIndex` is 2, so this function + * will include items at indices 0, 1, and 2. + * + * Therefore, if `startIndex` is -1 and `endIndex` is -1, that means we'll only + * be using the last segment in `list`. + */ + const normalizedBegin = + startIndex >= 0 ? startIndex : list.length + startIndex; + const normalizedEnd = endIndex >= 0 ? endIndex : list.length + endIndex; + + /** @type {Array} */ + const segments = []; + + for (let i = 0; i < context.count; ++i) { + // this is passed into `new CodePathSegment` to add to code path. + const allPrevSegments = []; + + for (let j = normalizedBegin; j <= normalizedEnd; ++j) { + allPrevSegments.push(list[j][i]); + } + + // note: `create` is just a wrapper that augments `new CodePathSegment`. + segments.push(create(context.idGenerator.next(), allPrevSegments)); + } + + return segments; } /** @@ -103,50 +102,56 @@ function createSegments(context, startIndex, endIndex, create) { * @returns {Array} The merged segments. */ function mergeExtraSegments(context, segments) { - let currentSegments = segments; - - /* - * We need to ensure that the array returned from this function contains no more - * than the number of segments that the context allows. `context.count` indicates - * how many items should be in the returned array to ensure that the new segment - * entries will line up with the already existing segment entries. - */ - while (currentSegments.length > context.count) { - const merged = []; - - /* - * Because `context.count` is a factor of 2 inside of a `finally` block, - * we can divide the segment count by 2 to merge the paths together. - * This loops through each segment in the list and creates a new `CodePathSegment` - * that has the segment and the segment two slots away as previous segments. - * - * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such - * that: - * - * When `i` is 0: - * a->e - * c->e - * - * When `i` is 1: - * b->f - * d->f - */ - for (let i = 0, length = Math.floor(currentSegments.length / 2); i < length; ++i) { - merged.push(CodePathSegment.newNext( - context.idGenerator.next(), - [currentSegments[i], currentSegments[i + length]] - )); - } - - /* - * Go through the loop condition one more time to see if we have the - * number of segments for the context. If not, we'll keep merging paths - * of the merged segments until we get there. - */ - currentSegments = merged; - } - - return currentSegments; + let currentSegments = segments; + + /* + * We need to ensure that the array returned from this function contains no more + * than the number of segments that the context allows. `context.count` indicates + * how many items should be in the returned array to ensure that the new segment + * entries will line up with the already existing segment entries. + */ + while (currentSegments.length > context.count) { + const merged = []; + + /* + * Because `context.count` is a factor of 2 inside of a `finally` block, + * we can divide the segment count by 2 to merge the paths together. + * This loops through each segment in the list and creates a new `CodePathSegment` + * that has the segment and the segment two slots away as previous segments. + * + * If `currentSegments` is [a,b,c,d], this will create new segments e and f, such + * that: + * + * When `i` is 0: + * a->e + * c->e + * + * When `i` is 1: + * b->f + * d->f + */ + for ( + let i = 0, length = Math.floor(currentSegments.length / 2); + i < length; + ++i + ) { + merged.push( + CodePathSegment.newNext(context.idGenerator.next(), [ + currentSegments[i], + currentSegments[i + length], + ]), + ); + } + + /* + * Go through the loop condition one more time to see if we have the + * number of segments for the context. If not, we'll keep merging paths + * of the merged segments until we get there. + */ + currentSegments = merged; + } + + return currentSegments; } //------------------------------------------------------------------------------ @@ -157,193 +162,213 @@ function mergeExtraSegments(context, segments) { * Manages the forking of code paths. */ class ForkContext { - - /** - * Creates a new instance. - * @param {IdGenerator} idGenerator An identifier generator for segments. - * @param {ForkContext|null} upper The preceding fork context. - * @param {number} count The number of parallel segments in each element - * of `segmentsList`. - */ - constructor(idGenerator, upper, count) { - - /** - * The ID generator that will generate segment IDs for any new - * segments that are created. - * @type {IdGenerator} - */ - this.idGenerator = idGenerator; - - /** - * The preceding fork context. - * @type {ForkContext|null} - */ - this.upper = upper; - - /** - * The number of elements in each element of `segmentsList`. In most - * cases, this is 1 but can be 2 when there is a `finally` present, - * which forks the code path outside of normal flow. In the case of nested - * `finally` blocks, this can be a multiple of 2. - * @type {number} - */ - this.count = count; - - /** - * The segments within this context. Each element in this array has - * `count` elements that represent one step in each fork. For example, - * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path - * a->c->e and one path b->d->f, and `count` is 2 because each element - * is an array with two elements. - * @type {Array>} - */ - this.segmentsList = []; - } - - /** - * The segments that begin this fork context. - * @type {Array} - */ - get head() { - const list = this.segmentsList; - - return list.length === 0 ? [] : list.at(-1); - } - - /** - * Indicates if the context contains no segments. - * @type {boolean} - */ - get empty() { - return this.segmentsList.length === 0; - } - - /** - * Indicates if there are any segments that are reachable. - * @type {boolean} - */ - get reachable() { - const segments = this.head; - - return segments.length > 0 && segments.some(isReachable); - } - - /** - * Creates new segments in this context and appends them to the end of the - * already existing `CodePathSegment`s specified by `startIndex` and - * `endIndex`. - * @param {number} startIndex The index of the first segment in the context - * that should be specified as previous segments for the newly created segments. - * @param {number} endIndex The index of the last segment in the context - * that should be specified as previous segments for the newly created segments. - * @returns {Array} An array of the newly created segments. - */ - makeNext(startIndex, endIndex) { - return createSegments(this, startIndex, endIndex, CodePathSegment.newNext); - } - - /** - * Creates new unreachable segments in this context and appends them to the end of the - * already existing `CodePathSegment`s specified by `startIndex` and - * `endIndex`. - * @param {number} startIndex The index of the first segment in the context - * that should be specified as previous segments for the newly created segments. - * @param {number} endIndex The index of the last segment in the context - * that should be specified as previous segments for the newly created segments. - * @returns {Array} An array of the newly created segments. - */ - makeUnreachable(startIndex, endIndex) { - return createSegments(this, startIndex, endIndex, CodePathSegment.newUnreachable); - } - - /** - * Creates new segments in this context and does not append them to the end - * of the already existing `CodePathSegment`s specified by `startIndex` and - * `endIndex`. The `startIndex` and `endIndex` are only used to determine if - * the new segments should be reachable. If any of the segments in this range - * are reachable then the new segments are also reachable; otherwise, the new - * segments are unreachable. - * @param {number} startIndex The index of the first segment in the context - * that should be considered for reachability. - * @param {number} endIndex The index of the last segment in the context - * that should be considered for reachability. - * @returns {Array} An array of the newly created segments. - */ - makeDisconnected(startIndex, endIndex) { - return createSegments(this, startIndex, endIndex, CodePathSegment.newDisconnected); - } - - /** - * Adds segments to the head of this context. - * @param {Array} segments The segments to add. - * @returns {void} - */ - add(segments) { - assert(segments.length >= this.count, `${segments.length} >= ${this.count}`); - this.segmentsList.push(mergeExtraSegments(this, segments)); - } - - /** - * Replaces the head segments with the given segments. - * The current head segments are removed. - * @param {Array} replacementHeadSegments The new head segments. - * @returns {void} - */ - replaceHead(replacementHeadSegments) { - assert( - replacementHeadSegments.length >= this.count, - `${replacementHeadSegments.length} >= ${this.count}` - ); - this.segmentsList.splice(-1, 1, mergeExtraSegments(this, replacementHeadSegments)); - } - - /** - * Adds all segments of a given fork context into this context. - * @param {ForkContext} otherForkContext The fork context to add from. - * @returns {void} - */ - addAll(otherForkContext) { - assert(otherForkContext.count === this.count); - this.segmentsList.push(...otherForkContext.segmentsList); - } - - /** - * Clears all segments in this context. - * @returns {void} - */ - clear() { - this.segmentsList = []; - } - - /** - * Creates a new root context, meaning that there are no parent - * fork contexts. - * @param {IdGenerator} idGenerator An identifier generator for segments. - * @returns {ForkContext} New fork context. - */ - static newRoot(idGenerator) { - const context = new ForkContext(idGenerator, null, 1); - - context.add([CodePathSegment.newRoot(idGenerator.next())]); - - return context; - } - - /** - * Creates an empty fork context preceded by a given context. - * @param {ForkContext} parentContext The parent fork context. - * @param {boolean} shouldForkLeavingPath Indicates that we are inside of - * a `finally` block and should therefore fork the path that leaves - * `finally`. - * @returns {ForkContext} New fork context. - */ - static newEmpty(parentContext, shouldForkLeavingPath) { - return new ForkContext( - parentContext.idGenerator, - parentContext, - (shouldForkLeavingPath ? 2 : 1) * parentContext.count - ); - } + /** + * Creates a new instance. + * @param {IdGenerator} idGenerator An identifier generator for segments. + * @param {ForkContext|null} upper The preceding fork context. + * @param {number} count The number of parallel segments in each element + * of `segmentsList`. + */ + constructor(idGenerator, upper, count) { + /** + * The ID generator that will generate segment IDs for any new + * segments that are created. + * @type {IdGenerator} + */ + this.idGenerator = idGenerator; + + /** + * The preceding fork context. + * @type {ForkContext|null} + */ + this.upper = upper; + + /** + * The number of elements in each element of `segmentsList`. In most + * cases, this is 1 but can be 2 when there is a `finally` present, + * which forks the code path outside of normal flow. In the case of nested + * `finally` blocks, this can be a multiple of 2. + * @type {number} + */ + this.count = count; + + /** + * The segments within this context. Each element in this array has + * `count` elements that represent one step in each fork. For example, + * when `segmentsList` is `[[a, b], [c, d], [e, f]]`, there is one path + * a->c->e and one path b->d->f, and `count` is 2 because each element + * is an array with two elements. + * @type {Array>} + */ + this.segmentsList = []; + } + + /** + * The segments that begin this fork context. + * @type {Array} + */ + get head() { + const list = this.segmentsList; + + return list.length === 0 ? [] : list.at(-1); + } + + /** + * Indicates if the context contains no segments. + * @type {boolean} + */ + get empty() { + return this.segmentsList.length === 0; + } + + /** + * Indicates if there are any segments that are reachable. + * @type {boolean} + */ + get reachable() { + const segments = this.head; + + return segments.length > 0 && segments.some(isReachable); + } + + /** + * Creates new segments in this context and appends them to the end of the + * already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. + * @param {number} startIndex The index of the first segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {number} endIndex The index of the last segment in the context + * that should be specified as previous segments for the newly created segments. + * @returns {Array} An array of the newly created segments. + */ + makeNext(startIndex, endIndex) { + return createSegments( + this, + startIndex, + endIndex, + CodePathSegment.newNext, + ); + } + + /** + * Creates new unreachable segments in this context and appends them to the end of the + * already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. + * @param {number} startIndex The index of the first segment in the context + * that should be specified as previous segments for the newly created segments. + * @param {number} endIndex The index of the last segment in the context + * that should be specified as previous segments for the newly created segments. + * @returns {Array} An array of the newly created segments. + */ + makeUnreachable(startIndex, endIndex) { + return createSegments( + this, + startIndex, + endIndex, + CodePathSegment.newUnreachable, + ); + } + + /** + * Creates new segments in this context and does not append them to the end + * of the already existing `CodePathSegment`s specified by `startIndex` and + * `endIndex`. The `startIndex` and `endIndex` are only used to determine if + * the new segments should be reachable. If any of the segments in this range + * are reachable then the new segments are also reachable; otherwise, the new + * segments are unreachable. + * @param {number} startIndex The index of the first segment in the context + * that should be considered for reachability. + * @param {number} endIndex The index of the last segment in the context + * that should be considered for reachability. + * @returns {Array} An array of the newly created segments. + */ + makeDisconnected(startIndex, endIndex) { + return createSegments( + this, + startIndex, + endIndex, + CodePathSegment.newDisconnected, + ); + } + + /** + * Adds segments to the head of this context. + * @param {Array} segments The segments to add. + * @returns {void} + */ + add(segments) { + assert( + segments.length >= this.count, + `${segments.length} >= ${this.count}`, + ); + this.segmentsList.push(mergeExtraSegments(this, segments)); + } + + /** + * Replaces the head segments with the given segments. + * The current head segments are removed. + * @param {Array} replacementHeadSegments The new head segments. + * @returns {void} + */ + replaceHead(replacementHeadSegments) { + assert( + replacementHeadSegments.length >= this.count, + `${replacementHeadSegments.length} >= ${this.count}`, + ); + this.segmentsList.splice( + -1, + 1, + mergeExtraSegments(this, replacementHeadSegments), + ); + } + + /** + * Adds all segments of a given fork context into this context. + * @param {ForkContext} otherForkContext The fork context to add from. + * @returns {void} + */ + addAll(otherForkContext) { + assert(otherForkContext.count === this.count); + this.segmentsList.push(...otherForkContext.segmentsList); + } + + /** + * Clears all segments in this context. + * @returns {void} + */ + clear() { + this.segmentsList = []; + } + + /** + * Creates a new root context, meaning that there are no parent + * fork contexts. + * @param {IdGenerator} idGenerator An identifier generator for segments. + * @returns {ForkContext} New fork context. + */ + static newRoot(idGenerator) { + const context = new ForkContext(idGenerator, null, 1); + + context.add([CodePathSegment.newRoot(idGenerator.next())]); + + return context; + } + + /** + * Creates an empty fork context preceded by a given context. + * @param {ForkContext} parentContext The parent fork context. + * @param {boolean} shouldForkLeavingPath Indicates that we are inside of + * a `finally` block and should therefore fork the path that leaves + * `finally`. + * @returns {ForkContext} New fork context. + */ + static newEmpty(parentContext, shouldForkLeavingPath) { + return new ForkContext( + parentContext.idGenerator, + parentContext, + (shouldForkLeavingPath ? 2 : 1) * parentContext.count, + ); + } } module.exports = ForkContext; diff --git a/lib/linter/code-path-analysis/id-generator.js b/lib/linter/code-path-analysis/id-generator.js index b580104e1ac2..9cb4d33b29a9 100644 --- a/lib/linter/code-path-analysis/id-generator.js +++ b/lib/linter/code-path-analysis/id-generator.js @@ -17,29 +17,28 @@ * A generator for unique ids. */ class IdGenerator { - - /** - * @param {string} prefix Optional. A prefix of generated ids. - */ - constructor(prefix) { - this.prefix = String(prefix); - this.n = 0; - } - - /** - * Generates id. - * @returns {string} A generated id. - */ - next() { - this.n = 1 + this.n | 0; - - /* c8 ignore start */ - if (this.n < 0) { - this.n = 1; - }/* c8 ignore stop */ - - return this.prefix + this.n; - } + /** + * @param {string} prefix Optional. A prefix of generated ids. + */ + constructor(prefix) { + this.prefix = String(prefix); + this.n = 0; + } + + /** + * Generates id. + * @returns {string} A generated id. + */ + next() { + this.n = (1 + this.n) | 0; + + /* c8 ignore start */ + if (this.n < 0) { + this.n = 1; + } /* c8 ignore stop */ + + return this.prefix + this.n; + } } module.exports = IdGenerator; diff --git a/lib/linter/file-context.js b/lib/linter/file-context.js index b669472396f9..d8909454840f 100644 --- a/lib/linter/file-context.js +++ b/lib/linter/file-context.js @@ -9,126 +9,125 @@ * Represents a file context that the linter can use to lint a file. */ class FileContext { - - /** - * The current working directory. - * @type {string} - */ - cwd; - - /** - * The filename of the file being linted. - * @type {string} - */ - filename; - - /** - * The physical filename of the file being linted. - * @type {string} - */ - physicalFilename; - - /** - * The source code of the file being linted. - * @type {SourceCode} - */ - sourceCode; - - /** - * The parser options for the file being linted. - * @type {Record} - * @deprecated Use `languageOptions` instead. - */ - parserOptions; - - /** - * The path to the parser used to parse this file. - * @type {string} - * @deprecated No longer supported. - */ - parserPath; - - /** - * The language options used when parsing this file. - * @type {Record} - */ - languageOptions; - - /** - * The settings for the file being linted. - * @type {Record} - */ - settings; - - /** - * Creates a new instance. - * @param {Object} config The configuration object for the file context. - * @param {string} config.cwd The current working directory. - * @param {string} config.filename The filename of the file being linted. - * @param {string} config.physicalFilename The physical filename of the file being linted. - * @param {SourceCode} config.sourceCode The source code of the file being linted. - * @param {Record} config.parserOptions The parser options for the file being linted. - * @param {string} config.parserPath The path to the parser used to parse this file. - * @param {Record} config.languageOptions The language options used when parsing this file. - * @param {Record} config.settings The settings for the file being linted. - */ - constructor({ - cwd, - filename, - physicalFilename, - sourceCode, - parserOptions, - parserPath, - languageOptions, - settings - }) { - this.cwd = cwd; - this.filename = filename; - this.physicalFilename = physicalFilename; - this.sourceCode = sourceCode; - this.parserOptions = parserOptions; - this.parserPath = parserPath; - this.languageOptions = languageOptions; - this.settings = settings; - - Object.freeze(this); - } - - /** - * Gets the current working directory. - * @returns {string} The current working directory. - * @deprecated Use `cwd` instead. - */ - getCwd() { - return this.cwd; - } - - /** - * Gets the filename of the file being linted. - * @returns {string} The filename of the file being linted. - * @deprecated Use `filename` instead. - */ - getFilename() { - return this.filename; - } - - /** - * Gets the physical filename of the file being linted. - * @returns {string} The physical filename of the file being linted. - * @deprecated Use `physicalFilename` instead. - */ - getPhysicalFilename() { - return this.physicalFilename; - } - - /** - * Gets the source code of the file being linted. - * @returns {SourceCode} The source code of the file being linted. - * @deprecated Use `sourceCode` instead. - */ - getSourceCode() { - return this.sourceCode; - } + /** + * The current working directory. + * @type {string} + */ + cwd; + + /** + * The filename of the file being linted. + * @type {string} + */ + filename; + + /** + * The physical filename of the file being linted. + * @type {string} + */ + physicalFilename; + + /** + * The source code of the file being linted. + * @type {SourceCode} + */ + sourceCode; + + /** + * The parser options for the file being linted. + * @type {Record} + * @deprecated Use `languageOptions` instead. + */ + parserOptions; + + /** + * The path to the parser used to parse this file. + * @type {string} + * @deprecated No longer supported. + */ + parserPath; + + /** + * The language options used when parsing this file. + * @type {Record} + */ + languageOptions; + + /** + * The settings for the file being linted. + * @type {Record} + */ + settings; + + /** + * Creates a new instance. + * @param {Object} config The configuration object for the file context. + * @param {string} config.cwd The current working directory. + * @param {string} config.filename The filename of the file being linted. + * @param {string} config.physicalFilename The physical filename of the file being linted. + * @param {SourceCode} config.sourceCode The source code of the file being linted. + * @param {Record} config.parserOptions The parser options for the file being linted. + * @param {string} config.parserPath The path to the parser used to parse this file. + * @param {Record} config.languageOptions The language options used when parsing this file. + * @param {Record} config.settings The settings for the file being linted. + */ + constructor({ + cwd, + filename, + physicalFilename, + sourceCode, + parserOptions, + parserPath, + languageOptions, + settings, + }) { + this.cwd = cwd; + this.filename = filename; + this.physicalFilename = physicalFilename; + this.sourceCode = sourceCode; + this.parserOptions = parserOptions; + this.parserPath = parserPath; + this.languageOptions = languageOptions; + this.settings = settings; + + Object.freeze(this); + } + + /** + * Gets the current working directory. + * @returns {string} The current working directory. + * @deprecated Use `cwd` instead. + */ + getCwd() { + return this.cwd; + } + + /** + * Gets the filename of the file being linted. + * @returns {string} The filename of the file being linted. + * @deprecated Use `filename` instead. + */ + getFilename() { + return this.filename; + } + + /** + * Gets the physical filename of the file being linted. + * @returns {string} The physical filename of the file being linted. + * @deprecated Use `physicalFilename` instead. + */ + getPhysicalFilename() { + return this.physicalFilename; + } + + /** + * Gets the source code of the file being linted. + * @returns {SourceCode} The source code of the file being linted. + * @deprecated Use `sourceCode` instead. + */ + getSourceCode() { + return this.sourceCode; + } } exports.FileContext = FileContext; diff --git a/lib/linter/index.js b/lib/linter/index.js index 9e5397768bb9..5a86f71cd13b 100644 --- a/lib/linter/index.js +++ b/lib/linter/index.js @@ -4,8 +4,8 @@ const { Linter } = require("./linter"); const SourceCodeFixer = require("./source-code-fixer"); module.exports = { - Linter, + Linter, - // For testers. - SourceCodeFixer + // For testers. + SourceCodeFixer, }; diff --git a/lib/linter/interpolate.js b/lib/linter/interpolate.js index 5f4ff9227363..b1275181aed3 100644 --- a/lib/linter/interpolate.js +++ b/lib/linter/interpolate.js @@ -14,7 +14,7 @@ * @returns {RegExp} Global regular expression matching placeholders */ function getPlaceholderMatcher() { - return /\{\{([^{}]+?)\}\}/gu; + return /\{\{([^{}]+?)\}\}/gu; } /** @@ -25,26 +25,26 @@ function getPlaceholderMatcher() { * @returns {string} Message with replaced placeholders */ function interpolate(text, data) { - if (!data) { - return text; - } + if (!data) { + return text; + } - const matcher = getPlaceholderMatcher(); + const matcher = getPlaceholderMatcher(); - // Substitution content for any {{ }} markers. - return text.replace(matcher, (fullMatch, termWithWhitespace) => { - const term = termWithWhitespace.trim(); + // Substitution content for any {{ }} markers. + return text.replace(matcher, (fullMatch, termWithWhitespace) => { + const term = termWithWhitespace.trim(); - if (term in data) { - return data[term]; - } + if (term in data) { + return data[term]; + } - // Preserve old behavior: If parameter name not provided, don't replace it. - return fullMatch; - }); + // Preserve old behavior: If parameter name not provided, don't replace it. + return fullMatch; + }); } module.exports = { - getPlaceholderMatcher, - interpolate + getPlaceholderMatcher, + interpolate, }; diff --git a/lib/linter/linter.js b/lib/linter/linter.js index 6db9c13b706a..ba8c6ab5e173 100644 --- a/lib/linter/linter.js +++ b/lib/linter/linter.js @@ -10,46 +10,55 @@ // Requirements //------------------------------------------------------------------------------ -const - path = require("node:path"), - eslintScope = require("eslint-scope"), - evk = require("eslint-visitor-keys"), - espree = require("espree"), - merge = require("lodash.merge"), - pkg = require("../../package.json"), - { - Legacy: { - ConfigOps, - ConfigValidator, - environments: BuiltInEnvironments - } - } = require("@eslint/eslintrc/universal"), - Traverser = require("../shared/traverser"), - { SourceCode } = require("../languages/js/source-code"), - applyDisableDirectives = require("./apply-disable-directives"), - { ConfigCommentParser } = require("@eslint/plugin-kit"), - NodeEventGenerator = require("./node-event-generator"), - createReportTranslator = require("./report-translator"), - Rules = require("./rules"), - createEmitter = require("./safe-emitter"), - SourceCodeFixer = require("./source-code-fixer"), - timing = require("./timing"), - ruleReplacements = require("../../conf/replacements.json"); +const path = require("node:path"), + eslintScope = require("eslint-scope"), + evk = require("eslint-visitor-keys"), + espree = require("espree"), + merge = require("lodash.merge"), + pkg = require("../../package.json"), + { + Legacy: { + ConfigOps, + ConfigValidator, + environments: BuiltInEnvironments, + }, + } = require("@eslint/eslintrc/universal"), + Traverser = require("../shared/traverser"), + { SourceCode } = require("../languages/js/source-code"), + applyDisableDirectives = require("./apply-disable-directives"), + { ConfigCommentParser } = require("@eslint/plugin-kit"), + NodeEventGenerator = require("./node-event-generator"), + createReportTranslator = require("./report-translator"), + Rules = require("./rules"), + createEmitter = require("./safe-emitter"), + SourceCodeFixer = require("./source-code-fixer"), + timing = require("./timing"), + ruleReplacements = require("../../conf/replacements.json"); const { getRuleFromConfig } = require("../config/flat-config-helpers"); const { FlatConfigArray } = require("../config/flat-config-array"); const { startTime, endTime } = require("../shared/stats"); const { RuleValidator } = require("../config/rule-validator"); const { assertIsRuleSeverity } = require("../config/flat-config-schema"); -const { normalizeSeverityToString, normalizeSeverityToNumber } = require("../shared/severity"); +const { + normalizeSeverityToString, + normalizeSeverityToNumber, +} = require("../shared/severity"); const { deepMergeArrays } = require("../shared/deep-merge-arrays"); const jslang = require("../languages/js"); -const { activeFlags, inactiveFlags, getInactivityReasonMessage } = require("../shared/flags"); +const { + activeFlags, + inactiveFlags, + getInactivityReasonMessage, +} = require("../shared/flags"); const debug = require("debug")("eslint:linter"); const MAX_AUTOFIX_PASSES = 10; const DEFAULT_PARSER_NAME = "espree"; const DEFAULT_ECMA_VERSION = 5; const commentParser = new ConfigCommentParser(); -const DEFAULT_ERROR_LOC = { start: { line: 1, column: 0 }, end: { line: 1, column: 1 } }; +const DEFAULT_ERROR_LOC = { + start: { line: 1, column: 0 }, + end: { line: 1, column: 1 }, +}; const parserSymbol = Symbol.for("eslint.RuleTester.parser"); const { LATEST_ECMA_VERSION } = require("../../conf/ecma-version"); const { VFile } = require("./vfile"); @@ -79,7 +88,6 @@ const STEP_KIND_CALL = 2; /** @typedef {import("@eslint/core").RuleConfig} RuleConfig */ /** @typedef {import("../types").Linter.StringSeverity} StringSeverity */ - /* eslint-disable jsdoc/valid-types -- https://github.com/jsdoc-type-pratt-parser/jsdoc-type-pratt-parser/issues/4#issuecomment-778805577 */ /** * @template T @@ -157,7 +165,7 @@ const STEP_KIND_CALL = 2; * @returns {boolean} True if the parser is Espree or false if not. */ function isEspree(parser) { - return !!(parser === espree || parser[parserSymbol] === espree); + return !!(parser === espree || parser[parserSymbol] === espree); } /** @@ -169,72 +177,80 @@ function isEspree(parser) { * @param {{exportedVariables: Object, enabledGlobals: Object}} commentDirectives Directives from comment configuration * @returns {void} */ -function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, enabledGlobals }) { - - // Define configured global variables. - for (const id of new Set([...Object.keys(configGlobals), ...Object.keys(enabledGlobals)])) { - - /* - * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would - * typically be caught when validating a config anyway (validity for inline global comments is checked separately). - */ - const configValue = configGlobals[id] === void 0 ? void 0 : ConfigOps.normalizeConfigGlobal(configGlobals[id]); - const commentValue = enabledGlobals[id] && enabledGlobals[id].value; - const value = commentValue || configValue; - const sourceComments = enabledGlobals[id] && enabledGlobals[id].comments; - - if (value === "off") { - continue; - } - - let variable = globalScope.set.get(id); - - if (!variable) { - variable = new eslintScope.Variable(id, globalScope); - - globalScope.variables.push(variable); - globalScope.set.set(id, variable); - } - - variable.eslintImplicitGlobalSetting = configValue; - variable.eslintExplicitGlobal = sourceComments !== void 0; - variable.eslintExplicitGlobalComments = sourceComments; - variable.writeable = (value === "writable"); - } - - // mark all exported variables as such - Object.keys(exportedVariables).forEach(name => { - const variable = globalScope.set.get(name); - - if (variable) { - variable.eslintUsed = true; - variable.eslintExported = true; - } - }); - - /* - * "through" contains all references which definitions cannot be found. - * Since we augment the global scope using configuration, we need to update - * references and remove the ones that were added by configuration. - */ - globalScope.through = globalScope.through.filter(reference => { - const name = reference.identifier.name; - const variable = globalScope.set.get(name); - - if (variable) { - - /* - * Links the variable and the reference. - * And this reference is removed from `Scope#through`. - */ - reference.resolved = variable; - variable.references.push(reference); - - return false; - } - - return true; - }); +function addDeclaredGlobals( + globalScope, + configGlobals, + { exportedVariables, enabledGlobals }, +) { + // Define configured global variables. + for (const id of new Set([ + ...Object.keys(configGlobals), + ...Object.keys(enabledGlobals), + ])) { + /* + * `ConfigOps.normalizeConfigGlobal` will throw an error if a configured global value is invalid. However, these errors would + * typically be caught when validating a config anyway (validity for inline global comments is checked separately). + */ + const configValue = + configGlobals[id] === void 0 + ? void 0 + : ConfigOps.normalizeConfigGlobal(configGlobals[id]); + const commentValue = enabledGlobals[id] && enabledGlobals[id].value; + const value = commentValue || configValue; + const sourceComments = + enabledGlobals[id] && enabledGlobals[id].comments; + + if (value === "off") { + continue; + } + + let variable = globalScope.set.get(id); + + if (!variable) { + variable = new eslintScope.Variable(id, globalScope); + + globalScope.variables.push(variable); + globalScope.set.set(id, variable); + } + + variable.eslintImplicitGlobalSetting = configValue; + variable.eslintExplicitGlobal = sourceComments !== void 0; + variable.eslintExplicitGlobalComments = sourceComments; + variable.writeable = value === "writable"; + } + + // mark all exported variables as such + Object.keys(exportedVariables).forEach(name => { + const variable = globalScope.set.get(name); + + if (variable) { + variable.eslintUsed = true; + variable.eslintExported = true; + } + }); + + /* + * "through" contains all references which definitions cannot be found. + * Since we augment the global scope using configuration, we need to update + * references and remove the ones that were added by configuration. + */ + globalScope.through = globalScope.through.filter(reference => { + const name = reference.identifier.name; + const variable = globalScope.set.get(name); + + if (variable) { + /* + * Links the variable and the reference. + * And this reference is removed from `Scope#through`. + */ + reference.resolved = variable; + variable.references.push(reference); + + return false; + } + + return true; + }); } /** @@ -244,9 +260,9 @@ function addDeclaredGlobals(globalScope, configGlobals, { exportedVariables, ena * @private */ function createMissingRuleMessage(ruleId) { - return Object.hasOwn(ruleReplacements.rules, ruleId) - ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}` - : `Definition for rule '${ruleId}' was not found.`; + return Object.hasOwn(ruleReplacements.rules, ruleId) + ? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements.rules[ruleId].join(", ")}` + : `Definition for rule '${ruleId}' was not found.`; } /** @@ -261,21 +277,24 @@ function createMissingRuleMessage(ruleId) { * @param {Language} language The language to use to adjust the location information. * @returns {Object} The updated location. */ -function updateLocationInformation({ line, column, endLine, endColumn }, language) { - - const columnOffset = language.columnStart === 1 ? 0 : 1; - const lineOffset = language.lineStart === 1 ? 0 : 1; - - // calculate separately to account for undefined - const finalEndLine = endLine === void 0 ? endLine : endLine + lineOffset; - const finalEndColumn = endColumn === void 0 ? endColumn : endColumn + columnOffset; - - return { - line: line + lineOffset, - column: column + columnOffset, - endLine: finalEndLine, - endColumn: finalEndColumn - }; +function updateLocationInformation( + { line, column, endLine, endColumn }, + language, +) { + const columnOffset = language.columnStart === 1 ? 0 : 1; + const lineOffset = language.lineStart === 1 ? 0 : 1; + + // calculate separately to account for undefined + const finalEndLine = endLine === void 0 ? endLine : endLine + lineOffset; + const finalEndColumn = + endColumn === void 0 ? endColumn : endColumn + columnOffset; + + return { + line: line + lineOffset, + column: column + columnOffset, + endLine: finalEndLine, + endColumn: finalEndColumn, + }; } /** @@ -290,31 +309,34 @@ function updateLocationInformation({ line, column, endLine, endColumn }, languag * @private */ function createLintingProblem(options) { - const { - ruleId = null, - loc = DEFAULT_ERROR_LOC, - message = createMissingRuleMessage(options.ruleId), - severity = 2, - - // fallback for eslintrc mode - language = { - columnStart: 0, - lineStart: 1 - } - } = options; - - return { - ruleId, - message, - ...updateLocationInformation({ - line: loc.start.line, - column: loc.start.column, - endLine: loc.end.line, - endColumn: loc.end.column - }, language), - severity, - nodeType: null - }; + const { + ruleId = null, + loc = DEFAULT_ERROR_LOC, + message = createMissingRuleMessage(options.ruleId), + severity = 2, + + // fallback for eslintrc mode + language = { + columnStart: 0, + lineStart: 1, + }, + } = options; + + return { + ruleId, + message, + ...updateLocationInformation( + { + line: loc.start.line, + column: loc.start.column, + endLine: loc.end.line, + endColumn: loc.end.column, + }, + language, + ), + severity, + nodeType: null, + }; } /** @@ -324,7 +346,7 @@ function createLintingProblem(options) { * @returns {Array} The value as an array. */ function asArray(value) { - return Array.isArray(value) ? value : [value]; + return Array.isArray(value) ? value : [value]; } /** @@ -338,39 +360,60 @@ function asArray(value) { * @param {"error"|"warn"} severity The severity to report. * @returns {void} */ -function addProblemIfSameSeverityAndOptions(config, loc, problems, ruleId, ruleOptions, ruleOptionsInline, severity) { - const existingConfigRaw = config.rules?.[ruleId]; - const existingConfig = existingConfigRaw ? asArray(existingConfigRaw) : ["off"]; - const existingSeverity = normalizeSeverityToString(existingConfig[0]); - const inlineSeverity = normalizeSeverityToString(ruleOptions[0]); - const sameSeverity = existingSeverity === inlineSeverity; - - if (!sameSeverity) { - return; - } - - const alreadyConfigured = existingConfigRaw - ? `is already configured to '${existingSeverity}'` - : "is not enabled so can't be turned off"; - let message; - - if ((existingConfig.length === 1 && ruleOptions.length === 1) || existingSeverity === "off") { - message = `Unused inline config ('${ruleId}' ${alreadyConfigured}).`; - } else if (!containsDifferentProperty(ruleOptions.slice(1), existingConfig.slice(1))) { - message = ruleOptionsInline.length === 1 - ? `Unused inline config ('${ruleId}' ${alreadyConfigured}).` - : `Unused inline config ('${ruleId}' ${alreadyConfigured} with the same options).`; - } - - if (message) { - problems.push(createLintingProblem({ - ruleId: null, - message, - loc, - language: config.language, - severity: normalizeSeverityToNumber(severity) - })); - } +function addProblemIfSameSeverityAndOptions( + config, + loc, + problems, + ruleId, + ruleOptions, + ruleOptionsInline, + severity, +) { + const existingConfigRaw = config.rules?.[ruleId]; + const existingConfig = existingConfigRaw + ? asArray(existingConfigRaw) + : ["off"]; + const existingSeverity = normalizeSeverityToString(existingConfig[0]); + const inlineSeverity = normalizeSeverityToString(ruleOptions[0]); + const sameSeverity = existingSeverity === inlineSeverity; + + if (!sameSeverity) { + return; + } + + const alreadyConfigured = existingConfigRaw + ? `is already configured to '${existingSeverity}'` + : "is not enabled so can't be turned off"; + let message; + + if ( + (existingConfig.length === 1 && ruleOptions.length === 1) || + existingSeverity === "off" + ) { + message = `Unused inline config ('${ruleId}' ${alreadyConfigured}).`; + } else if ( + !containsDifferentProperty( + ruleOptions.slice(1), + existingConfig.slice(1), + ) + ) { + message = + ruleOptionsInline.length === 1 + ? `Unused inline config ('${ruleId}' ${alreadyConfigured}).` + : `Unused inline config ('${ruleId}' ${alreadyConfigured} with the same options).`; + } + + if (message) { + problems.push( + createLintingProblem({ + ruleId: null, + message, + loc, + language: config.language, + severity: normalizeSeverityToNumber(severity), + }), + ); + } } /** @@ -386,57 +429,61 @@ function addProblemIfSameSeverityAndOptions(config, loc, problems, ruleId, ruleO * @param {SourceCode} sourceCode The SourceCode object to get comments from. * @returns {Object} Directives and problems from the comment */ -function createDisableDirectives({ type, value, justification, node }, ruleMapper, language, sourceCode) { - const ruleIds = Object.keys(commentParser.parseListConfig(value)); - const directiveRules = ruleIds.length ? ruleIds : [null]; - const result = { - directives: [], // valid disable directives - directiveProblems: [] // problems in directives - }; - const parentDirective = { node, value, ruleIds }; - - for (const ruleId of directiveRules) { - - const loc = sourceCode.getLoc(node); - - // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/) - if (ruleId === null || !!ruleMapper(ruleId)) { - - - if (type === "disable-next-line") { - const { line, column } = updateLocationInformation( - loc.end, - language - ); - - result.directives.push({ - parentDirective, - type, - line, - column, - ruleId, - justification - }); - } else { - const { line, column } = updateLocationInformation( - loc.start, - language - ); - - result.directives.push({ - parentDirective, - type, - line, - column, - ruleId, - justification - }); - } - } else { - result.directiveProblems.push(createLintingProblem({ ruleId, loc, language })); - } - } - return result; +function createDisableDirectives( + { type, value, justification, node }, + ruleMapper, + language, + sourceCode, +) { + const ruleIds = Object.keys(commentParser.parseListConfig(value)); + const directiveRules = ruleIds.length ? ruleIds : [null]; + const result = { + directives: [], // valid disable directives + directiveProblems: [], // problems in directives + }; + const parentDirective = { node, value, ruleIds }; + + for (const ruleId of directiveRules) { + const loc = sourceCode.getLoc(node); + + // push to directives, if the rule is defined(including null, e.g. /*eslint enable*/) + if (ruleId === null || !!ruleMapper(ruleId)) { + if (type === "disable-next-line") { + const { line, column } = updateLocationInformation( + loc.end, + language, + ); + + result.directives.push({ + parentDirective, + type, + line, + column, + ruleId, + justification, + }); + } else { + const { line, column } = updateLocationInformation( + loc.start, + language, + ); + + result.directives.push({ + parentDirective, + type, + line, + column, + ruleId, + justification, + }); + } + } else { + result.directiveProblems.push( + createLintingProblem({ ruleId, loc, language }), + ); + } + } + return result; } /** @@ -450,223 +497,265 @@ function createDisableDirectives({ type, value, justification, node }, ruleMappe * @returns {{configuredRules: Object, enabledGlobals: {value:string,comment:Token}[], exportedVariables: Object, problems: LintMessage[], disableDirectives: DisableDirective[]}} * A collection of the directive comments that were found, along with any problems that occurred when parsing */ -function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) { - const configuredRules = {}; - const enabledGlobals = Object.create(null); - const exportedVariables = {}; - const problems = []; - const disableDirectives = []; - const validator = new ConfigValidator({ - builtInRules: Rules - }); - - sourceCode.getInlineConfigNodes().filter(token => token.type !== "Shebang").forEach(comment => { - - const directive = commentParser.parseDirective(comment.value); - - if (!directive) { - return; - } - - const { - label, - value, - justification: justificationPart - } = directive; - - const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test(label); - - if (comment.type === "Line" && !lineCommentSupported) { - return; - } - - const loc = sourceCode.getLoc(comment); - - if (warnInlineConfig) { - const kind = comment.type === "Block" ? `/*${label}*/` : `//${label}`; - - problems.push(createLintingProblem({ - ruleId: null, - message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`, - loc, - severity: 1 - })); - return; - } - - if (label === "eslint-disable-line" && loc.start.line !== loc.end.line) { - const message = `${label} comment should not span multiple lines.`; - - problems.push(createLintingProblem({ - ruleId: null, - message, - loc - })); - return; - } - - switch (label) { - case "eslint-disable": - case "eslint-enable": - case "eslint-disable-next-line": - case "eslint-disable-line": { - const directiveType = label.slice("eslint-".length); - const { directives, directiveProblems } = createDisableDirectives({ - type: directiveType, - value, - justification: justificationPart, - node: comment - }, ruleMapper, jslang, sourceCode); - - disableDirectives.push(...directives); - problems.push(...directiveProblems); - break; - } - - case "exported": - Object.assign(exportedVariables, commentParser.parseListConfig(value)); - break; - - case "globals": - case "global": - for (const [id, idSetting] of Object.entries(commentParser.parseStringConfig(value))) { - let normalizedValue; - - try { - normalizedValue = ConfigOps.normalizeConfigGlobal(idSetting); - } catch (err) { - problems.push(createLintingProblem({ - ruleId: null, - loc, - message: err.message - })); - continue; - } - - if (enabledGlobals[id]) { - enabledGlobals[id].comments.push(comment); - enabledGlobals[id].value = normalizedValue; - } else { - enabledGlobals[id] = { - comments: [comment], - value: normalizedValue - }; - } - } - break; - - case "eslint": { - const parseResult = commentParser.parseJSONLikeConfig(value); - - if (parseResult.ok) { - Object.keys(parseResult.config).forEach(name => { - const rule = ruleMapper(name); - const ruleValue = parseResult.config[name]; - - if (!rule) { - problems.push(createLintingProblem({ ruleId: name, loc })); - return; - } - - if (Object.hasOwn(configuredRules, name)) { - problems.push(createLintingProblem({ - message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, - loc - })); - return; - } - - let ruleOptions = asArray(ruleValue); - - /* - * If the rule was already configured, inline rule configuration that - * only has severity should retain options from the config and just override the severity. - * - * Example: - * - * { - * rules: { - * curly: ["error", "multi"] - * } - * } - * - * /* eslint curly: ["warn"] * / - * - * Results in: - * - * curly: ["warn", "multi"] - */ - if ( - - /* - * If inline config for the rule has only severity - */ - ruleOptions.length === 1 && - - /* - * And the rule was already configured - */ - config.rules && Object.hasOwn(config.rules, name) - ) { - - /* - * Then use severity from the inline config and options from the provided config - */ - ruleOptions = [ - ruleOptions[0], // severity from the inline config - ...asArray(config.rules[name]).slice(1) // options from the provided config - ]; - } - - try { - validator.validateRuleOptions(rule, name, ruleOptions); - } catch (err) { - - /* - * If the rule has invalid `meta.schema`, throw the error because - * this is not an invalid inline configuration but an invalid rule. - */ - if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") { - throw err; - } - - problems.push(createLintingProblem({ - ruleId: name, - message: err.message, - loc - })); - - // do not apply the config, if found invalid options. - return; - } - - configuredRules[name] = ruleOptions; - }); - } else { - const problem = createLintingProblem({ - ruleId: null, - loc, - message: parseResult.error.message - }); - - problem.fatal = true; - problems.push(problem); - } - - break; - } - - // no default - } - }); - - return { - configuredRules, - enabledGlobals, - exportedVariables, - problems, - disableDirectives - }; +function getDirectiveComments( + sourceCode, + ruleMapper, + warnInlineConfig, + config, +) { + const configuredRules = {}; + const enabledGlobals = Object.create(null); + const exportedVariables = {}; + const problems = []; + const disableDirectives = []; + const validator = new ConfigValidator({ + builtInRules: Rules, + }); + + sourceCode + .getInlineConfigNodes() + .filter(token => token.type !== "Shebang") + .forEach(comment => { + const directive = commentParser.parseDirective(comment.value); + + if (!directive) { + return; + } + + const { + label, + value, + justification: justificationPart, + } = directive; + + const lineCommentSupported = /^eslint-disable-(next-)?line$/u.test( + label, + ); + + if (comment.type === "Line" && !lineCommentSupported) { + return; + } + + const loc = sourceCode.getLoc(comment); + + if (warnInlineConfig) { + const kind = + comment.type === "Block" ? `/*${label}*/` : `//${label}`; + + problems.push( + createLintingProblem({ + ruleId: null, + message: `'${kind}' has no effect because you have 'noInlineConfig' setting in ${warnInlineConfig}.`, + loc, + severity: 1, + }), + ); + return; + } + + if ( + label === "eslint-disable-line" && + loc.start.line !== loc.end.line + ) { + const message = `${label} comment should not span multiple lines.`; + + problems.push( + createLintingProblem({ + ruleId: null, + message, + loc, + }), + ); + return; + } + + switch (label) { + case "eslint-disable": + case "eslint-enable": + case "eslint-disable-next-line": + case "eslint-disable-line": { + const directiveType = label.slice("eslint-".length); + const { directives, directiveProblems } = + createDisableDirectives( + { + type: directiveType, + value, + justification: justificationPart, + node: comment, + }, + ruleMapper, + jslang, + sourceCode, + ); + + disableDirectives.push(...directives); + problems.push(...directiveProblems); + break; + } + + case "exported": + Object.assign( + exportedVariables, + commentParser.parseListConfig(value), + ); + break; + + case "globals": + case "global": + for (const [id, idSetting] of Object.entries( + commentParser.parseStringConfig(value), + )) { + let normalizedValue; + + try { + normalizedValue = + ConfigOps.normalizeConfigGlobal(idSetting); + } catch (err) { + problems.push( + createLintingProblem({ + ruleId: null, + loc, + message: err.message, + }), + ); + continue; + } + + if (enabledGlobals[id]) { + enabledGlobals[id].comments.push(comment); + enabledGlobals[id].value = normalizedValue; + } else { + enabledGlobals[id] = { + comments: [comment], + value: normalizedValue, + }; + } + } + break; + + case "eslint": { + const parseResult = + commentParser.parseJSONLikeConfig(value); + + if (parseResult.ok) { + Object.keys(parseResult.config).forEach(name => { + const rule = ruleMapper(name); + const ruleValue = parseResult.config[name]; + + if (!rule) { + problems.push( + createLintingProblem({ ruleId: name, loc }), + ); + return; + } + + if (Object.hasOwn(configuredRules, name)) { + problems.push( + createLintingProblem({ + message: `Rule "${name}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, + loc, + }), + ); + return; + } + + let ruleOptions = asArray(ruleValue); + + /* + * If the rule was already configured, inline rule configuration that + * only has severity should retain options from the config and just override the severity. + * + * Example: + * + * { + * rules: { + * curly: ["error", "multi"] + * } + * } + * + * /* eslint curly: ["warn"] * / + * + * Results in: + * + * curly: ["warn", "multi"] + */ + if ( + /* + * If inline config for the rule has only severity + */ + ruleOptions.length === 1 && + /* + * And the rule was already configured + */ + config.rules && + Object.hasOwn(config.rules, name) + ) { + /* + * Then use severity from the inline config and options from the provided config + */ + ruleOptions = [ + ruleOptions[0], // severity from the inline config + ...asArray(config.rules[name]).slice(1), // options from the provided config + ]; + } + + try { + validator.validateRuleOptions( + rule, + name, + ruleOptions, + ); + } catch (err) { + /* + * If the rule has invalid `meta.schema`, throw the error because + * this is not an invalid inline configuration but an invalid rule. + */ + if ( + err.code === + "ESLINT_INVALID_RULE_OPTIONS_SCHEMA" + ) { + throw err; + } + + problems.push( + createLintingProblem({ + ruleId: name, + message: err.message, + loc, + }), + ); + + // do not apply the config, if found invalid options. + return; + } + + configuredRules[name] = ruleOptions; + }); + } else { + const problem = createLintingProblem({ + ruleId: null, + loc, + message: parseResult.error.message, + }); + + problem.fatal = true; + problems.push(problem); + } + + break; + } + + // no default + } + }); + + return { + configuredRules, + enabledGlobals, + exportedVariables, + problems, + disableDirectives, + }; } /** @@ -678,32 +767,39 @@ function getDirectiveComments(sourceCode, ruleMapper, warnInlineConfig, config) * A collection of the directive comments that were found, along with any problems that occurred when parsing */ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) { - const disableDirectives = []; - const problems = []; - - if (sourceCode.getDisableDirectives) { - const { - directives: directivesSources, - problems: directivesProblems - } = sourceCode.getDisableDirectives(); - - problems.push(...directivesProblems.map(directiveProblem => createLintingProblem({ - ...directiveProblem, - language - }))); - - directivesSources.forEach(directive => { - const { directives, directiveProblems } = createDisableDirectives(directive, ruleMapper, language, sourceCode); - - disableDirectives.push(...directives); - problems.push(...directiveProblems); - }); - } - - return { - problems, - disableDirectives - }; + const disableDirectives = []; + const problems = []; + + if (sourceCode.getDisableDirectives) { + const { directives: directivesSources, problems: directivesProblems } = + sourceCode.getDisableDirectives(); + + problems.push( + ...directivesProblems.map(directiveProblem => + createLintingProblem({ + ...directiveProblem, + language, + }), + ), + ); + + directivesSources.forEach(directive => { + const { directives, directiveProblems } = createDisableDirectives( + directive, + ruleMapper, + language, + sourceCode, + ); + + disableDirectives.push(...directives); + problems.push(...directiveProblems); + }); + } + + return { + problems, + disableDirectives, + }; } /** @@ -713,18 +809,17 @@ function getDirectiveCommentsForFlatConfig(sourceCode, ruleMapper, language) { * @returns {number} normalized ECMAScript version */ function normalizeEcmaVersion(parser, ecmaVersion) { - - if (isEspree(parser)) { - if (ecmaVersion === "latest") { - return espree.latestEcmaVersion; - } - } - - /* - * Calculate ECMAScript edition number from official year version starting with - * ES2015, which corresponds with ES6 (or a difference of 2009). - */ - return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion; + if (isEspree(parser)) { + if (ecmaVersion === "latest") { + return espree.latestEcmaVersion; + } + } + + /* + * Calculate ECMAScript edition number from official year version starting with + * ES2015, which corresponds with ES6 (or a difference of 2009). + */ + return ecmaVersion >= 2015 ? ecmaVersion - 2009 : ecmaVersion; } /** @@ -734,29 +829,28 @@ function normalizeEcmaVersion(parser, ecmaVersion) { * @returns {number} normalized ECMAScript version */ function normalizeEcmaVersionForLanguageOptions(ecmaVersion) { - - switch (ecmaVersion) { - case 3: - return 3; - - // void 0 = no ecmaVersion specified so use the default - case 5: - case void 0: - return 5; - - default: - if (typeof ecmaVersion === "number") { - return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009; - } - } - - /* - * We default to the latest supported ecmaVersion for everything else. - * Remember, this is for languageOptions.ecmaVersion, which sets the version - * that is used for a number of processes inside of ESLint. It's normally - * safe to assume people want the latest unless otherwise specified. - */ - return LATEST_ECMA_VERSION; + switch (ecmaVersion) { + case 3: + return 3; + + // void 0 = no ecmaVersion specified so use the default + case 5: + case void 0: + return 5; + + default: + if (typeof ecmaVersion === "number") { + return ecmaVersion >= 2015 ? ecmaVersion : ecmaVersion + 2009; + } + } + + /* + * We default to the latest supported ecmaVersion for everything else. + * Remember, this is for languageOptions.ecmaVersion, which sets the version + * that is used for a number of processes inside of ESLint. It's normally + * safe to assume people want the latest unless otherwise specified. + */ + return LATEST_ECMA_VERSION; } const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu; @@ -767,20 +861,22 @@ const eslintEnvPattern = /\/\*\s*eslint-env\s(.+?)(?:\*\/|$)/gsu; * @returns {Object|null} A result of parseListConfig() with "eslint-env *" comment. */ function findEslintEnv(text) { - let match, retv; - - eslintEnvPattern.lastIndex = 0; - - while ((match = eslintEnvPattern.exec(text)) !== null) { - if (match[0].endsWith("*/")) { - retv = Object.assign( - retv || {}, - commentParser.parseListConfig(commentParser.parseDirective(match[0].slice(2, -2)).value) - ); - } - } - - return retv; + let match, retv; + + eslintEnvPattern.lastIndex = 0; + + while ((match = eslintEnvPattern.exec(text)) !== null) { + if (match[0].endsWith("*/")) { + retv = Object.assign( + retv || {}, + commentParser.parseListConfig( + commentParser.parseDirective(match[0].slice(2, -2)).value, + ), + ); + } + } + + return retv; } /** @@ -796,10 +892,10 @@ function findEslintEnv(text) { * @returns {string} The normalized filename. */ function normalizeFilename(filename) { - const parts = filename.split(path.sep); - const index = parts.lastIndexOf(""); + const parts = filename.split(path.sep); + const index = parts.lastIndexOf(""); - return index === -1 ? filename : parts.slice(index).join(path.sep); + return index === -1 ? filename : parts.slice(index).join(path.sep); } /** @@ -810,49 +906,63 @@ function normalizeFilename(filename) { * @returns {Required & InternalOptions} Normalized options */ function normalizeVerifyOptions(providedOptions, config) { - - const linterOptions = config.linterOptions || config; - - // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat - const disableInlineConfig = linterOptions.noInlineConfig === true; - const ignoreInlineConfig = providedOptions.allowInlineConfig === false; - const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig - ? ` (${config.configNameOfNoInlineConfig})` - : ""; - - let reportUnusedDisableDirectives = providedOptions.reportUnusedDisableDirectives; - - if (typeof reportUnusedDisableDirectives === "boolean") { - reportUnusedDisableDirectives = reportUnusedDisableDirectives ? "error" : "off"; - } - if (typeof reportUnusedDisableDirectives !== "string") { - if (typeof linterOptions.reportUnusedDisableDirectives === "boolean") { - reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives ? "warn" : "off"; - } else { - reportUnusedDisableDirectives = linterOptions.reportUnusedDisableDirectives === void 0 ? "off" : normalizeSeverityToString(linterOptions.reportUnusedDisableDirectives); - } - } - - const reportUnusedInlineConfigs = linterOptions.reportUnusedInlineConfigs === void 0 ? "off" : normalizeSeverityToString(linterOptions.reportUnusedInlineConfigs); - - let ruleFilter = providedOptions.ruleFilter; - - if (typeof ruleFilter !== "function") { - ruleFilter = () => true; - } - - return { - filename: normalizeFilename(providedOptions.filename || ""), - allowInlineConfig: !ignoreInlineConfig, - warnInlineConfig: disableInlineConfig && !ignoreInlineConfig - ? `your config${configNameOfNoInlineConfig}` - : null, - reportUnusedDisableDirectives, - reportUnusedInlineConfigs, - disableFixes: Boolean(providedOptions.disableFixes), - stats: providedOptions.stats, - ruleFilter - }; + const linterOptions = config.linterOptions || config; + + // .noInlineConfig for eslintrc, .linterOptions.noInlineConfig for flat + const disableInlineConfig = linterOptions.noInlineConfig === true; + const ignoreInlineConfig = providedOptions.allowInlineConfig === false; + const configNameOfNoInlineConfig = config.configNameOfNoInlineConfig + ? ` (${config.configNameOfNoInlineConfig})` + : ""; + + let reportUnusedDisableDirectives = + providedOptions.reportUnusedDisableDirectives; + + if (typeof reportUnusedDisableDirectives === "boolean") { + reportUnusedDisableDirectives = reportUnusedDisableDirectives + ? "error" + : "off"; + } + if (typeof reportUnusedDisableDirectives !== "string") { + if (typeof linterOptions.reportUnusedDisableDirectives === "boolean") { + reportUnusedDisableDirectives = + linterOptions.reportUnusedDisableDirectives ? "warn" : "off"; + } else { + reportUnusedDisableDirectives = + linterOptions.reportUnusedDisableDirectives === void 0 + ? "off" + : normalizeSeverityToString( + linterOptions.reportUnusedDisableDirectives, + ); + } + } + + const reportUnusedInlineConfigs = + linterOptions.reportUnusedInlineConfigs === void 0 + ? "off" + : normalizeSeverityToString( + linterOptions.reportUnusedInlineConfigs, + ); + + let ruleFilter = providedOptions.ruleFilter; + + if (typeof ruleFilter !== "function") { + ruleFilter = () => true; + } + + return { + filename: normalizeFilename(providedOptions.filename || ""), + allowInlineConfig: !ignoreInlineConfig, + warnInlineConfig: + disableInlineConfig && !ignoreInlineConfig + ? `your config${configNameOfNoInlineConfig}` + : null, + reportUnusedDisableDirectives, + reportUnusedInlineConfigs, + disableFixes: Boolean(providedOptions.disableFixes), + stats: providedOptions.stats, + ruleFilter, + }; } /** @@ -863,25 +973,36 @@ function normalizeVerifyOptions(providedOptions, config) { * @returns {ParserOptions} Resulting parser options after merge */ function resolveParserOptions(parser, providedOptions, enabledEnvironments) { - - const parserOptionsFromEnv = enabledEnvironments - .filter(env => env.parserOptions) - .reduce((parserOptions, env) => merge(parserOptions, env.parserOptions), {}); - const mergedParserOptions = merge(parserOptionsFromEnv, providedOptions || {}); - const isModule = mergedParserOptions.sourceType === "module"; - - if (isModule) { - - /* - * can't have global return inside of modules - * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add) - */ - mergedParserOptions.ecmaFeatures = Object.assign({}, mergedParserOptions.ecmaFeatures, { globalReturn: false }); - } - - mergedParserOptions.ecmaVersion = normalizeEcmaVersion(parser, mergedParserOptions.ecmaVersion); - - return mergedParserOptions; + const parserOptionsFromEnv = enabledEnvironments + .filter(env => env.parserOptions) + .reduce( + (parserOptions, env) => merge(parserOptions, env.parserOptions), + {}, + ); + const mergedParserOptions = merge( + parserOptionsFromEnv, + providedOptions || {}, + ); + const isModule = mergedParserOptions.sourceType === "module"; + + if (isModule) { + /* + * can't have global return inside of modules + * TODO: espree validate parserOptions.globalReturn when sourceType is setting to module.(@aladdin-add) + */ + mergedParserOptions.ecmaFeatures = Object.assign( + {}, + mergedParserOptions.ecmaFeatures, + { globalReturn: false }, + ); + } + + mergedParserOptions.ecmaVersion = normalizeEcmaVersion( + parser, + mergedParserOptions.ecmaVersion, + ); + + return mergedParserOptions; } /** @@ -892,20 +1013,20 @@ function resolveParserOptions(parser, providedOptions, enabledEnvironments) { * @param {ParserOptions} config.parserOptions The parserOptions to use. * @returns {LanguageOptions} The languageOptions equivalent. */ -function createLanguageOptions({ globals: configuredGlobals, parser, parserOptions }) { - - const { - ecmaVersion, - sourceType - } = parserOptions; - - return { - globals: configuredGlobals, - ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion), - sourceType, - parser, - parserOptions - }; +function createLanguageOptions({ + globals: configuredGlobals, + parser, + parserOptions, +}) { + const { ecmaVersion, sourceType } = parserOptions; + + return { + globals: configuredGlobals, + ecmaVersion: normalizeEcmaVersionForLanguageOptions(ecmaVersion), + sourceType, + parser, + parserOptions, + }; } /** @@ -915,11 +1036,13 @@ function createLanguageOptions({ globals: configuredGlobals, parser, parserOptio * @returns {Record} The resolved globals object */ function resolveGlobals(providedGlobals, enabledEnvironments) { - return Object.assign( - Object.create(null), - ...enabledEnvironments.filter(env => env.globals).map(env => env.globals), - providedGlobals - ); + return Object.assign( + Object.create(null), + ...enabledEnvironments + .filter(env => env.globals) + .map(env => env.globals), + providedGlobals, + ); } /** @@ -930,26 +1053,26 @@ function resolveGlobals(providedGlobals, enabledEnvironments) { * @returns {void} */ function storeTime(time, timeOpts, slots) { - const { type, key } = timeOpts; - - if (!slots.times) { - slots.times = { passes: [{}] }; - } - - const passIndex = slots.fixPasses; - - if (passIndex > slots.times.passes.length - 1) { - slots.times.passes.push({}); - } - - if (key) { - slots.times.passes[passIndex][type] ??= {}; - slots.times.passes[passIndex][type][key] ??= { total: 0 }; - slots.times.passes[passIndex][type][key].total += time; - } else { - slots.times.passes[passIndex][type] ??= { total: 0 }; - slots.times.passes[passIndex][type].total += time; - } + const { type, key } = timeOpts; + + if (!slots.times) { + slots.times = { passes: [{}] }; + } + + const passIndex = slots.fixPasses; + + if (passIndex > slots.times.passes.length - 1) { + slots.times.passes.push({}); + } + + if (key) { + slots.times.passes[passIndex][type] ??= {}; + slots.times.passes[passIndex][type][key] ??= { total: 0 }; + slots.times.passes[passIndex][type][key].total += time; + } else { + slots.times.passes[passIndex][type] ??= { total: 0 }; + slots.times.passes[passIndex][type].total += time; + } } /** @@ -959,10 +1082,10 @@ function storeTime(time, timeOpts, slots) { * @returns {Array} of rule options, empty Array if none */ function getRuleOptions(ruleConfig, defaultOptions) { - if (Array.isArray(ruleConfig)) { - return deepMergeArrays(defaultOptions, ruleConfig.slice(1)); - } - return defaultOptions ?? []; + if (Array.isArray(ruleConfig)) { + return deepMergeArrays(defaultOptions, ruleConfig.slice(1)); + } + return defaultOptions ?? []; } /** @@ -973,19 +1096,19 @@ function getRuleOptions(ruleConfig, defaultOptions) { * @returns {ScopeManager} The analysis result. */ function analyzeScope(ast, languageOptions, visitorKeys) { - const parserOptions = languageOptions.parserOptions; - const ecmaFeatures = parserOptions.ecmaFeatures || {}; - const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION; - - return eslintScope.analyze(ast, { - ignoreEval: true, - nodejsScope: ecmaFeatures.globalReturn, - impliedStrict: ecmaFeatures.impliedStrict, - ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, - sourceType: languageOptions.sourceType || "script", - childVisitorKeys: visitorKeys || evk.KEYS, - fallback: Traverser.getKeys - }); + const parserOptions = languageOptions.parserOptions; + const ecmaFeatures = parserOptions.ecmaFeatures || {}; + const ecmaVersion = languageOptions.ecmaVersion || DEFAULT_ECMA_VERSION; + + return eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: ecmaFeatures.globalReturn, + impliedStrict: ecmaFeatures.impliedStrict, + ecmaVersion: typeof ecmaVersion === "number" ? ecmaVersion : 6, + sourceType: languageOptions.sourceType || "script", + childVisitorKeys: visitorKeys || evk.KEYS, + fallback: Traverser.getKeys, + }); } /** @@ -997,17 +1120,22 @@ function analyzeScope(ast, languageOptions, visitorKeys) { * @returns {Object} A map of selector listeners provided by the rule */ function createRuleListeners(rule, ruleContext) { - - if (!rule || typeof rule !== "object" || typeof rule.create !== "function") { - throw new TypeError(`Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`); - } - - try { - return rule.create(ruleContext); - } catch (ex) { - ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`; - throw ex; - } + if ( + !rule || + typeof rule !== "object" || + typeof rule.create !== "function" + ) { + throw new TypeError( + `Error while loading rule '${ruleContext.id}': Rule must be an object with a \`create\` method`, + ); + } + + try { + return rule.create(ruleContext); + } catch (ex) { + ex.message = `Error while loading rule '${ruleContext.id}': ${ex.message}`; + throw ex; + } } /** @@ -1031,199 +1159,230 @@ function createRuleListeners(rule, ruleContext) { * @throws {Error} If traversal into a node fails. */ function runRules( - sourceCode, - configuredRules, - ruleMapper, - parserName, - language, - languageOptions, - settings, - filename, - applyDefaultOptions, - disableFixes, - cwd, - physicalFilename, - ruleFilter, - stats, - slots + sourceCode, + configuredRules, + ruleMapper, + parserName, + language, + languageOptions, + settings, + filename, + applyDefaultOptions, + disableFixes, + cwd, + physicalFilename, + ruleFilter, + stats, + slots, ) { - const emitter = createEmitter(); - - // must happen first to assign all node.parent properties - const eventQueue = sourceCode.traverse(); - - /* - * Create a frozen object with the ruleContext properties and methods that are shared by all rules. - * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the - * properties once for each rule. - */ - const sharedTraversalContext = new FileContext({ - cwd, - filename, - physicalFilename: physicalFilename || filename, - sourceCode, - parserOptions: { - ...languageOptions.parserOptions - }, - parserPath: parserName, - languageOptions, - settings - }); - - const lintingProblems = []; - - Object.keys(configuredRules).forEach(ruleId => { - const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); - - // not load disabled rules - if (severity === 0) { - return; - } - - if (ruleFilter && !ruleFilter({ ruleId, severity })) { - return; - } - - const rule = ruleMapper(ruleId); - - if (!rule) { - lintingProblems.push(createLintingProblem({ ruleId, language })); - return; - } - - const messageIds = rule.meta && rule.meta.messages; - let reportTranslator = null; - const ruleContext = Object.freeze( - Object.assign( - Object.create(sharedTraversalContext), - { - id: ruleId, - options: getRuleOptions(configuredRules[ruleId], applyDefaultOptions ? rule.meta?.defaultOptions : void 0), - report(...args) { - - /* - * Create a report translator lazily. - * In a vast majority of cases, any given rule reports zero errors on a given - * piece of code. Creating a translator lazily avoids the performance cost of - * creating a new translator function for each rule that usually doesn't get - * called. - * - * Using lazy report translators improves end-to-end performance by about 3% - * with Node 8.4.0. - */ - if (reportTranslator === null) { - reportTranslator = createReportTranslator({ - ruleId, - severity, - sourceCode, - messageIds, - disableFixes, - language - }); - } - const problem = reportTranslator(...args); - - if (problem.fix && !(rule.meta && rule.meta.fixable)) { - throw new Error("Fixable rules must set the `meta.fixable` property to \"code\" or \"whitespace\"."); - } - if (problem.suggestions && !(rule.meta && rule.meta.hasSuggestions === true)) { - if (rule.meta && rule.meta.docs && typeof rule.meta.docs.suggestion !== "undefined") { - - // Encourage migration from the former property name. - throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); - } - throw new Error("Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - } - lintingProblems.push(problem); - } - } - ) - ); - - const ruleListenersReturn = (timing.enabled || stats) - ? timing.time(ruleId, createRuleListeners, stats)(rule, ruleContext) : createRuleListeners(rule, ruleContext); - - const ruleListeners = stats ? ruleListenersReturn.result : ruleListenersReturn; - - if (stats) { - storeTime(ruleListenersReturn.tdiff, { type: "rules", key: ruleId }, slots); - } - - /** - * Include `ruleId` in error logs - * @param {Function} ruleListener A rule method that listens for a node. - * @returns {Function} ruleListener wrapped in error handler - */ - function addRuleErrorHandler(ruleListener) { - return function ruleErrorHandler(...listenerArgs) { - try { - const ruleListenerReturn = ruleListener(...listenerArgs); - - const ruleListenerResult = stats ? ruleListenerReturn.result : ruleListenerReturn; - - if (stats) { - storeTime(ruleListenerReturn.tdiff, { type: "rules", key: ruleId }, slots); - } - - return ruleListenerResult; - } catch (e) { - e.ruleId = ruleId; - throw e; - } - }; - } - - if (typeof ruleListeners === "undefined" || ruleListeners === null) { - throw new Error(`The create() function for rule '${ruleId}' did not return an object.`); - } - - // add all the selectors from the rule as listeners - Object.keys(ruleListeners).forEach(selector => { - const ruleListener = (timing.enabled || stats) - ? timing.time(ruleId, ruleListeners[selector], stats) : ruleListeners[selector]; - - emitter.on( - selector, - addRuleErrorHandler(ruleListener) - ); - }); - }); - - const eventGenerator = new NodeEventGenerator(emitter, { - visitorKeys: sourceCode.visitorKeys ?? language.visitorKeys, - fallback: Traverser.getKeys, - matchClass: language.matchesSelectorClass ?? (() => false), - nodeTypeKey: language.nodeTypeKey - }); - - for (const step of eventQueue) { - switch (step.kind) { - case STEP_KIND_VISIT: { - try { - if (step.phase === 1) { - eventGenerator.enterNode(step.target); - } else { - eventGenerator.leaveNode(step.target); - } - } catch (err) { - err.currentNode = step.target; - throw err; - } - break; - } - - case STEP_KIND_CALL: { - emitter.emit(step.target, ...step.args); - break; - } - - default: - throw new Error(`Invalid traversal step found: "${step.type}".`); - } - - } - - return lintingProblems; + const emitter = createEmitter(); + + // must happen first to assign all node.parent properties + const eventQueue = sourceCode.traverse(); + + /* + * Create a frozen object with the ruleContext properties and methods that are shared by all rules. + * All rule contexts will inherit from this object. This avoids the performance penalty of copying all the + * properties once for each rule. + */ + const sharedTraversalContext = new FileContext({ + cwd, + filename, + physicalFilename: physicalFilename || filename, + sourceCode, + parserOptions: { + ...languageOptions.parserOptions, + }, + parserPath: parserName, + languageOptions, + settings, + }); + + const lintingProblems = []; + + Object.keys(configuredRules).forEach(ruleId => { + const severity = ConfigOps.getRuleSeverity(configuredRules[ruleId]); + + // not load disabled rules + if (severity === 0) { + return; + } + + if (ruleFilter && !ruleFilter({ ruleId, severity })) { + return; + } + + const rule = ruleMapper(ruleId); + + if (!rule) { + lintingProblems.push(createLintingProblem({ ruleId, language })); + return; + } + + const messageIds = rule.meta && rule.meta.messages; + let reportTranslator = null; + const ruleContext = Object.freeze( + Object.assign(Object.create(sharedTraversalContext), { + id: ruleId, + options: getRuleOptions( + configuredRules[ruleId], + applyDefaultOptions ? rule.meta?.defaultOptions : void 0, + ), + report(...args) { + /* + * Create a report translator lazily. + * In a vast majority of cases, any given rule reports zero errors on a given + * piece of code. Creating a translator lazily avoids the performance cost of + * creating a new translator function for each rule that usually doesn't get + * called. + * + * Using lazy report translators improves end-to-end performance by about 3% + * with Node 8.4.0. + */ + if (reportTranslator === null) { + reportTranslator = createReportTranslator({ + ruleId, + severity, + sourceCode, + messageIds, + disableFixes, + language, + }); + } + const problem = reportTranslator(...args); + + if (problem.fix && !(rule.meta && rule.meta.fixable)) { + throw new Error( + 'Fixable rules must set the `meta.fixable` property to "code" or "whitespace".', + ); + } + if ( + problem.suggestions && + !(rule.meta && rule.meta.hasSuggestions === true) + ) { + if ( + rule.meta && + rule.meta.docs && + typeof rule.meta.docs.suggestion !== "undefined" + ) { + // Encourage migration from the former property name. + throw new Error( + "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint.", + ); + } + throw new Error( + "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.", + ); + } + lintingProblems.push(problem); + }, + }), + ); + + const ruleListenersReturn = + timing.enabled || stats + ? timing.time( + ruleId, + createRuleListeners, + stats, + )(rule, ruleContext) + : createRuleListeners(rule, ruleContext); + + const ruleListeners = stats + ? ruleListenersReturn.result + : ruleListenersReturn; + + if (stats) { + storeTime( + ruleListenersReturn.tdiff, + { type: "rules", key: ruleId }, + slots, + ); + } + + /** + * Include `ruleId` in error logs + * @param {Function} ruleListener A rule method that listens for a node. + * @returns {Function} ruleListener wrapped in error handler + */ + function addRuleErrorHandler(ruleListener) { + return function ruleErrorHandler(...listenerArgs) { + try { + const ruleListenerReturn = ruleListener(...listenerArgs); + + const ruleListenerResult = stats + ? ruleListenerReturn.result + : ruleListenerReturn; + + if (stats) { + storeTime( + ruleListenerReturn.tdiff, + { type: "rules", key: ruleId }, + slots, + ); + } + + return ruleListenerResult; + } catch (e) { + e.ruleId = ruleId; + throw e; + } + }; + } + + if (typeof ruleListeners === "undefined" || ruleListeners === null) { + throw new Error( + `The create() function for rule '${ruleId}' did not return an object.`, + ); + } + + // add all the selectors from the rule as listeners + Object.keys(ruleListeners).forEach(selector => { + const ruleListener = + timing.enabled || stats + ? timing.time(ruleId, ruleListeners[selector], stats) + : ruleListeners[selector]; + + emitter.on(selector, addRuleErrorHandler(ruleListener)); + }); + }); + + const eventGenerator = new NodeEventGenerator(emitter, { + visitorKeys: sourceCode.visitorKeys ?? language.visitorKeys, + fallback: Traverser.getKeys, + matchClass: language.matchesSelectorClass ?? (() => false), + nodeTypeKey: language.nodeTypeKey, + }); + + for (const step of eventQueue) { + switch (step.kind) { + case STEP_KIND_VISIT: { + try { + if (step.phase === 1) { + eventGenerator.enterNode(step.target); + } else { + eventGenerator.leaveNode(step.target); + } + } catch (err) { + err.currentNode = step.target; + throw err; + } + break; + } + + case STEP_KIND_CALL: { + emitter.emit(step.target, ...step.args); + break; + } + + default: + throw new Error( + `Invalid traversal step found: "${step.type}".`, + ); + } + } + + return lintingProblems; } /** @@ -1232,14 +1391,14 @@ function runRules( * @returns {string} The source code text. */ function ensureText(textOrSourceCode) { - if (typeof textOrSourceCode === "object") { - const { hasBOM, text } = textOrSourceCode; - const bom = hasBOM ? "\uFEFF" : ""; + if (typeof textOrSourceCode === "object") { + const { hasBOM, text } = textOrSourceCode; + const bom = hasBOM ? "\uFEFF" : ""; - return bom + text; - } + return bom + text; + } - return String(textOrSourceCode); + return String(textOrSourceCode); } /** @@ -1249,11 +1408,12 @@ function ensureText(textOrSourceCode) { * @returns {Environment|null} The environment. */ function getEnv(slots, envId) { - return ( - (slots.lastConfigArray && slots.lastConfigArray.pluginEnvironments.get(envId)) || - BuiltInEnvironments.get(envId) || - null - ); + return ( + (slots.lastConfigArray && + slots.lastConfigArray.pluginEnvironments.get(envId)) || + BuiltInEnvironments.get(envId) || + null + ); } /** @@ -1263,10 +1423,11 @@ function getEnv(slots, envId) { * @returns {Rule|null} The rule. */ function getRule(slots, ruleId) { - return ( - (slots.lastConfigArray && slots.lastConfigArray.pluginRules.get(ruleId)) || - slots.ruleMap.get(ruleId) - ); + return ( + (slots.lastConfigArray && + slots.lastConfigArray.pluginRules.get(ruleId)) || + slots.ruleMap.get(ruleId) + ); } /** @@ -1275,16 +1436,16 @@ function getRule(slots, ruleId) { * @returns {string | undefined} normalized cwd */ function normalizeCwd(cwd) { - if (cwd) { - return cwd; - } - if (typeof process === "object") { - return process.cwd(); - } - - // It's more explicit to assign the undefined - // eslint-disable-next-line no-undefined -- Consistently returning a value - return undefined; + if (cwd) { + return cwd; + } + if (typeof process === "object") { + return process.cwd(); + } + + // It's more explicit to assign the undefined + // eslint-disable-next-line no-undefined -- Consistently returning a value + return undefined; } /** @@ -1300,11 +1461,13 @@ const internalSlotsMap = new WeakMap(); * @throws {Error} If the linter is in flat config mode. */ function assertEslintrcConfig(linter) { - const { configType } = internalSlotsMap.get(linter); + const { configType } = internalSlotsMap.get(linter); - if (configType === "flat") { - throw new Error("This method cannot be used with flat config. Add your entries directly into the config array."); - } + if (configType === "flat") { + throw new Error( + "This method cannot be used with flat config. Add your entries directly into the config array.", + ); + } } //------------------------------------------------------------------------------ @@ -1316,1182 +1479,1377 @@ function assertEslintrcConfig(linter) { * @name Linter */ class Linter { - - /** - * Initialize the Linter. - * @param {Object} [config] the config object - * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. - * @param {Array} [config.flags] the feature flags to enable. - * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used. - */ - constructor({ cwd, configType = "flat", flags = [] } = {}) { - - const processedFlags = []; - - flags.forEach(flag => { - if (inactiveFlags.has(flag)) { - const inactiveFlagData = inactiveFlags.get(flag); - const inactivityReason = getInactivityReasonMessage(inactiveFlagData); - - if (typeof inactiveFlagData.replacedBy === "undefined") { - throw new Error(`The flag '${flag}' is inactive: ${inactivityReason}`); - } - - // if there's a replacement, enable it instead of original - if (typeof inactiveFlagData.replacedBy === "string") { - processedFlags.push(inactiveFlagData.replacedBy); - } - - globalThis.process?.emitWarning?.( - `The flag '${flag}' is inactive: ${inactivityReason}`, - `ESLintInactiveFlag_${flag}` - ); - - return; - } - - if (!activeFlags.has(flag)) { - throw new Error(`Unknown flag '${flag}'.`); - } - - processedFlags.push(flag); - }); - - internalSlotsMap.set(this, { - cwd: normalizeCwd(cwd), - flags: processedFlags, - lastConfigArray: null, - lastSourceCode: null, - lastSuppressedMessages: [], - configType, // TODO: Remove after flat config conversion - parserMap: new Map([["espree", espree]]), - ruleMap: new Rules() - }); - - this.version = pkg.version; - } - - /** - * Getter for package version. - * @static - * @returns {string} The version from package.json. - */ - static get version() { - return pkg.version; - } - - /** - * Indicates if the given feature flag is enabled for this instance. - * @param {string} flag The feature flag to check. - * @returns {boolean} `true` if the feature flag is enabled, `false` if not. - */ - hasFlag(flag) { - return internalSlotsMap.get(this).flags.includes(flag); - } - - /** - * Lint using eslintrc and without processors. - * @param {VFile} file The file to lint. - * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. - * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. - * @throws {Error} If during rule execution. - * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. - */ - #eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions) { - - const slots = internalSlotsMap.get(this); - const config = providedConfig || {}; - const options = normalizeVerifyOptions(providedOptions, config); - - // Resolve parser. - let parserName = DEFAULT_PARSER_NAME; - let parser = espree; - - if (typeof config.parser === "object" && config.parser !== null) { - parserName = config.parser.filePath; - parser = config.parser.definition; - } else if (typeof config.parser === "string") { - if (!slots.parserMap.has(config.parser)) { - return [{ - ruleId: null, - fatal: true, - severity: 2, - message: `Configured parser '${config.parser}' was not found.`, - line: 0, - column: 0, - nodeType: null - }]; - } - parserName = config.parser; - parser = slots.parserMap.get(config.parser); - } - - // search and apply "eslint-env *". - const envInFile = options.allowInlineConfig && !options.warnInlineConfig - ? findEslintEnv(file.body) - : {}; - const resolvedEnvConfig = Object.assign({ builtin: true }, config.env, envInFile); - const enabledEnvs = Object.keys(resolvedEnvConfig) - .filter(envName => resolvedEnvConfig[envName]) - .map(envName => getEnv(slots, envName)) - .filter(env => env); - - const parserOptions = resolveParserOptions(parser, config.parserOptions || {}, enabledEnvs); - const configuredGlobals = resolveGlobals(config.globals || {}, enabledEnvs); - const settings = config.settings || {}; - const languageOptions = createLanguageOptions({ - globals: config.globals, - parser, - parserOptions - }); - - if (!slots.lastSourceCode) { - let t; - - if (options.stats) { - t = startTime(); - } - - const parserService = new ParserService(); - const parseResult = parserService.parseSync( - file, - { - language: jslang, - languageOptions - } - ); - - if (options.stats) { - const time = endTime(t); - const timeOpts = { type: "parse" }; - - storeTime(time, timeOpts, slots); - } - - if (!parseResult.ok) { - return parseResult.errors; - } - - slots.lastSourceCode = parseResult.sourceCode; - } else { - - /* - * If the given source code object as the first argument does not have scopeManager, analyze the scope. - * This is for backward compatibility (SourceCode is frozen so it cannot rebind). - */ - if (!slots.lastSourceCode.scopeManager) { - slots.lastSourceCode = new SourceCode({ - text: slots.lastSourceCode.text, - ast: slots.lastSourceCode.ast, - hasBOM: slots.lastSourceCode.hasBOM, - parserServices: slots.lastSourceCode.parserServices, - visitorKeys: slots.lastSourceCode.visitorKeys, - scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions) - }); - } - } - - const sourceCode = slots.lastSourceCode; - const commentDirectives = options.allowInlineConfig - ? getDirectiveComments(sourceCode, ruleId => getRule(slots, ruleId), options.warnInlineConfig, config) - : { configuredRules: {}, enabledGlobals: {}, exportedVariables: {}, problems: [], disableDirectives: [] }; - - addDeclaredGlobals( - sourceCode.scopeManager.scopes[0], - configuredGlobals, - { exportedVariables: commentDirectives.exportedVariables, enabledGlobals: commentDirectives.enabledGlobals } - ); - - const configuredRules = Object.assign({}, config.rules, commentDirectives.configuredRules); - - let lintingProblems; - - try { - lintingProblems = runRules( - sourceCode, - configuredRules, - ruleId => getRule(slots, ruleId), - parserName, - jslang, - languageOptions, - settings, - options.filename, - true, - options.disableFixes, - slots.cwd, - providedOptions.physicalFilename, - null, - options.stats, - slots - ); - } catch (err) { - err.message += `\nOccurred while linting ${options.filename}`; - debug("An error occurred while traversing"); - debug("Filename:", options.filename); - if (err.currentNode) { - const { line } = sourceCode.getLoc(err.currentNode).start; - - debug("Line:", line); - err.message += `:${line}`; - } - debug("Parser Options:", parserOptions); - debug("Parser Path:", parserName); - debug("Settings:", settings); - - if (err.ruleId) { - err.message += `\nRule: "${err.ruleId}"`; - } - - throw err; - } - - return applyDisableDirectives({ - language: jslang, - sourceCode, - directives: commentDirectives.disableDirectives, - disableFixes: options.disableFixes, - problems: lintingProblems - .concat(commentDirectives.problems) - .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), - reportUnusedDisableDirectives: options.reportUnusedDisableDirectives - }); - - } - - /** - * Same as linter.verify, except without support for processors. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. - * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. - * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. - * @throws {Error} If during rule execution. - * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. - */ - _verifyWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { - const slots = internalSlotsMap.get(this); - const filename = normalizeFilename(providedOptions.filename || ""); - let text; - - // evaluate arguments - if (typeof textOrSourceCode === "string") { - slots.lastSourceCode = null; - text = textOrSourceCode; - } else { - slots.lastSourceCode = textOrSourceCode; - text = textOrSourceCode.text; - } - - const file = new VFile(filename, text, { - physicalPath: providedOptions.physicalFilename - }); - - return this.#eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions); - } - - /** - * Verifies the text against the rules specified by the second argument. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. - * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything. - * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked. - * If this is not set, the filename will default to '' in the rule context. If - * an object, then it has "filename", "allowInlineConfig", and some properties. - * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. - */ - verify(textOrSourceCode, config, filenameOrOptions) { - debug("Verify"); - - const { configType, cwd } = internalSlotsMap.get(this); - - const options = typeof filenameOrOptions === "string" - ? { filename: filenameOrOptions } - : filenameOrOptions || {}; - - const configToUse = config ?? {}; - - if (configType !== "eslintrc") { - - /* - * Because of how Webpack packages up the files, we can't - * compare directly to `FlatConfigArray` using `instanceof` - * because it's not the same `FlatConfigArray` as in the tests. - * So, we work around it by assuming an array is, in fact, a - * `FlatConfigArray` if it has a `getConfig()` method. - */ - let configArray = configToUse; - - if (!Array.isArray(configToUse) || typeof configToUse.getConfig !== "function") { - configArray = new FlatConfigArray(configToUse, { basePath: cwd }); - configArray.normalizeSync(); - } - - return this._distinguishSuppressedMessages(this._verifyWithFlatConfigArray(textOrSourceCode, configArray, options, true)); - } - - if (typeof configToUse.extractConfig === "function") { - return this._distinguishSuppressedMessages(this._verifyWithConfigArray(textOrSourceCode, configToUse, options)); - } - - /* - * If we get to here, it means `config` is just an object rather - * than a config array so we can go right into linting. - */ - - /* - * `Linter` doesn't support `overrides` property in configuration. - * So we cannot apply multiple processors. - */ - if (options.preprocess || options.postprocess) { - return this._distinguishSuppressedMessages(this._verifyWithProcessor(textOrSourceCode, configToUse, options)); - } - return this._distinguishSuppressedMessages(this._verifyWithoutProcessors(textOrSourceCode, configToUse, options)); - } - - /** - * Verify with a processor. - * @param {string|SourceCode} textOrSourceCode The source code. - * @param {FlatConfig} config The config array. - * @param {VerifyOptions&ProcessorOptions} options The options. - * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. - * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. - */ - _verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options, configForRecursive) { - const slots = internalSlotsMap.get(this); - const filename = options.filename || ""; - const filenameToExpose = normalizeFilename(filename); - const physicalFilename = options.physicalFilename || filenameToExpose; - const text = ensureText(textOrSourceCode); - const file = new VFile(filenameToExpose, text, { - physicalPath: physicalFilename - }); - - const preprocess = options.preprocess || (rawText => [rawText]); - const postprocess = options.postprocess || (messagesList => messagesList.flat()); - - const processorService = new ProcessorService(); - const preprocessResult = processorService.preprocessSync(file, { - processor: { - preprocess, - postprocess - } - }); - - if (!preprocessResult.ok) { - return preprocessResult.errors; - } - - const filterCodeBlock = - options.filterCodeBlock || - (blockFilename => blockFilename.endsWith(".js")); - const originalExtname = path.extname(filename); - const { files } = preprocessResult; - - const messageLists = files.map(block => { - debug("A code block was found: %o", block.path || "(unnamed)"); - - // Keep the legacy behavior. - if (typeof block === "string") { - return this._verifyWithFlatConfigArrayAndWithoutProcessors(block, config, options); - } - - // Skip this block if filtered. - if (!filterCodeBlock(block.path, block.body)) { - debug("This code block was skipped."); - return []; - } - - // Resolve configuration again if the file content or extension was changed. - if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) { - debug("Resolving configuration again because the file content or extension was changed."); - return this._verifyWithFlatConfigArray( - block.rawBody, - configForRecursive, - { ...options, filename: block.path, physicalFilename: block.physicalPath } - ); - } - - slots.lastSourceCode = null; - - // Does lint. - return this.#flatVerifyWithoutProcessors( - block, - config, - { ...options, filename: block.path, physicalFilename: block.physicalPath } - ); - }); - - return processorService.postprocessSync(file, messageLists, { - processor: { - preprocess, - postprocess - } - }); - } - - /** - * Verify using flat config and without any processors. - * @param {VFile} file The file to lint. - * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. - * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. - * @throws {Error} If during rule execution. - * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. - */ - #flatVerifyWithoutProcessors(file, providedConfig, providedOptions) { - - const slots = internalSlotsMap.get(this); - const config = providedConfig || {}; - const { settings = {}, languageOptions } = config; - const options = normalizeVerifyOptions(providedOptions, config); - - if (!slots.lastSourceCode) { - let t; - - if (options.stats) { - t = startTime(); - } - - const parserService = new ParserService(); - const parseResult = parserService.parseSync( - file, - config - ); - - if (options.stats) { - const time = endTime(t); - - storeTime(time, { type: "parse" }, slots); - } - - if (!parseResult.ok) { - return parseResult.errors; - } - - slots.lastSourceCode = parseResult.sourceCode; - } else { - - /* - * If the given source code object as the first argument does not have scopeManager, analyze the scope. - * This is for backward compatibility (SourceCode is frozen so it cannot rebind). - * - * We check explicitly for `null` to ensure that this is a JS-flavored language. - * For non-JS languages we don't want to do this. - * - * TODO: Remove this check when we stop exporting the `SourceCode` object. - */ - if (slots.lastSourceCode.scopeManager === null) { - slots.lastSourceCode = new SourceCode({ - text: slots.lastSourceCode.text, - ast: slots.lastSourceCode.ast, - hasBOM: slots.lastSourceCode.hasBOM, - parserServices: slots.lastSourceCode.parserServices, - visitorKeys: slots.lastSourceCode.visitorKeys, - scopeManager: analyzeScope(slots.lastSourceCode.ast, languageOptions) - }); - } - } - - const sourceCode = slots.lastSourceCode; - - /* - * Make adjustments based on the language options. For JavaScript, - * this is primarily about adding variables into the global scope - * to account for ecmaVersion and configured globals. - */ - sourceCode.applyLanguageOptions?.(languageOptions); - - const mergedInlineConfig = { - rules: {} - }; - const inlineConfigProblems = []; - - /* - * Inline config can be either enabled or disabled. If disabled, it's possible - * to detect the inline config and emit a warning (though this is not required). - * So we first check to see if inline config is allowed at all, and if so, we - * need to check if it's a warning or not. - */ - if (options.allowInlineConfig) { - - // if inline config should warn then add the warnings - if (options.warnInlineConfig) { - if (sourceCode.getInlineConfigNodes) { - sourceCode.getInlineConfigNodes().forEach(node => { - - const loc = sourceCode.getLoc(node); - const range = sourceCode.getRange(node); - - inlineConfigProblems.push(createLintingProblem({ - ruleId: null, - message: `'${sourceCode.text.slice(range[0], range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`, - loc, - severity: 1, - language: config.language - })); - - }); - } - } else { - const inlineConfigResult = sourceCode.applyInlineConfig?.(); - - if (inlineConfigResult) { - inlineConfigProblems.push( - ...inlineConfigResult.problems - .map(problem => createLintingProblem({ ...problem, language: config.language })) - .map(problem => { - problem.fatal = true; - return problem; - }) - ); - - // next we need to verify information about the specified rules - const ruleValidator = new RuleValidator(); - - for (const { config: inlineConfig, loc } of inlineConfigResult.configs) { - - Object.keys(inlineConfig.rules).forEach(ruleId => { - const rule = getRuleFromConfig(ruleId, config); - const ruleValue = inlineConfig.rules[ruleId]; - - if (!rule) { - inlineConfigProblems.push(createLintingProblem({ - ruleId, - loc, - language: config.language - })); - return; - } - - if (Object.hasOwn(mergedInlineConfig.rules, ruleId)) { - inlineConfigProblems.push(createLintingProblem({ - message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, - loc, - language: config.language - })); - return; - } - - try { - - const ruleOptionsInline = asArray(ruleValue); - let ruleOptions = ruleOptionsInline; - - assertIsRuleSeverity(ruleId, ruleOptions[0]); - - /* - * If the rule was already configured, inline rule configuration that - * only has severity should retain options from the config and just override the severity. - * - * Example: - * - * { - * rules: { - * curly: ["error", "multi"] - * } - * } - * - * /* eslint curly: ["warn"] * / - * - * Results in: - * - * curly: ["warn", "multi"] - */ - - let shouldValidateOptions = true; - - if ( - - /* - * If inline config for the rule has only severity - */ - ruleOptions.length === 1 && - - /* - * And the rule was already configured - */ - config.rules && Object.hasOwn(config.rules, ruleId) - ) { - - /* - * Then use severity from the inline config and options from the provided config - */ - ruleOptions = [ - ruleOptions[0], // severity from the inline config - ...config.rules[ruleId].slice(1) // options from the provided config - ]; - - // if the rule was enabled, the options have already been validated - if (config.rules[ruleId][0] > 0) { - shouldValidateOptions = false; - } - } else { - - /** - * Since we know the user provided options, apply defaults on top of them - */ - const slicedOptions = ruleOptions.slice(1); - const mergedOptions = deepMergeArrays(rule.meta?.defaultOptions, slicedOptions); - - if (mergedOptions.length) { - ruleOptions = [ruleOptions[0], ...mergedOptions]; - } - } - - if (options.reportUnusedInlineConfigs !== "off") { - addProblemIfSameSeverityAndOptions( - config, loc, inlineConfigProblems, ruleId, ruleOptions, ruleOptionsInline, options.reportUnusedInlineConfigs - ); - } - - if (shouldValidateOptions) { - ruleValidator.validate({ - plugins: config.plugins, - rules: { - [ruleId]: ruleOptions - } - }); - } - - mergedInlineConfig.rules[ruleId] = ruleOptions; - } catch (err) { - - /* - * If the rule has invalid `meta.schema`, throw the error because - * this is not an invalid inline configuration but an invalid rule. - */ - if (err.code === "ESLINT_INVALID_RULE_OPTIONS_SCHEMA") { - throw err; - } - - let baseMessage = err.message.slice( - err.message.startsWith("Key \"rules\":") - ? err.message.indexOf(":", 12) + 1 - : err.message.indexOf(":") + 1 - ).trim(); - - if (err.messageTemplate) { - baseMessage += ` You passed "${ruleValue}".`; - } - - inlineConfigProblems.push(createLintingProblem({ - ruleId, - message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`, - loc, - language: config.language - })); - } - }); - } - } - } - } - - const commentDirectives = options.allowInlineConfig && !options.warnInlineConfig - ? getDirectiveCommentsForFlatConfig( - sourceCode, - ruleId => getRuleFromConfig(ruleId, config), - config.language - ) - : { problems: [], disableDirectives: [] }; - - const configuredRules = Object.assign({}, config.rules, mergedInlineConfig.rules); - - let lintingProblems; - - sourceCode.finalize?.(); - - try { - lintingProblems = runRules( - sourceCode, - configuredRules, - ruleId => getRuleFromConfig(ruleId, config), - void 0, - config.language, - languageOptions, - settings, - options.filename, - false, - options.disableFixes, - slots.cwd, - providedOptions.physicalFilename, - options.ruleFilter, - options.stats, - slots - ); - } catch (err) { - err.message += `\nOccurred while linting ${options.filename}`; - debug("An error occurred while traversing"); - debug("Filename:", options.filename); - if (err.currentNode) { - const { line } = sourceCode.getLoc(err.currentNode).start; - - debug("Line:", line); - err.message += `:${line}`; - } - debug("Parser Options:", languageOptions.parserOptions); - - // debug("Parser Path:", parserName); - debug("Settings:", settings); - - if (err.ruleId) { - err.message += `\nRule: "${err.ruleId}"`; - } - - throw err; - } - - return applyDisableDirectives({ - language: config.language, - sourceCode, - directives: commentDirectives.disableDirectives, - disableFixes: options.disableFixes, - problems: lintingProblems - .concat(commentDirectives.problems) - .concat(inlineConfigProblems) - .sort((problemA, problemB) => problemA.line - problemB.line || problemA.column - problemB.column), - reportUnusedDisableDirectives: options.reportUnusedDisableDirectives, - ruleFilter: options.ruleFilter, - configuredRules - }); - - - } - - /** - * Same as linter.verify, except without support for processors. - * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. - * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. - * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. - * @throws {Error} If during rule execution. - * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. - */ - _verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, providedConfig, providedOptions) { - const slots = internalSlotsMap.get(this); - const filename = normalizeFilename(providedOptions.filename || ""); - let text; - - // evaluate arguments - if (typeof textOrSourceCode === "string") { - slots.lastSourceCode = null; - text = textOrSourceCode; - } else { - slots.lastSourceCode = textOrSourceCode; - text = textOrSourceCode.text; - } - - const file = new VFile(filename, text, { - physicalPath: providedOptions.physicalFilename - }); - - return this.#flatVerifyWithoutProcessors(file, providedConfig, providedOptions); - } - - /** - * Verify a given code with `ConfigArray`. - * @param {string|SourceCode} textOrSourceCode The source code. - * @param {ConfigArray} configArray The config array. - * @param {VerifyOptions&ProcessorOptions} options The options. - * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. - */ - _verifyWithConfigArray(textOrSourceCode, configArray, options) { - debug("With ConfigArray: %s", options.filename); - - // Store the config array in order to get plugin envs and rules later. - internalSlotsMap.get(this).lastConfigArray = configArray; - - // Extract the final config for this file. - const config = configArray.extractConfig(options.filename); - const processor = - config.processor && - configArray.pluginProcessors.get(config.processor); - - // Verify. - if (processor) { - debug("Apply the processor: %o", config.processor); - const { preprocess, postprocess, supportsAutofix } = processor; - const disableFixes = options.disableFixes || !supportsAutofix; - - return this._verifyWithProcessor( - textOrSourceCode, - config, - { ...options, disableFixes, postprocess, preprocess }, - configArray - ); - } - return this._verifyWithoutProcessors(textOrSourceCode, config, options); - } - - /** - * Verify a given code with a flat config. - * @param {string|SourceCode} textOrSourceCode The source code. - * @param {FlatConfigArray} configArray The config array. - * @param {VerifyOptions&ProcessorOptions} options The options. - * @param {boolean} [firstCall=false] Indicates if this is being called directly - * from verify(). (TODO: Remove once eslintrc is removed.) - * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. - */ - _verifyWithFlatConfigArray(textOrSourceCode, configArray, options, firstCall = false) { - debug("With flat config: %s", options.filename); - - // we need a filename to match configs against - const filename = options.filename || "__placeholder__.js"; - - // Store the config array in order to get plugin envs and rules later. - internalSlotsMap.get(this).lastConfigArray = configArray; - const config = configArray.getConfig(filename); - - if (!config) { - return [ - { - ruleId: null, - severity: 1, - message: `No matching configuration found for ${filename}.`, - line: 0, - column: 0, - nodeType: null - } - ]; - } - - // Verify. - if (config.processor) { - debug("Apply the processor: %o", config.processor); - const { preprocess, postprocess, supportsAutofix } = config.processor; - const disableFixes = options.disableFixes || !supportsAutofix; - - return this._verifyWithFlatConfigArrayAndProcessor( - textOrSourceCode, - config, - { ...options, filename, disableFixes, postprocess, preprocess }, - configArray - ); - } - - // check for options-based processing - if (firstCall && (options.preprocess || options.postprocess)) { - return this._verifyWithFlatConfigArrayAndProcessor(textOrSourceCode, config, options); - } - - return this._verifyWithFlatConfigArrayAndWithoutProcessors(textOrSourceCode, config, options); - } - - /** - * Verify with a processor. - * @param {string|SourceCode} textOrSourceCode The source code. - * @param {ConfigData|ExtractedConfig} config The config array. - * @param {VerifyOptions&ProcessorOptions} options The options. - * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. - * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. - */ - _verifyWithProcessor(textOrSourceCode, config, options, configForRecursive) { - const slots = internalSlotsMap.get(this); - const filename = options.filename || ""; - const filenameToExpose = normalizeFilename(filename); - const physicalFilename = options.physicalFilename || filenameToExpose; - const text = ensureText(textOrSourceCode); - const file = new VFile(filenameToExpose, text, { - physicalPath: physicalFilename - }); - - const preprocess = options.preprocess || (rawText => [rawText]); - const postprocess = options.postprocess || (messagesList => messagesList.flat()); - - const processorService = new ProcessorService(); - const preprocessResult = processorService.preprocessSync(file, { - processor: { - preprocess, - postprocess - } - }); - - if (!preprocessResult.ok) { - return preprocessResult.errors; - } - - const filterCodeBlock = - options.filterCodeBlock || - (blockFilePath => blockFilePath.endsWith(".js")); - const originalExtname = path.extname(filename); - - const { files } = preprocessResult; - - const messageLists = files.map(block => { - debug("A code block was found: %o", block.path ?? "(unnamed)"); - - // Keep the legacy behavior. - if (typeof block === "string") { - return this._verifyWithoutProcessors(block, config, options); - } - - // Skip this block if filtered. - if (!filterCodeBlock(block.path, block.body)) { - debug("This code block was skipped."); - return []; - } - - // Resolve configuration again if the file content or extension was changed. - if (configForRecursive && (text !== block.rawBody || path.extname(block.path) !== originalExtname)) { - debug("Resolving configuration again because the file content or extension was changed."); - return this._verifyWithConfigArray( - block.rawBody, - configForRecursive, - { ...options, filename: block.path, physicalFilename: block.physicalPath } - ); - } - - slots.lastSourceCode = null; - - // Does lint. - return this.#eslintrcVerifyWithoutProcessors( - block, - config, - { ...options, filename: block.path, physicalFilename: block.physicalPath } - ); - }); - - return processorService.postprocessSync(file, messageLists, { - processor: { - preprocess, - postprocess - } - }); - - } - - /** - * Given a list of reported problems, distinguish problems between normal messages and suppressed messages. - * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages. - * @param {Array} problems A list of reported problems. - * @returns {LintMessage[]} A list of LintMessage. - */ - _distinguishSuppressedMessages(problems) { - const messages = []; - const suppressedMessages = []; - const slots = internalSlotsMap.get(this); - - for (const problem of problems) { - if (problem.suppressions) { - suppressedMessages.push(problem); - } else { - messages.push(problem); - } - } - - slots.lastSuppressedMessages = suppressedMessages; - - return messages; - } - - /** - * Gets the SourceCode object representing the parsed source. - * @returns {SourceCode} The SourceCode object. - */ - getSourceCode() { - return internalSlotsMap.get(this).lastSourceCode; - } - - /** - * Gets the times spent on (parsing, fixing, linting) a file. - * @returns {LintTimes} The times. - */ - getTimes() { - return internalSlotsMap.get(this).times ?? { passes: [] }; - } - - /** - * Gets the number of autofix passes that were made in the last run. - * @returns {number} The number of autofix passes. - */ - getFixPassCount() { - return internalSlotsMap.get(this).fixPasses ?? 0; - } - - /** - * Gets the list of SuppressedLintMessage produced in the last running. - * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage - */ - getSuppressedMessages() { - return internalSlotsMap.get(this).lastSuppressedMessages; - } - - /** - * Defines a new linting rule. - * @param {string} ruleId A unique rule identifier - * @param {Rule} rule A rule object - * @returns {void} - */ - defineRule(ruleId, rule) { - assertEslintrcConfig(this); - internalSlotsMap.get(this).ruleMap.define(ruleId, rule); - } - - /** - * Defines many new linting rules. - * @param {Record} rulesToDefine map from unique rule identifier to rule - * @returns {void} - */ - defineRules(rulesToDefine) { - assertEslintrcConfig(this); - Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => { - this.defineRule(ruleId, rulesToDefine[ruleId]); - }); - } - - /** - * Gets an object with all loaded rules. - * @returns {Map} All loaded rules - */ - getRules() { - assertEslintrcConfig(this); - const { lastConfigArray, ruleMap } = internalSlotsMap.get(this); - - return new Map(function *() { - yield* ruleMap; - - if (lastConfigArray) { - yield* lastConfigArray.pluginRules; - } - }()); - } - - /** - * Define a new parser module - * @param {string} parserId Name of the parser - * @param {Parser} parserModule The parser object - * @returns {void} - */ - defineParser(parserId, parserModule) { - assertEslintrcConfig(this); - internalSlotsMap.get(this).parserMap.set(parserId, parserModule); - } - - /** - * Performs multiple autofix passes over the text until as many fixes as possible - * have been applied. - * @param {string} text The source text to apply fixes to. - * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use. - * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use. - * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the - * SourceCodeFixer. - */ - verifyAndFix(text, config, options) { - let messages, - fixedResult, - fixed = false, - passNumber = 0, - currentText = text; - const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`; - const shouldFix = options && typeof options.fix !== "undefined" ? options.fix : true; - const stats = options?.stats; - - /** - * This loop continues until one of the following is true: - * - * 1. No more fixes have been applied. - * 2. Ten passes have been made. - * - * That means anytime a fix is successfully applied, there will be another pass. - * Essentially, guaranteeing a minimum of two passes. - */ - const slots = internalSlotsMap.get(this); - - // Remove lint times from the last run. - if (stats) { - delete slots.times; - slots.fixPasses = 0; - } - - do { - passNumber++; - let tTotal; - - if (stats) { - tTotal = startTime(); - } - - debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`); - messages = this.verify(currentText, config, options); - - debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`); - let t; - - if (stats) { - t = startTime(); - } - - fixedResult = SourceCodeFixer.applyFixes(currentText, messages, shouldFix); - - if (stats) { - - if (fixedResult.fixed) { - const time = endTime(t); - - storeTime(time, { type: "fix" }, slots); - slots.fixPasses++; - } else { - storeTime(0, { type: "fix" }, slots); - } - } - - /* - * stop if there are any syntax errors. - * 'fixedResult.output' is a empty string. - */ - if (messages.length === 1 && messages[0].fatal) { - break; - } - - // keep track if any fixes were ever applied - important for return value - fixed = fixed || fixedResult.fixed; - - // update to use the fixed output instead of the original text - currentText = fixedResult.output; - - if (stats) { - tTotal = endTime(tTotal); - const passIndex = slots.times.passes.length - 1; - - slots.times.passes[passIndex].total = tTotal; - } - - } while ( - fixedResult.fixed && - passNumber < MAX_AUTOFIX_PASSES - ); - - /* - * If the last result had fixes, we need to lint again to be sure we have - * the most up-to-date information. - */ - if (fixedResult.fixed) { - let tTotal; - - if (stats) { - tTotal = startTime(); - } - - fixedResult.messages = this.verify(currentText, config, options); - - if (stats) { - storeTime(0, { type: "fix" }, slots); - slots.times.passes.at(-1).total = endTime(tTotal); - } - } - - // ensure the last result properly reflects if fixes were done - fixedResult.fixed = fixed; - fixedResult.output = currentText; - - return fixedResult; - } + /** + * Initialize the Linter. + * @param {Object} [config] the config object + * @param {string} [config.cwd] path to a directory that should be considered as the current working directory, can be undefined. + * @param {Array} [config.flags] the feature flags to enable. + * @param {"flat"|"eslintrc"} [config.configType="flat"] the type of config used. + */ + constructor({ cwd, configType = "flat", flags = [] } = {}) { + const processedFlags = []; + + flags.forEach(flag => { + if (inactiveFlags.has(flag)) { + const inactiveFlagData = inactiveFlags.get(flag); + const inactivityReason = + getInactivityReasonMessage(inactiveFlagData); + + if (typeof inactiveFlagData.replacedBy === "undefined") { + throw new Error( + `The flag '${flag}' is inactive: ${inactivityReason}`, + ); + } + + // if there's a replacement, enable it instead of original + if (typeof inactiveFlagData.replacedBy === "string") { + processedFlags.push(inactiveFlagData.replacedBy); + } + + globalThis.process?.emitWarning?.( + `The flag '${flag}' is inactive: ${inactivityReason}`, + `ESLintInactiveFlag_${flag}`, + ); + + return; + } + + if (!activeFlags.has(flag)) { + throw new Error(`Unknown flag '${flag}'.`); + } + + processedFlags.push(flag); + }); + + internalSlotsMap.set(this, { + cwd: normalizeCwd(cwd), + flags: processedFlags, + lastConfigArray: null, + lastSourceCode: null, + lastSuppressedMessages: [], + configType, // TODO: Remove after flat config conversion + parserMap: new Map([["espree", espree]]), + ruleMap: new Rules(), + }); + + this.version = pkg.version; + } + + /** + * Getter for package version. + * @static + * @returns {string} The version from package.json. + */ + static get version() { + return pkg.version; + } + + /** + * Indicates if the given feature flag is enabled for this instance. + * @param {string} flag The feature flag to check. + * @returns {boolean} `true` if the feature flag is enabled, `false` if not. + */ + hasFlag(flag) { + return internalSlotsMap.get(this).flags.includes(flag); + } + + /** + * Lint using eslintrc and without processors. + * @param {VFile} file The file to lint. + * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + #eslintrcVerifyWithoutProcessors(file, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); + const config = providedConfig || {}; + const options = normalizeVerifyOptions(providedOptions, config); + + // Resolve parser. + let parserName = DEFAULT_PARSER_NAME; + let parser = espree; + + if (typeof config.parser === "object" && config.parser !== null) { + parserName = config.parser.filePath; + parser = config.parser.definition; + } else if (typeof config.parser === "string") { + if (!slots.parserMap.has(config.parser)) { + return [ + { + ruleId: null, + fatal: true, + severity: 2, + message: `Configured parser '${config.parser}' was not found.`, + line: 0, + column: 0, + nodeType: null, + }, + ]; + } + parserName = config.parser; + parser = slots.parserMap.get(config.parser); + } + + // search and apply "eslint-env *". + const envInFile = + options.allowInlineConfig && !options.warnInlineConfig + ? findEslintEnv(file.body) + : {}; + const resolvedEnvConfig = Object.assign( + { builtin: true }, + config.env, + envInFile, + ); + const enabledEnvs = Object.keys(resolvedEnvConfig) + .filter(envName => resolvedEnvConfig[envName]) + .map(envName => getEnv(slots, envName)) + .filter(env => env); + + const parserOptions = resolveParserOptions( + parser, + config.parserOptions || {}, + enabledEnvs, + ); + const configuredGlobals = resolveGlobals( + config.globals || {}, + enabledEnvs, + ); + const settings = config.settings || {}; + const languageOptions = createLanguageOptions({ + globals: config.globals, + parser, + parserOptions, + }); + + if (!slots.lastSourceCode) { + let t; + + if (options.stats) { + t = startTime(); + } + + const parserService = new ParserService(); + const parseResult = parserService.parseSync(file, { + language: jslang, + languageOptions, + }); + + if (options.stats) { + const time = endTime(t); + const timeOpts = { type: "parse" }; + + storeTime(time, timeOpts, slots); + } + + if (!parseResult.ok) { + return parseResult.errors; + } + + slots.lastSourceCode = parseResult.sourceCode; + } else { + /* + * If the given source code object as the first argument does not have scopeManager, analyze the scope. + * This is for backward compatibility (SourceCode is frozen so it cannot rebind). + */ + if (!slots.lastSourceCode.scopeManager) { + slots.lastSourceCode = new SourceCode({ + text: slots.lastSourceCode.text, + ast: slots.lastSourceCode.ast, + hasBOM: slots.lastSourceCode.hasBOM, + parserServices: slots.lastSourceCode.parserServices, + visitorKeys: slots.lastSourceCode.visitorKeys, + scopeManager: analyzeScope( + slots.lastSourceCode.ast, + languageOptions, + ), + }); + } + } + + const sourceCode = slots.lastSourceCode; + const commentDirectives = options.allowInlineConfig + ? getDirectiveComments( + sourceCode, + ruleId => getRule(slots, ruleId), + options.warnInlineConfig, + config, + ) + : { + configuredRules: {}, + enabledGlobals: {}, + exportedVariables: {}, + problems: [], + disableDirectives: [], + }; + + addDeclaredGlobals( + sourceCode.scopeManager.scopes[0], + configuredGlobals, + { + exportedVariables: commentDirectives.exportedVariables, + enabledGlobals: commentDirectives.enabledGlobals, + }, + ); + + const configuredRules = Object.assign( + {}, + config.rules, + commentDirectives.configuredRules, + ); + + let lintingProblems; + + try { + lintingProblems = runRules( + sourceCode, + configuredRules, + ruleId => getRule(slots, ruleId), + parserName, + jslang, + languageOptions, + settings, + options.filename, + true, + options.disableFixes, + slots.cwd, + providedOptions.physicalFilename, + null, + options.stats, + slots, + ); + } catch (err) { + err.message += `\nOccurred while linting ${options.filename}`; + debug("An error occurred while traversing"); + debug("Filename:", options.filename); + if (err.currentNode) { + const { line } = sourceCode.getLoc(err.currentNode).start; + + debug("Line:", line); + err.message += `:${line}`; + } + debug("Parser Options:", parserOptions); + debug("Parser Path:", parserName); + debug("Settings:", settings); + + if (err.ruleId) { + err.message += `\nRule: "${err.ruleId}"`; + } + + throw err; + } + + return applyDisableDirectives({ + language: jslang, + sourceCode, + directives: commentDirectives.disableDirectives, + disableFixes: options.disableFixes, + problems: lintingProblems + .concat(commentDirectives.problems) + .sort( + (problemA, problemB) => + problemA.line - problemB.line || + problemA.column - problemB.column, + ), + reportUnusedDisableDirectives: + options.reportUnusedDisableDirectives, + }); + } + + /** + * Same as linter.verify, except without support for processors. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {ConfigData} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + _verifyWithoutProcessors( + textOrSourceCode, + providedConfig, + providedOptions, + ) { + const slots = internalSlotsMap.get(this); + const filename = normalizeFilename( + providedOptions.filename || "", + ); + let text; + + // evaluate arguments + if (typeof textOrSourceCode === "string") { + slots.lastSourceCode = null; + text = textOrSourceCode; + } else { + slots.lastSourceCode = textOrSourceCode; + text = textOrSourceCode.text; + } + + const file = new VFile(filename, text, { + physicalPath: providedOptions.physicalFilename, + }); + + return this.#eslintrcVerifyWithoutProcessors( + file, + providedConfig, + providedOptions, + ); + } + + /** + * Verifies the text against the rules specified by the second argument. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {ConfigData|ConfigArray} config An ESLintConfig instance to configure everything. + * @param {(string|(VerifyOptions&ProcessorOptions))} [filenameOrOptions] The optional filename of the file being checked. + * If this is not set, the filename will default to '' in the rule context. If + * an object, then it has "filename", "allowInlineConfig", and some properties. + * @returns {LintMessage[]} The results as an array of messages or an empty array if no messages. + */ + verify(textOrSourceCode, config, filenameOrOptions) { + debug("Verify"); + + const { configType, cwd } = internalSlotsMap.get(this); + + const options = + typeof filenameOrOptions === "string" + ? { filename: filenameOrOptions } + : filenameOrOptions || {}; + + const configToUse = config ?? {}; + + if (configType !== "eslintrc") { + /* + * Because of how Webpack packages up the files, we can't + * compare directly to `FlatConfigArray` using `instanceof` + * because it's not the same `FlatConfigArray` as in the tests. + * So, we work around it by assuming an array is, in fact, a + * `FlatConfigArray` if it has a `getConfig()` method. + */ + let configArray = configToUse; + + if ( + !Array.isArray(configToUse) || + typeof configToUse.getConfig !== "function" + ) { + configArray = new FlatConfigArray(configToUse, { + basePath: cwd, + }); + configArray.normalizeSync(); + } + + return this._distinguishSuppressedMessages( + this._verifyWithFlatConfigArray( + textOrSourceCode, + configArray, + options, + true, + ), + ); + } + + if (typeof configToUse.extractConfig === "function") { + return this._distinguishSuppressedMessages( + this._verifyWithConfigArray( + textOrSourceCode, + configToUse, + options, + ), + ); + } + + /* + * If we get to here, it means `config` is just an object rather + * than a config array so we can go right into linting. + */ + + /* + * `Linter` doesn't support `overrides` property in configuration. + * So we cannot apply multiple processors. + */ + if (options.preprocess || options.postprocess) { + return this._distinguishSuppressedMessages( + this._verifyWithProcessor( + textOrSourceCode, + configToUse, + options, + ), + ); + } + return this._distinguishSuppressedMessages( + this._verifyWithoutProcessors( + textOrSourceCode, + configToUse, + options, + ), + ); + } + + /** + * Verify with a processor. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {FlatConfig} config The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @param {FlatConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. + */ + _verifyWithFlatConfigArrayAndProcessor( + textOrSourceCode, + config, + options, + configForRecursive, + ) { + const slots = internalSlotsMap.get(this); + const filename = options.filename || ""; + const filenameToExpose = normalizeFilename(filename); + const physicalFilename = options.physicalFilename || filenameToExpose; + const text = ensureText(textOrSourceCode); + const file = new VFile(filenameToExpose, text, { + physicalPath: physicalFilename, + }); + + const preprocess = options.preprocess || (rawText => [rawText]); + const postprocess = + options.postprocess || (messagesList => messagesList.flat()); + + const processorService = new ProcessorService(); + const preprocessResult = processorService.preprocessSync(file, { + processor: { + preprocess, + postprocess, + }, + }); + + if (!preprocessResult.ok) { + return preprocessResult.errors; + } + + const filterCodeBlock = + options.filterCodeBlock || + (blockFilename => blockFilename.endsWith(".js")); + const originalExtname = path.extname(filename); + const { files } = preprocessResult; + + const messageLists = files.map(block => { + debug("A code block was found: %o", block.path || "(unnamed)"); + + // Keep the legacy behavior. + if (typeof block === "string") { + return this._verifyWithFlatConfigArrayAndWithoutProcessors( + block, + config, + options, + ); + } + + // Skip this block if filtered. + if (!filterCodeBlock(block.path, block.body)) { + debug("This code block was skipped."); + return []; + } + + // Resolve configuration again if the file content or extension was changed. + if ( + configForRecursive && + (text !== block.rawBody || + path.extname(block.path) !== originalExtname) + ) { + debug( + "Resolving configuration again because the file content or extension was changed.", + ); + return this._verifyWithFlatConfigArray( + block.rawBody, + configForRecursive, + { + ...options, + filename: block.path, + physicalFilename: block.physicalPath, + }, + ); + } + + slots.lastSourceCode = null; + + // Does lint. + return this.#flatVerifyWithoutProcessors(block, config, { + ...options, + filename: block.path, + physicalFilename: block.physicalPath, + }); + }); + + return processorService.postprocessSync(file, messageLists, { + processor: { + preprocess, + postprocess, + }, + }); + } + + /** + * Verify using flat config and without any processors. + * @param {VFile} file The file to lint. + * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + #flatVerifyWithoutProcessors(file, providedConfig, providedOptions) { + const slots = internalSlotsMap.get(this); + const config = providedConfig || {}; + const { settings = {}, languageOptions } = config; + const options = normalizeVerifyOptions(providedOptions, config); + + if (!slots.lastSourceCode) { + let t; + + if (options.stats) { + t = startTime(); + } + + const parserService = new ParserService(); + const parseResult = parserService.parseSync(file, config); + + if (options.stats) { + const time = endTime(t); + + storeTime(time, { type: "parse" }, slots); + } + + if (!parseResult.ok) { + return parseResult.errors; + } + + slots.lastSourceCode = parseResult.sourceCode; + } else { + /* + * If the given source code object as the first argument does not have scopeManager, analyze the scope. + * This is for backward compatibility (SourceCode is frozen so it cannot rebind). + * + * We check explicitly for `null` to ensure that this is a JS-flavored language. + * For non-JS languages we don't want to do this. + * + * TODO: Remove this check when we stop exporting the `SourceCode` object. + */ + if (slots.lastSourceCode.scopeManager === null) { + slots.lastSourceCode = new SourceCode({ + text: slots.lastSourceCode.text, + ast: slots.lastSourceCode.ast, + hasBOM: slots.lastSourceCode.hasBOM, + parserServices: slots.lastSourceCode.parserServices, + visitorKeys: slots.lastSourceCode.visitorKeys, + scopeManager: analyzeScope( + slots.lastSourceCode.ast, + languageOptions, + ), + }); + } + } + + const sourceCode = slots.lastSourceCode; + + /* + * Make adjustments based on the language options. For JavaScript, + * this is primarily about adding variables into the global scope + * to account for ecmaVersion and configured globals. + */ + sourceCode.applyLanguageOptions?.(languageOptions); + + const mergedInlineConfig = { + rules: {}, + }; + const inlineConfigProblems = []; + + /* + * Inline config can be either enabled or disabled. If disabled, it's possible + * to detect the inline config and emit a warning (though this is not required). + * So we first check to see if inline config is allowed at all, and if so, we + * need to check if it's a warning or not. + */ + if (options.allowInlineConfig) { + // if inline config should warn then add the warnings + if (options.warnInlineConfig) { + if (sourceCode.getInlineConfigNodes) { + sourceCode.getInlineConfigNodes().forEach(node => { + const loc = sourceCode.getLoc(node); + const range = sourceCode.getRange(node); + + inlineConfigProblems.push( + createLintingProblem({ + ruleId: null, + message: `'${sourceCode.text.slice(range[0], range[1])}' has no effect because you have 'noInlineConfig' setting in ${options.warnInlineConfig}.`, + loc, + severity: 1, + language: config.language, + }), + ); + }); + } + } else { + const inlineConfigResult = sourceCode.applyInlineConfig?.(); + + if (inlineConfigResult) { + inlineConfigProblems.push( + ...inlineConfigResult.problems + .map(problem => + createLintingProblem({ + ...problem, + language: config.language, + }), + ) + .map(problem => { + problem.fatal = true; + return problem; + }), + ); + + // next we need to verify information about the specified rules + const ruleValidator = new RuleValidator(); + + for (const { + config: inlineConfig, + loc, + } of inlineConfigResult.configs) { + Object.keys(inlineConfig.rules).forEach(ruleId => { + const rule = getRuleFromConfig(ruleId, config); + const ruleValue = inlineConfig.rules[ruleId]; + + if (!rule) { + inlineConfigProblems.push( + createLintingProblem({ + ruleId, + loc, + language: config.language, + }), + ); + return; + } + + if ( + Object.hasOwn(mergedInlineConfig.rules, ruleId) + ) { + inlineConfigProblems.push( + createLintingProblem({ + message: `Rule "${ruleId}" is already configured by another configuration comment in the preceding code. This configuration is ignored.`, + loc, + language: config.language, + }), + ); + return; + } + + try { + const ruleOptionsInline = asArray(ruleValue); + let ruleOptions = ruleOptionsInline; + + assertIsRuleSeverity(ruleId, ruleOptions[0]); + + /* + * If the rule was already configured, inline rule configuration that + * only has severity should retain options from the config and just override the severity. + * + * Example: + * + * { + * rules: { + * curly: ["error", "multi"] + * } + * } + * + * /* eslint curly: ["warn"] * / + * + * Results in: + * + * curly: ["warn", "multi"] + */ + + let shouldValidateOptions = true; + + if ( + /* + * If inline config for the rule has only severity + */ + ruleOptions.length === 1 && + /* + * And the rule was already configured + */ + config.rules && + Object.hasOwn(config.rules, ruleId) + ) { + /* + * Then use severity from the inline config and options from the provided config + */ + ruleOptions = [ + ruleOptions[0], // severity from the inline config + ...config.rules[ruleId].slice(1), // options from the provided config + ]; + + // if the rule was enabled, the options have already been validated + if (config.rules[ruleId][0] > 0) { + shouldValidateOptions = false; + } + } else { + /** + * Since we know the user provided options, apply defaults on top of them + */ + const slicedOptions = ruleOptions.slice(1); + const mergedOptions = deepMergeArrays( + rule.meta?.defaultOptions, + slicedOptions, + ); + + if (mergedOptions.length) { + ruleOptions = [ + ruleOptions[0], + ...mergedOptions, + ]; + } + } + + if ( + options.reportUnusedInlineConfigs !== "off" + ) { + addProblemIfSameSeverityAndOptions( + config, + loc, + inlineConfigProblems, + ruleId, + ruleOptions, + ruleOptionsInline, + options.reportUnusedInlineConfigs, + ); + } + + if (shouldValidateOptions) { + ruleValidator.validate({ + plugins: config.plugins, + rules: { + [ruleId]: ruleOptions, + }, + }); + } + + mergedInlineConfig.rules[ruleId] = ruleOptions; + } catch (err) { + /* + * If the rule has invalid `meta.schema`, throw the error because + * this is not an invalid inline configuration but an invalid rule. + */ + if ( + err.code === + "ESLINT_INVALID_RULE_OPTIONS_SCHEMA" + ) { + throw err; + } + + let baseMessage = err.message + .slice( + err.message.startsWith('Key "rules":') + ? err.message.indexOf(":", 12) + 1 + : err.message.indexOf(":") + 1, + ) + .trim(); + + if (err.messageTemplate) { + baseMessage += ` You passed "${ruleValue}".`; + } + + inlineConfigProblems.push( + createLintingProblem({ + ruleId, + message: `Inline configuration for rule "${ruleId}" is invalid:\n\t${baseMessage}\n`, + loc, + language: config.language, + }), + ); + } + }); + } + } + } + } + + const commentDirectives = + options.allowInlineConfig && !options.warnInlineConfig + ? getDirectiveCommentsForFlatConfig( + sourceCode, + ruleId => getRuleFromConfig(ruleId, config), + config.language, + ) + : { problems: [], disableDirectives: [] }; + + const configuredRules = Object.assign( + {}, + config.rules, + mergedInlineConfig.rules, + ); + + let lintingProblems; + + sourceCode.finalize?.(); + + try { + lintingProblems = runRules( + sourceCode, + configuredRules, + ruleId => getRuleFromConfig(ruleId, config), + void 0, + config.language, + languageOptions, + settings, + options.filename, + false, + options.disableFixes, + slots.cwd, + providedOptions.physicalFilename, + options.ruleFilter, + options.stats, + slots, + ); + } catch (err) { + err.message += `\nOccurred while linting ${options.filename}`; + debug("An error occurred while traversing"); + debug("Filename:", options.filename); + if (err.currentNode) { + const { line } = sourceCode.getLoc(err.currentNode).start; + + debug("Line:", line); + err.message += `:${line}`; + } + debug("Parser Options:", languageOptions.parserOptions); + + // debug("Parser Path:", parserName); + debug("Settings:", settings); + + if (err.ruleId) { + err.message += `\nRule: "${err.ruleId}"`; + } + + throw err; + } + + return applyDisableDirectives({ + language: config.language, + sourceCode, + directives: commentDirectives.disableDirectives, + disableFixes: options.disableFixes, + problems: lintingProblems + .concat(commentDirectives.problems) + .concat(inlineConfigProblems) + .sort( + (problemA, problemB) => + problemA.line - problemB.line || + problemA.column - problemB.column, + ), + reportUnusedDisableDirectives: + options.reportUnusedDisableDirectives, + ruleFilter: options.ruleFilter, + configuredRules, + }); + } + + /** + * Same as linter.verify, except without support for processors. + * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object. + * @param {FlatConfig} providedConfig An ESLintConfig instance to configure everything. + * @param {VerifyOptions} [providedOptions] The optional filename of the file being checked. + * @throws {Error} If during rule execution. + * @returns {(LintMessage|SuppressedLintMessage)[]} The results as an array of messages or an empty array if no messages. + */ + _verifyWithFlatConfigArrayAndWithoutProcessors( + textOrSourceCode, + providedConfig, + providedOptions, + ) { + const slots = internalSlotsMap.get(this); + const filename = normalizeFilename( + providedOptions.filename || "", + ); + let text; + + // evaluate arguments + if (typeof textOrSourceCode === "string") { + slots.lastSourceCode = null; + text = textOrSourceCode; + } else { + slots.lastSourceCode = textOrSourceCode; + text = textOrSourceCode.text; + } + + const file = new VFile(filename, text, { + physicalPath: providedOptions.physicalFilename, + }); + + return this.#flatVerifyWithoutProcessors( + file, + providedConfig, + providedOptions, + ); + } + + /** + * Verify a given code with `ConfigArray`. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {ConfigArray} configArray The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. + */ + _verifyWithConfigArray(textOrSourceCode, configArray, options) { + debug("With ConfigArray: %s", options.filename); + + // Store the config array in order to get plugin envs and rules later. + internalSlotsMap.get(this).lastConfigArray = configArray; + + // Extract the final config for this file. + const config = configArray.extractConfig(options.filename); + const processor = + config.processor && + configArray.pluginProcessors.get(config.processor); + + // Verify. + if (processor) { + debug("Apply the processor: %o", config.processor); + const { preprocess, postprocess, supportsAutofix } = processor; + const disableFixes = options.disableFixes || !supportsAutofix; + + return this._verifyWithProcessor( + textOrSourceCode, + config, + { ...options, disableFixes, postprocess, preprocess }, + configArray, + ); + } + return this._verifyWithoutProcessors(textOrSourceCode, config, options); + } + + /** + * Verify a given code with a flat config. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {FlatConfigArray} configArray The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @param {boolean} [firstCall=false] Indicates if this is being called directly + * from verify(). (TODO: Remove once eslintrc is removed.) + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. + */ + _verifyWithFlatConfigArray( + textOrSourceCode, + configArray, + options, + firstCall = false, + ) { + debug("With flat config: %s", options.filename); + + // we need a filename to match configs against + const filename = options.filename || "__placeholder__.js"; + + // Store the config array in order to get plugin envs and rules later. + internalSlotsMap.get(this).lastConfigArray = configArray; + const config = configArray.getConfig(filename); + + if (!config) { + return [ + { + ruleId: null, + severity: 1, + message: `No matching configuration found for ${filename}.`, + line: 0, + column: 0, + nodeType: null, + }, + ]; + } + + // Verify. + if (config.processor) { + debug("Apply the processor: %o", config.processor); + const { preprocess, postprocess, supportsAutofix } = + config.processor; + const disableFixes = options.disableFixes || !supportsAutofix; + + return this._verifyWithFlatConfigArrayAndProcessor( + textOrSourceCode, + config, + { ...options, filename, disableFixes, postprocess, preprocess }, + configArray, + ); + } + + // check for options-based processing + if (firstCall && (options.preprocess || options.postprocess)) { + return this._verifyWithFlatConfigArrayAndProcessor( + textOrSourceCode, + config, + options, + ); + } + + return this._verifyWithFlatConfigArrayAndWithoutProcessors( + textOrSourceCode, + config, + options, + ); + } + + /** + * Verify with a processor. + * @param {string|SourceCode} textOrSourceCode The source code. + * @param {ConfigData|ExtractedConfig} config The config array. + * @param {VerifyOptions&ProcessorOptions} options The options. + * @param {ConfigArray} [configForRecursive] The `ConfigArray` object to apply multiple processors recursively. + * @returns {(LintMessage|SuppressedLintMessage)[]} The found problems. + */ + _verifyWithProcessor( + textOrSourceCode, + config, + options, + configForRecursive, + ) { + const slots = internalSlotsMap.get(this); + const filename = options.filename || ""; + const filenameToExpose = normalizeFilename(filename); + const physicalFilename = options.physicalFilename || filenameToExpose; + const text = ensureText(textOrSourceCode); + const file = new VFile(filenameToExpose, text, { + physicalPath: physicalFilename, + }); + + const preprocess = options.preprocess || (rawText => [rawText]); + const postprocess = + options.postprocess || (messagesList => messagesList.flat()); + + const processorService = new ProcessorService(); + const preprocessResult = processorService.preprocessSync(file, { + processor: { + preprocess, + postprocess, + }, + }); + + if (!preprocessResult.ok) { + return preprocessResult.errors; + } + + const filterCodeBlock = + options.filterCodeBlock || + (blockFilePath => blockFilePath.endsWith(".js")); + const originalExtname = path.extname(filename); + + const { files } = preprocessResult; + + const messageLists = files.map(block => { + debug("A code block was found: %o", block.path ?? "(unnamed)"); + + // Keep the legacy behavior. + if (typeof block === "string") { + return this._verifyWithoutProcessors(block, config, options); + } + + // Skip this block if filtered. + if (!filterCodeBlock(block.path, block.body)) { + debug("This code block was skipped."); + return []; + } + + // Resolve configuration again if the file content or extension was changed. + if ( + configForRecursive && + (text !== block.rawBody || + path.extname(block.path) !== originalExtname) + ) { + debug( + "Resolving configuration again because the file content or extension was changed.", + ); + return this._verifyWithConfigArray( + block.rawBody, + configForRecursive, + { + ...options, + filename: block.path, + physicalFilename: block.physicalPath, + }, + ); + } + + slots.lastSourceCode = null; + + // Does lint. + return this.#eslintrcVerifyWithoutProcessors(block, config, { + ...options, + filename: block.path, + physicalFilename: block.physicalPath, + }); + }); + + return processorService.postprocessSync(file, messageLists, { + processor: { + preprocess, + postprocess, + }, + }); + } + + /** + * Given a list of reported problems, distinguish problems between normal messages and suppressed messages. + * The normal messages will be returned and the suppressed messages will be stored as lastSuppressedMessages. + * @param {Array} problems A list of reported problems. + * @returns {LintMessage[]} A list of LintMessage. + */ + _distinguishSuppressedMessages(problems) { + const messages = []; + const suppressedMessages = []; + const slots = internalSlotsMap.get(this); + + for (const problem of problems) { + if (problem.suppressions) { + suppressedMessages.push(problem); + } else { + messages.push(problem); + } + } + + slots.lastSuppressedMessages = suppressedMessages; + + return messages; + } + + /** + * Gets the SourceCode object representing the parsed source. + * @returns {SourceCode} The SourceCode object. + */ + getSourceCode() { + return internalSlotsMap.get(this).lastSourceCode; + } + + /** + * Gets the times spent on (parsing, fixing, linting) a file. + * @returns {LintTimes} The times. + */ + getTimes() { + return internalSlotsMap.get(this).times ?? { passes: [] }; + } + + /** + * Gets the number of autofix passes that were made in the last run. + * @returns {number} The number of autofix passes. + */ + getFixPassCount() { + return internalSlotsMap.get(this).fixPasses ?? 0; + } + + /** + * Gets the list of SuppressedLintMessage produced in the last running. + * @returns {SuppressedLintMessage[]} The list of SuppressedLintMessage + */ + getSuppressedMessages() { + return internalSlotsMap.get(this).lastSuppressedMessages; + } + + /** + * Defines a new linting rule. + * @param {string} ruleId A unique rule identifier + * @param {Rule} rule A rule object + * @returns {void} + */ + defineRule(ruleId, rule) { + assertEslintrcConfig(this); + internalSlotsMap.get(this).ruleMap.define(ruleId, rule); + } + + /** + * Defines many new linting rules. + * @param {Record} rulesToDefine map from unique rule identifier to rule + * @returns {void} + */ + defineRules(rulesToDefine) { + assertEslintrcConfig(this); + Object.getOwnPropertyNames(rulesToDefine).forEach(ruleId => { + this.defineRule(ruleId, rulesToDefine[ruleId]); + }); + } + + /** + * Gets an object with all loaded rules. + * @returns {Map} All loaded rules + */ + getRules() { + assertEslintrcConfig(this); + const { lastConfigArray, ruleMap } = internalSlotsMap.get(this); + + return new Map( + (function* () { + yield* ruleMap; + + if (lastConfigArray) { + yield* lastConfigArray.pluginRules; + } + })(), + ); + } + + /** + * Define a new parser module + * @param {string} parserId Name of the parser + * @param {Parser} parserModule The parser object + * @returns {void} + */ + defineParser(parserId, parserModule) { + assertEslintrcConfig(this); + internalSlotsMap.get(this).parserMap.set(parserId, parserModule); + } + + /** + * Performs multiple autofix passes over the text until as many fixes as possible + * have been applied. + * @param {string} text The source text to apply fixes to. + * @param {ConfigData|ConfigArray|FlatConfigArray} config The ESLint config object to use. + * @param {VerifyOptions&ProcessorOptions&FixOptions} options The ESLint options object to use. + * @returns {{fixed:boolean,messages:LintMessage[],output:string}} The result of the fix operation as returned from the + * SourceCodeFixer. + */ + verifyAndFix(text, config, options) { + let messages, + fixedResult, + fixed = false, + passNumber = 0, + currentText = text, + secondPreviousText, + previousText; + const debugTextDescription = + (options && options.filename) || `${text.slice(0, 10)}...`; + const shouldFix = + options && typeof options.fix !== "undefined" ? options.fix : true; + const stats = options?.stats; + + /** + * This loop continues until one of the following is true: + * + * 1. No more fixes have been applied. + * 2. Ten passes have been made. + * + * That means anytime a fix is successfully applied, there will be another pass. + * Essentially, guaranteeing a minimum of two passes. + */ + const slots = internalSlotsMap.get(this); + + // Remove lint times from the last run. + if (stats) { + delete slots.times; + slots.fixPasses = 0; + } + + do { + passNumber++; + let tTotal; + + if (stats) { + tTotal = startTime(); + } + + debug( + `Linting code for ${debugTextDescription} (pass ${passNumber})`, + ); + messages = this.verify(currentText, config, options); + + debug( + `Generating fixed text for ${debugTextDescription} (pass ${passNumber})`, + ); + let t; + + if (stats) { + t = startTime(); + } + + fixedResult = SourceCodeFixer.applyFixes( + currentText, + messages, + shouldFix, + ); + + if (stats) { + if (fixedResult.fixed) { + const time = endTime(t); + + storeTime(time, { type: "fix" }, slots); + slots.fixPasses++; + } else { + storeTime(0, { type: "fix" }, slots); + } + } + + /* + * stop if there are any syntax errors. + * 'fixedResult.output' is a empty string. + */ + if (messages.length === 1 && messages[0].fatal) { + break; + } + + // keep track if any fixes were ever applied - important for return value + fixed = fixed || fixedResult.fixed; + + // update to use the fixed output instead of the original text + secondPreviousText = previousText; + previousText = currentText; + currentText = fixedResult.output; + + if (stats) { + tTotal = endTime(tTotal); + const passIndex = slots.times.passes.length - 1; + + slots.times.passes[passIndex].total = tTotal; + } + + // Stop if we've made a circular fix + if ( + passNumber > 1 && + currentText.length === secondPreviousText.length && + currentText === secondPreviousText + ) { + debug( + `Circular fixes detected after pass ${passNumber}. Exiting fix loop.`, + ); + globalThis?.process?.emitWarning?.( + `Circular fixes detected while fixing ${options?.filename ?? "text"}. It is likely that you have conflicting rules in your configuration.`, + "ESLintCircularFixesWarning", + ); + break; + } + } while (fixedResult.fixed && passNumber < MAX_AUTOFIX_PASSES); + + /* + * If the last result had fixes, we need to lint again to be sure we have + * the most up-to-date information. + */ + if (fixedResult.fixed) { + let tTotal; + + if (stats) { + tTotal = startTime(); + } + + fixedResult.messages = this.verify(currentText, config, options); + + if (stats) { + storeTime(0, { type: "fix" }, slots); + slots.times.passes.at(-1).total = endTime(tTotal); + } + } + + // ensure the last result properly reflects if fixes were done + fixedResult.fixed = fixed; + fixedResult.output = currentText; + + return fixedResult; + } } module.exports = { - Linter, - - /** - * Get the internal slots of a given Linter instance for tests. - * @param {Linter} instance The Linter instance to get. - * @returns {LinterInternalSlots} The internal slots. - */ - getLinterInternalSlots(instance) { - return internalSlotsMap.get(instance); - } + Linter, + + /** + * Get the internal slots of a given Linter instance for tests. + * @param {Linter} instance The Linter instance to get. + * @returns {LinterInternalSlots} The internal slots. + */ + getLinterInternalSlots(instance) { + return internalSlotsMap.get(instance); + }, }; diff --git a/lib/linter/node-event-generator.js b/lib/linter/node-event-generator.js index 0eb2f8dc02bf..b67085b81f6b 100644 --- a/lib/linter/node-event-generator.js +++ b/lib/linter/node-event-generator.js @@ -37,7 +37,7 @@ const esquery = require("esquery"); * @returns {any[]} The union of the input arrays */ function union(...arrays) { - return [...new Set(arrays.flat())]; + return [...new Set(arrays.flat())]; } /** @@ -46,16 +46,16 @@ function union(...arrays) { * @returns {any[]} The intersection of the input arrays */ function intersection(...arrays) { - if (arrays.length === 0) { - return []; - } + if (arrays.length === 0) { + return []; + } - let result = [...new Set(arrays[0])]; + let result = [...new Set(arrays[0])]; - for (const array of arrays.slice(1)) { - result = result.filter(x => array.includes(x)); - } - return result; + for (const array of arrays.slice(1)) { + result = result.filter(x => array.includes(x)); + } + return result; } /** @@ -64,51 +64,57 @@ function intersection(...arrays) { * @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it */ function getPossibleTypes(parsedSelector) { - switch (parsedSelector.type) { - case "identifier": - return [parsedSelector.value]; - - case "matches": { - const typesForComponents = parsedSelector.selectors.map(getPossibleTypes); - - if (typesForComponents.every(Boolean)) { - return union(...typesForComponents); - } - return null; - } - - case "compound": { - const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent); - - // If all of the components could match any type, then the compound could also match any type. - if (!typesForComponents.length) { - return null; - } - - /* - * If at least one of the components could only match a particular type, the compound could only match - * the intersection of those types. - */ - return intersection(...typesForComponents); - } - - case "child": - case "descendant": - case "sibling": - case "adjacent": - return getPossibleTypes(parsedSelector.right); - - case "class": - if (parsedSelector.name === "function") { - return ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]; - } - - return null; - - default: - return null; - - } + switch (parsedSelector.type) { + case "identifier": + return [parsedSelector.value]; + + case "matches": { + const typesForComponents = + parsedSelector.selectors.map(getPossibleTypes); + + if (typesForComponents.every(Boolean)) { + return union(...typesForComponents); + } + return null; + } + + case "compound": { + const typesForComponents = parsedSelector.selectors + .map(getPossibleTypes) + .filter(typesForComponent => typesForComponent); + + // If all of the components could match any type, then the compound could also match any type. + if (!typesForComponents.length) { + return null; + } + + /* + * If at least one of the components could only match a particular type, the compound could only match + * the intersection of those types. + */ + return intersection(...typesForComponents); + } + + case "child": + case "descendant": + case "sibling": + case "adjacent": + return getPossibleTypes(parsedSelector.right); + + case "class": + if (parsedSelector.name === "function") { + return [ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + ]; + } + + return null; + + default: + return null; + } } /** @@ -117,27 +123,34 @@ function getPossibleTypes(parsedSelector) { * @returns {number} The number of class, pseudo-class, and attribute queries in this selector */ function countClassAttributes(parsedSelector) { - switch (parsedSelector.type) { - case "child": - case "descendant": - case "sibling": - case "adjacent": - return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right); - - case "compound": - case "not": - case "matches": - return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0); - - case "attribute": - case "field": - case "nth-child": - case "nth-last-child": - return 1; - - default: - return 0; - } + switch (parsedSelector.type) { + case "child": + case "descendant": + case "sibling": + case "adjacent": + return ( + countClassAttributes(parsedSelector.left) + + countClassAttributes(parsedSelector.right) + ); + + case "compound": + case "not": + case "matches": + return parsedSelector.selectors.reduce( + (sum, childSelector) => + sum + countClassAttributes(childSelector), + 0, + ); + + case "attribute": + case "field": + case "nth-child": + case "nth-last-child": + return 1; + + default: + return 0; + } } /** @@ -146,24 +159,30 @@ function countClassAttributes(parsedSelector) { * @returns {number} The number of identifier queries */ function countIdentifiers(parsedSelector) { - switch (parsedSelector.type) { - case "child": - case "descendant": - case "sibling": - case "adjacent": - return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right); - - case "compound": - case "not": - case "matches": - return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0); - - case "identifier": - return 1; - - default: - return 0; - } + switch (parsedSelector.type) { + case "child": + case "descendant": + case "sibling": + case "adjacent": + return ( + countIdentifiers(parsedSelector.left) + + countIdentifiers(parsedSelector.right) + ); + + case "compound": + case "not": + case "matches": + return parsedSelector.selectors.reduce( + (sum, childSelector) => sum + countIdentifiers(childSelector), + 0, + ); + + case "identifier": + return 1; + + default: + return 0; + } } /** @@ -177,9 +196,11 @@ function countIdentifiers(parsedSelector) { * a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically */ function compareSpecificity(selectorA, selectorB) { - return selectorA.attributeCount - selectorB.attributeCount || - selectorA.identifierCount - selectorB.identifierCount || - (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1); + return ( + selectorA.attributeCount - selectorB.attributeCount || + selectorA.identifierCount - selectorB.identifierCount || + (selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1) + ); } /** @@ -189,14 +210,20 @@ function compareSpecificity(selectorA, selectorB) { * @throws {Error} An error if the selector is invalid */ function tryParseSelector(rawSelector) { - try { - return esquery.parse(rawSelector.replace(/:exit$/u, "")); - } catch (err) { - if (err.location && err.location.start && typeof err.location.start.offset === "number") { - throw new SyntaxError(`Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`); - } - throw err; - } + try { + return esquery.parse(rawSelector.replace(/:exit$/u, "")); + } catch (err) { + if ( + err.location && + err.location.start && + typeof err.location.start.offset === "number" + ) { + throw new SyntaxError( + `Syntax error in selector "${rawSelector}" at position ${err.location.start.offset}: ${err.message}`, + ); + } + throw err; + } } const selectorCache = new Map(); @@ -207,23 +234,23 @@ const selectorCache = new Map(); * @returns {ASTSelector} A selector descriptor */ function parseSelector(rawSelector) { - if (selectorCache.has(rawSelector)) { - return selectorCache.get(rawSelector); - } - - const parsedSelector = tryParseSelector(rawSelector); - - const result = { - rawSelector, - isExit: rawSelector.endsWith(":exit"), - parsedSelector, - listenerTypes: getPossibleTypes(parsedSelector), - attributeCount: countClassAttributes(parsedSelector), - identifierCount: countIdentifiers(parsedSelector) - }; - - selectorCache.set(rawSelector, result); - return result; + if (selectorCache.has(rawSelector)) { + return selectorCache.get(rawSelector); + } + + const parsedSelector = tryParseSelector(rawSelector); + + const result = { + rawSelector, + isExit: rawSelector.endsWith(":exit"), + parsedSelector, + listenerTypes: getPossibleTypes(parsedSelector), + attributeCount: countClassAttributes(parsedSelector), + identifierCount: countIdentifiers(parsedSelector), + }; + + selectorCache.set(rawSelector, result); + return result; } //------------------------------------------------------------------------------ @@ -243,110 +270,142 @@ function parseSelector(rawSelector) { * ``` */ class NodeEventGenerator { - - /** - * @param {SafeEmitter} emitter - * An SafeEmitter which is the destination of events. This emitter must already - * have registered listeners for all of the events that it needs to listen for. - * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.) - * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes. - * @returns {NodeEventGenerator} new instance - */ - constructor(emitter, esqueryOptions) { - this.emitter = emitter; - this.esqueryOptions = esqueryOptions; - this.currentAncestry = []; - this.enterSelectorsByNodeType = new Map(); - this.exitSelectorsByNodeType = new Map(); - this.anyTypeEnterSelectors = []; - this.anyTypeExitSelectors = []; - - emitter.eventNames().forEach(rawSelector => { - const selector = parseSelector(rawSelector); - - if (selector.listenerTypes) { - const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType; - - selector.listenerTypes.forEach(nodeType => { - if (!typeMap.has(nodeType)) { - typeMap.set(nodeType, []); - } - typeMap.get(nodeType).push(selector); - }); - return; - } - const selectors = selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; - - selectors.push(selector); - }); - - this.anyTypeEnterSelectors.sort(compareSpecificity); - this.anyTypeExitSelectors.sort(compareSpecificity); - this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); - this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity)); - } - - /** - * Checks a selector against a node, and emits it if it matches - * @param {ASTNode} node The node to check - * @param {ASTSelector} selector An AST selector descriptor - * @returns {void} - */ - applySelector(node, selector) { - if (esquery.matches(node, selector.parsedSelector, this.currentAncestry, this.esqueryOptions)) { - this.emitter.emit(selector.rawSelector, node); - } - } - - /** - * Applies all appropriate selectors to a node, in specificity order - * @param {ASTNode} node The node to check - * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited - * @returns {void} - */ - applySelectors(node, isExit) { - const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || []; - const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors; - - /* - * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor. - * Iterate through each of them, applying selectors in the right order. - */ - let selectorsByTypeIndex = 0; - let anyTypeSelectorsIndex = 0; - - while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) { - if ( - selectorsByTypeIndex >= selectorsByNodeType.length || - anyTypeSelectorsIndex < anyTypeSelectors.length && - compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0 - ) { - this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]); - } else { - this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]); - } - } - } - - /** - * Emits an event of entering AST node. - * @param {ASTNode} node A node which was entered. - * @returns {void} - */ - enterNode(node) { - this.applySelectors(node, false); - this.currentAncestry.unshift(node); - } - - /** - * Emits an event of leaving AST node. - * @param {ASTNode} node A node which was left. - * @returns {void} - */ - leaveNode(node) { - this.currentAncestry.shift(); - this.applySelectors(node, true); - } + /** + * @param {SafeEmitter} emitter + * An SafeEmitter which is the destination of events. This emitter must already + * have registered listeners for all of the events that it needs to listen for. + * (See lib/linter/safe-emitter.js for more details on `SafeEmitter`.) + * @param {ESQueryOptions} esqueryOptions `esquery` options for traversing custom nodes. + * @returns {NodeEventGenerator} new instance + */ + constructor(emitter, esqueryOptions) { + this.emitter = emitter; + this.esqueryOptions = esqueryOptions; + this.currentAncestry = []; + this.enterSelectorsByNodeType = new Map(); + this.exitSelectorsByNodeType = new Map(); + this.anyTypeEnterSelectors = []; + this.anyTypeExitSelectors = []; + + emitter.eventNames().forEach(rawSelector => { + const selector = parseSelector(rawSelector); + + if (selector.listenerTypes) { + const typeMap = selector.isExit + ? this.exitSelectorsByNodeType + : this.enterSelectorsByNodeType; + + selector.listenerTypes.forEach(nodeType => { + if (!typeMap.has(nodeType)) { + typeMap.set(nodeType, []); + } + typeMap.get(nodeType).push(selector); + }); + return; + } + const selectors = selector.isExit + ? this.anyTypeExitSelectors + : this.anyTypeEnterSelectors; + + selectors.push(selector); + }); + + this.anyTypeEnterSelectors.sort(compareSpecificity); + this.anyTypeExitSelectors.sort(compareSpecificity); + this.enterSelectorsByNodeType.forEach(selectorList => + selectorList.sort(compareSpecificity), + ); + this.exitSelectorsByNodeType.forEach(selectorList => + selectorList.sort(compareSpecificity), + ); + } + + /** + * Checks a selector against a node, and emits it if it matches + * @param {ASTNode} node The node to check + * @param {ASTSelector} selector An AST selector descriptor + * @returns {void} + */ + applySelector(node, selector) { + if ( + esquery.matches( + node, + selector.parsedSelector, + this.currentAncestry, + this.esqueryOptions, + ) + ) { + this.emitter.emit(selector.rawSelector, node); + } + } + + /** + * Applies all appropriate selectors to a node, in specificity order + * @param {ASTNode} node The node to check + * @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited + * @returns {void} + */ + applySelectors(node, isExit) { + const selectorsByNodeType = + (isExit + ? this.exitSelectorsByNodeType + : this.enterSelectorsByNodeType + ).get(node.type) || []; + const anyTypeSelectors = isExit + ? this.anyTypeExitSelectors + : this.anyTypeEnterSelectors; + + /* + * selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor. + * Iterate through each of them, applying selectors in the right order. + */ + let selectorsByTypeIndex = 0; + let anyTypeSelectorsIndex = 0; + + while ( + selectorsByTypeIndex < selectorsByNodeType.length || + anyTypeSelectorsIndex < anyTypeSelectors.length + ) { + if ( + selectorsByTypeIndex >= selectorsByNodeType.length || + (anyTypeSelectorsIndex < anyTypeSelectors.length && + compareSpecificity( + anyTypeSelectors[anyTypeSelectorsIndex], + selectorsByNodeType[selectorsByTypeIndex], + ) < 0) + ) { + this.applySelector( + node, + anyTypeSelectors[anyTypeSelectorsIndex++], + ); + } else { + this.applySelector( + node, + selectorsByNodeType[selectorsByTypeIndex++], + ); + } + } + } + + /** + * Emits an event of entering AST node. + * @param {ASTNode} node A node which was entered. + * @returns {void} + */ + enterNode(node) { + this.applySelectors(node, false); + this.currentAncestry.unshift(node); + } + + /** + * Emits an event of leaving AST node. + * @param {ASTNode} node A node which was left. + * @returns {void} + */ + leaveNode(node) { + this.currentAncestry.shift(); + this.applySelectors(node, true); + } } module.exports = NodeEventGenerator; diff --git a/lib/linter/report-translator.js b/lib/linter/report-translator.js index e0a463057d11..c3952eadbf7e 100644 --- a/lib/linter/report-translator.js +++ b/lib/linter/report-translator.js @@ -35,39 +35,36 @@ const { interpolate } = require("./interpolate"); // Module Definition //------------------------------------------------------------------------------ - /** * Translates a multi-argument context.report() call into a single object argument call * @param {...*} args A list of arguments passed to `context.report` * @returns {MessageDescriptor} A normalized object containing report information */ function normalizeMultiArgReportCall(...args) { - - // If there is one argument, it is considered to be a new-style call already. - if (args.length === 1) { - - // Shallow clone the object to avoid surprises if reusing the descriptor - return Object.assign({}, args[0]); - } - - // If the second argument is a string, the arguments are interpreted as [node, message, data, fix]. - if (typeof args[1] === "string") { - return { - node: args[0], - message: args[1], - data: args[2], - fix: args[3] - }; - } - - // Otherwise, the arguments are interpreted as [node, loc, message, data, fix]. - return { - node: args[0], - loc: args[1], - message: args[2], - data: args[3], - fix: args[4] - }; + // If there is one argument, it is considered to be a new-style call already. + if (args.length === 1) { + // Shallow clone the object to avoid surprises if reusing the descriptor + return Object.assign({}, args[0]); + } + + // If the second argument is a string, the arguments are interpreted as [node, message, data, fix]. + if (typeof args[1] === "string") { + return { + node: args[0], + message: args[1], + data: args[2], + fix: args[3], + }; + } + + // Otherwise, the arguments are interpreted as [node, loc, message, data, fix]. + return { + node: args[0], + loc: args[1], + message: args[2], + data: args[3], + fix: args[4], + }; } /** @@ -77,11 +74,14 @@ function normalizeMultiArgReportCall(...args) { * @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object */ function assertValidNodeInfo(descriptor) { - if (descriptor.node) { - assert(typeof descriptor.node === "object", "Node must be an object"); - } else { - assert(descriptor.loc, "Node must be provided when reporting error if location is not provided"); - } + if (descriptor.node) { + assert(typeof descriptor.node === "object", "Node must be an object"); + } else { + assert( + descriptor.loc, + "Node must be provided when reporting error if location is not provided", + ); + } } /** @@ -91,10 +91,10 @@ function assertValidNodeInfo(descriptor) { * from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor. */ function normalizeReportLoc(descriptor) { - if (descriptor.loc.start) { - return descriptor.loc; - } - return { start: descriptor.loc, end: null }; + if (descriptor.loc.start) { + return descriptor.loc; + } + return { start: descriptor.loc, end: null }; } /** @@ -103,14 +103,14 @@ function normalizeReportLoc(descriptor) { * @returns {Fix|null} Deep cloned fix object or `null` if `null` or `undefined` was passed in. */ function cloneFix(fix) { - if (!fix) { - return null; - } - - return { - range: [fix.range[0], fix.range[1]], - text: fix.text - }; + if (!fix) { + return null; + } + + return { + range: [fix.range[0], fix.range[1]], + text: fix.text, + }; } /** @@ -119,9 +119,14 @@ function cloneFix(fix) { * @returns {void} */ function assertValidFix(fix) { - if (fix) { - assert(fix.range && typeof fix.range[0] === "number" && typeof fix.range[1] === "number", `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`); - } + if (fix) { + assert( + fix.range && + typeof fix.range[0] === "number" && + typeof fix.range[1] === "number", + `Fix has invalid range: ${JSON.stringify(fix, null, 2)}`, + ); + } } /** @@ -132,7 +137,7 @@ function assertValidFix(fix) { * @private */ function compareFixesByRange(a, b) { - return a.range[0] - b.range[0] || a.range[1] - b.range[1]; + return a.range[0] - b.range[0] || a.range[1] - b.range[1]; } /** @@ -142,37 +147,43 @@ function compareFixesByRange(a, b) { * @returns {{text: string, range: number[]}} The merged fixes */ function mergeFixes(fixes, sourceCode) { - for (const fix of fixes) { - assertValidFix(fix); - } - - if (fixes.length === 0) { - return null; - } - if (fixes.length === 1) { - return cloneFix(fixes[0]); - } - - fixes.sort(compareFixesByRange); - - const originalText = sourceCode.text; - const start = fixes[0].range[0]; - const end = fixes.at(-1).range[1]; - let text = ""; - let lastPos = Number.MIN_SAFE_INTEGER; - - for (const fix of fixes) { - assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report."); - - if (fix.range[0] >= 0) { - text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]); - } - text += fix.text; - lastPos = fix.range[1]; - } - text += originalText.slice(Math.max(0, start, lastPos), end); - - return { range: [start, end], text }; + for (const fix of fixes) { + assertValidFix(fix); + } + + if (fixes.length === 0) { + return null; + } + if (fixes.length === 1) { + return cloneFix(fixes[0]); + } + + fixes.sort(compareFixesByRange); + + const originalText = sourceCode.text; + const start = fixes[0].range[0]; + const end = fixes.at(-1).range[1]; + let text = ""; + let lastPos = Number.MIN_SAFE_INTEGER; + + for (const fix of fixes) { + assert( + fix.range[0] >= lastPos, + "Fix objects must not be overlapped in a report.", + ); + + if (fix.range[0] >= 0) { + text += originalText.slice( + Math.max(0, start, lastPos), + fix.range[0], + ); + } + text += fix.text; + lastPos = fix.range[1]; + } + text += originalText.slice(Math.max(0, start, lastPos), end); + + return { range: [start, end], text }; } /** @@ -183,22 +194,22 @@ function mergeFixes(fixes, sourceCode) { * @returns {({text: string, range: number[]}|null)} The fix for the descriptor */ function normalizeFixes(descriptor, sourceCode) { - if (typeof descriptor.fix !== "function") { - return null; - } + if (typeof descriptor.fix !== "function") { + return null; + } - const ruleFixer = new RuleFixer({ sourceCode }); + const ruleFixer = new RuleFixer({ sourceCode }); - // @type {null | Fix | Fix[] | IterableIterator} - const fix = descriptor.fix(ruleFixer); + // @type {null | Fix | Fix[] | IterableIterator} + const fix = descriptor.fix(ruleFixer); - // Merge to one. - if (fix && Symbol.iterator in fix) { - return mergeFixes(Array.from(fix), sourceCode); - } + // Merge to one. + if (fix && Symbol.iterator in fix) { + return mergeFixes(Array.from(fix), sourceCode); + } - assertValidFix(fix); - return cloneFix(fix); + assertValidFix(fix); + return cloneFix(fix); } /** @@ -209,23 +220,26 @@ function normalizeFixes(descriptor, sourceCode) { * @returns {Array} The suggestions for the descriptor */ function mapSuggestions(descriptor, sourceCode, messages) { - if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) { - return []; - } - - return descriptor.suggest - .map(suggestInfo => { - const computedDesc = suggestInfo.desc || messages[suggestInfo.messageId]; - - return { - ...suggestInfo, - desc: interpolate(computedDesc, suggestInfo.data), - fix: normalizeFixes(suggestInfo, sourceCode) - }; - }) - - // Remove suggestions that didn't provide a fix - .filter(({ fix }) => fix); + if (!descriptor.suggest || !Array.isArray(descriptor.suggest)) { + return []; + } + + return ( + descriptor.suggest + .map(suggestInfo => { + const computedDesc = + suggestInfo.desc || messages[suggestInfo.messageId]; + + return { + ...suggestInfo, + desc: interpolate(computedDesc, suggestInfo.data), + fix: normalizeFixes(suggestInfo, sourceCode), + }; + }) + + // Remove suggestions that didn't provide a fix + .filter(({ fix }) => fix) + ); } /** @@ -243,43 +257,43 @@ function mapSuggestions(descriptor, sourceCode, messages) { * @returns {LintMessage} Information about the report */ function createProblem(options) { - const { language } = options; - - // calculate offsets based on the language in use - const columnOffset = language.columnStart === 1 ? 0 : 1; - const lineOffset = language.lineStart === 1 ? 0 : 1; - - const problem = { - ruleId: options.ruleId, - severity: options.severity, - message: options.message, - line: options.loc.start.line + lineOffset, - column: options.loc.start.column + columnOffset, - nodeType: options.node && options.node.type || null - }; - - /* - * If this isn’t in the conditional, some of the tests fail - * because `messageId` is present in the problem object - */ - if (options.messageId) { - problem.messageId = options.messageId; - } - - if (options.loc.end) { - problem.endLine = options.loc.end.line + lineOffset; - problem.endColumn = options.loc.end.column + columnOffset; - } - - if (options.fix) { - problem.fix = options.fix; - } - - if (options.suggestions && options.suggestions.length > 0) { - problem.suggestions = options.suggestions; - } - - return problem; + const { language } = options; + + // calculate offsets based on the language in use + const columnOffset = language.columnStart === 1 ? 0 : 1; + const lineOffset = language.lineStart === 1 ? 0 : 1; + + const problem = { + ruleId: options.ruleId, + severity: options.severity, + message: options.message, + line: options.loc.start.line + lineOffset, + column: options.loc.start.column + columnOffset, + nodeType: (options.node && options.node.type) || null, + }; + + /* + * If this isn’t in the conditional, some of the tests fail + * because `messageId` is present in the problem object + */ + if (options.messageId) { + problem.messageId = options.messageId; + } + + if (options.loc.end) { + problem.endLine = options.loc.end.line + lineOffset; + problem.endColumn = options.loc.end.column + columnOffset; + } + + if (options.fix) { + problem.fix = options.fix; + } + + if (options.suggestions && options.suggestions.length > 0) { + problem.suggestions = options.suggestions; + } + + return problem; } /** @@ -289,31 +303,41 @@ function createProblem(options) { * @returns {void} */ function validateSuggestions(suggest, messages) { - if (suggest && Array.isArray(suggest)) { - suggest.forEach(suggestion => { - if (suggestion.messageId) { - const { messageId } = suggestion; - - if (!messages) { - throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`); - } - - if (!messages[messageId]) { - throw new TypeError(`context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); - } - - if (suggestion.desc) { - throw new TypeError("context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one."); - } - } else if (!suggestion.desc) { - throw new TypeError("context.report() called with a suggest option that doesn't have either a `desc` or `messageId`"); - } - - if (typeof suggestion.fix !== "function") { - throw new TypeError(`context.report() called with a suggest option without a fix function. See: ${suggestion}`); - } - }); - } + if (suggest && Array.isArray(suggest)) { + suggest.forEach(suggestion => { + if (suggestion.messageId) { + const { messageId } = suggestion; + + if (!messages) { + throw new TypeError( + `context.report() called with a suggest option with a messageId '${messageId}', but no messages were present in the rule metadata.`, + ); + } + + if (!messages[messageId]) { + throw new TypeError( + `context.report() called with a suggest option with a messageId '${messageId}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`, + ); + } + + if (suggestion.desc) { + throw new TypeError( + "context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.", + ); + } + } else if (!suggestion.desc) { + throw new TypeError( + "context.report() called with a suggest option that doesn't have either a `desc` or `messageId`", + ); + } + + if (typeof suggestion.fix !== "function") { + throw new TypeError( + `context.report() called with a suggest option without a fix function. See: ${suggestion}`, + ); + } + }); + } } /** @@ -324,53 +348,66 @@ function validateSuggestions(suggest, messages) { */ module.exports = function createReportTranslator(metadata) { - - /* - * `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant. - * The report translator itself (i.e. the function that `createReportTranslator` returns) gets - * called every time a rule reports a problem, which happens much less frequently (usually, the vast - * majority of rules don't report any problems for a given file). - */ - return (...args) => { - const descriptor = normalizeMultiArgReportCall(...args); - const messages = metadata.messageIds; - const { sourceCode } = metadata; - - assertValidNodeInfo(descriptor); - - let computedMessage; - - if (descriptor.messageId) { - if (!messages) { - throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata."); - } - const id = descriptor.messageId; - - if (descriptor.message) { - throw new TypeError("context.report() called with a message and a messageId. Please only pass one."); - } - if (!messages || !Object.hasOwn(messages, id)) { - throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`); - } - computedMessage = messages[id]; - } else if (descriptor.message) { - computedMessage = descriptor.message; - } else { - throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem."); - } - - validateSuggestions(descriptor.suggest, messages); - - return createProblem({ - ruleId: metadata.ruleId, - severity: metadata.severity, - node: descriptor.node, - message: interpolate(computedMessage, descriptor.data), - messageId: descriptor.messageId, - loc: descriptor.loc ? normalizeReportLoc(descriptor) : sourceCode.getLoc(descriptor.node), - fix: metadata.disableFixes ? null : normalizeFixes(descriptor, sourceCode), - suggestions: metadata.disableFixes ? [] : mapSuggestions(descriptor, sourceCode, messages), - language: metadata.language - }); - }; + /* + * `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant. + * The report translator itself (i.e. the function that `createReportTranslator` returns) gets + * called every time a rule reports a problem, which happens much less frequently (usually, the vast + * majority of rules don't report any problems for a given file). + */ + return (...args) => { + const descriptor = normalizeMultiArgReportCall(...args); + const messages = metadata.messageIds; + const { sourceCode } = metadata; + + assertValidNodeInfo(descriptor); + + let computedMessage; + + if (descriptor.messageId) { + if (!messages) { + throw new TypeError( + "context.report() called with a messageId, but no messages were present in the rule metadata.", + ); + } + const id = descriptor.messageId; + + if (descriptor.message) { + throw new TypeError( + "context.report() called with a message and a messageId. Please only pass one.", + ); + } + if (!messages || !Object.hasOwn(messages, id)) { + throw new TypeError( + `context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`, + ); + } + computedMessage = messages[id]; + } else if (descriptor.message) { + computedMessage = descriptor.message; + } else { + throw new TypeError( + "Missing `message` property in report() call; add a message that describes the linting problem.", + ); + } + + validateSuggestions(descriptor.suggest, messages); + + return createProblem({ + ruleId: metadata.ruleId, + severity: metadata.severity, + node: descriptor.node, + message: interpolate(computedMessage, descriptor.data), + messageId: descriptor.messageId, + loc: descriptor.loc + ? normalizeReportLoc(descriptor) + : sourceCode.getLoc(descriptor.node), + fix: metadata.disableFixes + ? null + : normalizeFixes(descriptor, sourceCode), + suggestions: metadata.disableFixes + ? [] + : mapSuggestions(descriptor, sourceCode, messages), + language: metadata.language, + }); + }; }; diff --git a/lib/linter/rule-fixer.js b/lib/linter/rule-fixer.js index f9c45feb5f3e..c86e3f451abf 100644 --- a/lib/linter/rule-fixer.js +++ b/lib/linter/rule-fixer.js @@ -24,10 +24,10 @@ * @private */ function insertTextAt(index, text) { - return { - range: [index, index], - text - }; + return { + range: [index, index], + text, + }; } //------------------------------------------------------------------------------ @@ -38,126 +38,124 @@ function insertTextAt(index, text) { * Creates code fixing commands for rules. */ class RuleFixer { - - /** - * The source code object representing the text to be fixed. - * @type {SourceCode} - */ - #sourceCode; - - /** - * Creates a new instance. - * @param {Object} options The options for the fixer. - * @param {SourceCode} options.sourceCode The source code object representing the text to be fixed. - */ - constructor({ sourceCode }) { - this.#sourceCode = sourceCode; - } - - /** - * Creates a fix command that inserts text after the given node or token. - * The fix is not applied until applyFixes() is called. - * @param {ASTNode|Token} nodeOrToken The node or token to insert after. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - insertTextAfter(nodeOrToken, text) { - const range = this.#sourceCode.getRange(nodeOrToken); - - return this.insertTextAfterRange(range, text); - } - - /** - * Creates a fix command that inserts text after the specified range in the source text. - * The fix is not applied until applyFixes() is called. - * @param {int[]} range The range to replace, first item is start of range, second - * is end of range. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - insertTextAfterRange(range, text) { - return insertTextAt(range[1], text); - } - - /** - * Creates a fix command that inserts text before the given node or token. - * The fix is not applied until applyFixes() is called. - * @param {ASTNode|Token} nodeOrToken The node or token to insert before. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - insertTextBefore(nodeOrToken, text) { - const range = this.#sourceCode.getRange(nodeOrToken); - - return this.insertTextBeforeRange(range, text); - } - - /** - * Creates a fix command that inserts text before the specified range in the source text. - * The fix is not applied until applyFixes() is called. - * @param {int[]} range The range to replace, first item is start of range, second - * is end of range. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - insertTextBeforeRange(range, text) { - return insertTextAt(range[0], text); - } - - /** - * Creates a fix command that replaces text at the node or token. - * The fix is not applied until applyFixes() is called. - * @param {ASTNode|Token} nodeOrToken The node or token to remove. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - replaceText(nodeOrToken, text) { - const range = this.#sourceCode.getRange(nodeOrToken); - - return this.replaceTextRange(range, text); - } - - /** - * Creates a fix command that replaces text at the specified range in the source text. - * The fix is not applied until applyFixes() is called. - * @param {int[]} range The range to replace, first item is start of range, second - * is end of range. - * @param {string} text The text to insert. - * @returns {Object} The fix command. - */ - replaceTextRange(range, text) { - return { - range, - text - }; - } - - /** - * Creates a fix command that removes the node or token from the source. - * The fix is not applied until applyFixes() is called. - * @param {ASTNode|Token} nodeOrToken The node or token to remove. - * @returns {Object} The fix command. - */ - remove(nodeOrToken) { - const range = this.#sourceCode.getRange(nodeOrToken); - - return this.removeRange(range); - } - - /** - * Creates a fix command that removes the specified range of text from the source. - * The fix is not applied until applyFixes() is called. - * @param {int[]} range The range to remove, first item is start of range, second - * is end of range. - * @returns {Object} The fix command. - */ - removeRange(range) { - return { - range, - text: "" - }; - } + /** + * The source code object representing the text to be fixed. + * @type {SourceCode} + */ + #sourceCode; + + /** + * Creates a new instance. + * @param {Object} options The options for the fixer. + * @param {SourceCode} options.sourceCode The source code object representing the text to be fixed. + */ + constructor({ sourceCode }) { + this.#sourceCode = sourceCode; + } + + /** + * Creates a fix command that inserts text after the given node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to insert after. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextAfter(nodeOrToken, text) { + const range = this.#sourceCode.getRange(nodeOrToken); + + return this.insertTextAfterRange(range, text); + } + + /** + * Creates a fix command that inserts text after the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextAfterRange(range, text) { + return insertTextAt(range[1], text); + } + + /** + * Creates a fix command that inserts text before the given node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to insert before. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextBefore(nodeOrToken, text) { + const range = this.#sourceCode.getRange(nodeOrToken); + + return this.insertTextBeforeRange(range, text); + } + + /** + * Creates a fix command that inserts text before the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + insertTextBeforeRange(range, text) { + return insertTextAt(range[0], text); + } + + /** + * Creates a fix command that replaces text at the node or token. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + replaceText(nodeOrToken, text) { + const range = this.#sourceCode.getRange(nodeOrToken); + + return this.replaceTextRange(range, text); + } + + /** + * Creates a fix command that replaces text at the specified range in the source text. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to replace, first item is start of range, second + * is end of range. + * @param {string} text The text to insert. + * @returns {Object} The fix command. + */ + replaceTextRange(range, text) { + return { + range, + text, + }; + } + + /** + * Creates a fix command that removes the node or token from the source. + * The fix is not applied until applyFixes() is called. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @returns {Object} The fix command. + */ + remove(nodeOrToken) { + const range = this.#sourceCode.getRange(nodeOrToken); + + return this.removeRange(range); + } + + /** + * Creates a fix command that removes the specified range of text from the source. + * The fix is not applied until applyFixes() is called. + * @param {int[]} range The range to remove, first item is start of range, second + * is end of range. + * @returns {Object} The fix command. + */ + removeRange(range) { + return { + range, + text: "", + }; + } } - module.exports = { RuleFixer }; diff --git a/lib/linter/rules.js b/lib/linter/rules.js index bb8e36823bd1..d6754e3aa8c5 100644 --- a/lib/linter/rules.js +++ b/lib/linter/rules.js @@ -26,46 +26,46 @@ const builtInRules = require("../rules"); * A storage for rules. */ class Rules { - constructor() { - this._rules = Object.create(null); - } + constructor() { + this._rules = Object.create(null); + } - /** - * Registers a rule module for rule id in storage. - * @param {string} ruleId Rule id (file name). - * @param {Rule} rule Rule object. - * @returns {void} - */ - define(ruleId, rule) { - this._rules[ruleId] = rule; - } + /** + * Registers a rule module for rule id in storage. + * @param {string} ruleId Rule id (file name). + * @param {Rule} rule Rule object. + * @returns {void} + */ + define(ruleId, rule) { + this._rules[ruleId] = rule; + } - /** - * Access rule handler by id (file name). - * @param {string} ruleId Rule id (file name). - * @returns {Rule} Rule object. - */ - get(ruleId) { - if (typeof this._rules[ruleId] === "string") { - this.define(ruleId, require(this._rules[ruleId])); - } - if (this._rules[ruleId]) { - return this._rules[ruleId]; - } - if (builtInRules.has(ruleId)) { - return builtInRules.get(ruleId); - } + /** + * Access rule handler by id (file name). + * @param {string} ruleId Rule id (file name). + * @returns {Rule} Rule object. + */ + get(ruleId) { + if (typeof this._rules[ruleId] === "string") { + this.define(ruleId, require(this._rules[ruleId])); + } + if (this._rules[ruleId]) { + return this._rules[ruleId]; + } + if (builtInRules.has(ruleId)) { + return builtInRules.get(ruleId); + } - return null; - } + return null; + } - *[Symbol.iterator]() { - yield* builtInRules; + *[Symbol.iterator]() { + yield* builtInRules; - for (const ruleId of Object.keys(this._rules)) { - yield [ruleId, this.get(ruleId)]; - } - } + for (const ruleId of Object.keys(this._rules)) { + yield [ruleId, this.get(ruleId)]; + } + } } module.exports = Rules; diff --git a/lib/linter/safe-emitter.js b/lib/linter/safe-emitter.js index f4837c1ddcf5..8dcf9dd4428b 100644 --- a/lib/linter/safe-emitter.js +++ b/lib/linter/safe-emitter.js @@ -30,23 +30,23 @@ * @returns {SafeEmitter} An emitter */ module.exports = () => { - const listeners = Object.create(null); + const listeners = Object.create(null); - return Object.freeze({ - on(eventName, listener) { - if (eventName in listeners) { - listeners[eventName].push(listener); - } else { - listeners[eventName] = [listener]; - } - }, - emit(eventName, ...args) { - if (eventName in listeners) { - listeners[eventName].forEach(listener => listener(...args)); - } - }, - eventNames() { - return Object.keys(listeners); - } - }); + return Object.freeze({ + on(eventName, listener) { + if (eventName in listeners) { + listeners[eventName].push(listener); + } else { + listeners[eventName] = [listener]; + } + }, + emit(eventName, ...args) { + if (eventName in listeners) { + listeners[eventName].forEach(listener => listener(...args)); + } + }, + eventNames() { + return Object.keys(listeners); + }, + }); }; diff --git a/lib/linter/source-code-fixer.js b/lib/linter/source-code-fixer.js index 35de5141492a..cb47c6e7b805 100644 --- a/lib/linter/source-code-fixer.js +++ b/lib/linter/source-code-fixer.js @@ -24,7 +24,7 @@ const BOM = "\uFEFF"; * @private */ function compareMessagesByFixRange(a, b) { - return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1]; + return a.fix.range[0] - b.fix.range[0] || a.fix.range[1] - b.fix.range[1]; } /** @@ -35,7 +35,7 @@ function compareMessagesByFixRange(a, b) { * @private */ function compareMessagesByLocation(a, b) { - return a.line - b.line || a.column - b.column; + return a.line - b.line || a.column - b.column; } //------------------------------------------------------------------------------ @@ -47,7 +47,7 @@ function compareMessagesByLocation(a, b) { * @constructor */ function SourceCodeFixer() { - Object.freeze(this); + Object.freeze(this); } /** @@ -58,95 +58,97 @@ function SourceCodeFixer() { * @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed * @returns {Object} An object containing the fixed text and any unfixed messages. */ -SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) { - debug("Applying fixes"); - - if (shouldFix === false) { - debug("shouldFix parameter was false, not attempting fixes"); - return { - fixed: false, - messages, - output: sourceText - }; - } - - // clone the array - const remainingMessages = [], - fixes = [], - bom = sourceText.startsWith(BOM) ? BOM : "", - text = bom ? sourceText.slice(1) : sourceText; - let lastPos = Number.NEGATIVE_INFINITY, - output = bom; - - /** - * Try to use the 'fix' from a problem. - * @param {Message} problem The message object to apply fixes from - * @returns {boolean} Whether fix was successfully applied - */ - function attemptFix(problem) { - const fix = problem.fix; - const start = fix.range[0]; - const end = fix.range[1]; - - // Remain it as a problem if it's overlapped or it's a negative range - if (lastPos >= start || start > end) { - remainingMessages.push(problem); - return false; - } - - // Remove BOM. - if ((start < 0 && end >= 0) || (start === 0 && fix.text.startsWith(BOM))) { - output = ""; - } - - // Make output to this fix. - output += text.slice(Math.max(0, lastPos), Math.max(0, start)); - output += fix.text; - lastPos = end; - return true; - } - - messages.forEach(problem => { - if (Object.hasOwn(problem, "fix") && problem.fix) { - fixes.push(problem); - } else { - remainingMessages.push(problem); - } - }); - - if (fixes.length) { - debug("Found fixes to apply"); - let fixesWereApplied = false; - - for (const problem of fixes.sort(compareMessagesByFixRange)) { - if (typeof shouldFix !== "function" || shouldFix(problem)) { - attemptFix(problem); - - /* - * The only time attemptFix will fail is if a previous fix was - * applied which conflicts with it. So we can mark this as true. - */ - fixesWereApplied = true; - } else { - remainingMessages.push(problem); - } - } - output += text.slice(Math.max(0, lastPos)); - - return { - fixed: fixesWereApplied, - messages: remainingMessages.sort(compareMessagesByLocation), - output - }; - } - - debug("No fixes to apply"); - return { - fixed: false, - messages, - output: bom + text - }; - +SourceCodeFixer.applyFixes = function (sourceText, messages, shouldFix) { + debug("Applying fixes"); + + if (shouldFix === false) { + debug("shouldFix parameter was false, not attempting fixes"); + return { + fixed: false, + messages, + output: sourceText, + }; + } + + // clone the array + const remainingMessages = [], + fixes = [], + bom = sourceText.startsWith(BOM) ? BOM : "", + text = bom ? sourceText.slice(1) : sourceText; + let lastPos = Number.NEGATIVE_INFINITY, + output = bom; + + /** + * Try to use the 'fix' from a problem. + * @param {Message} problem The message object to apply fixes from + * @returns {boolean} Whether fix was successfully applied + */ + function attemptFix(problem) { + const fix = problem.fix; + const start = fix.range[0]; + const end = fix.range[1]; + + // Remain it as a problem if it's overlapped or it's a negative range + if (lastPos >= start || start > end) { + remainingMessages.push(problem); + return false; + } + + // Remove BOM. + if ( + (start < 0 && end >= 0) || + (start === 0 && fix.text.startsWith(BOM)) + ) { + output = ""; + } + + // Make output to this fix. + output += text.slice(Math.max(0, lastPos), Math.max(0, start)); + output += fix.text; + lastPos = end; + return true; + } + + messages.forEach(problem => { + if (Object.hasOwn(problem, "fix") && problem.fix) { + fixes.push(problem); + } else { + remainingMessages.push(problem); + } + }); + + if (fixes.length) { + debug("Found fixes to apply"); + let fixesWereApplied = false; + + for (const problem of fixes.sort(compareMessagesByFixRange)) { + if (typeof shouldFix !== "function" || shouldFix(problem)) { + attemptFix(problem); + + /* + * The only time attemptFix will fail is if a previous fix was + * applied which conflicts with it. So we can mark this as true. + */ + fixesWereApplied = true; + } else { + remainingMessages.push(problem); + } + } + output += text.slice(Math.max(0, lastPos)); + + return { + fixed: fixesWereApplied, + messages: remainingMessages.sort(compareMessagesByLocation), + output, + }; + } + + debug("No fixes to apply"); + return { + fixed: false, + messages, + output: bom + text, + }; }; module.exports = SourceCodeFixer; diff --git a/lib/linter/timing.js b/lib/linter/timing.js index 232c5f4f2fb4..3548d0036f87 100644 --- a/lib/linter/timing.js +++ b/lib/linter/timing.js @@ -21,7 +21,7 @@ const { startTime, endTime } = require("../shared/stats"); * @private */ function alignLeft(str, len, ch) { - return str + new Array(len - str.length + 1).join(ch || " "); + return str + new Array(len - str.length + 1).join(ch || " "); } /* c8 ignore next */ @@ -34,7 +34,7 @@ function alignLeft(str, len, ch) { * @private */ function alignRight(str, len, ch) { - return new Array(len - str.length + 1).join(ch || " ") + str; + return new Array(len - str.length + 1).join(ch || " ") + str; } //------------------------------------------------------------------------------ @@ -51,19 +51,21 @@ const ALIGN = [alignLeft, alignRight, alignRight]; * @returns {number} the number of rules to show */ function getListSize() { - const MINIMUM_SIZE = 10; + const MINIMUM_SIZE = 10; - if (typeof process.env.TIMING !== "string") { - return MINIMUM_SIZE; - } + if (typeof process.env.TIMING !== "string") { + return MINIMUM_SIZE; + } - if (process.env.TIMING.toLowerCase() === "all") { - return Number.POSITIVE_INFINITY; - } + if (process.env.TIMING.toLowerCase() === "all") { + return Number.POSITIVE_INFINITY; + } - const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10); + const TIMING_ENV_VAR_AS_INTEGER = Number.parseInt(process.env.TIMING, 10); - return TIMING_ENV_VAR_AS_INTEGER > 10 ? TIMING_ENV_VAR_AS_INTEGER : MINIMUM_SIZE; + return TIMING_ENV_VAR_AS_INTEGER > 10 + ? TIMING_ENV_VAR_AS_INTEGER + : MINIMUM_SIZE; } /* c8 ignore next */ @@ -74,96 +76,97 @@ function getListSize() { * @private */ function display(data) { - let total = 0; - const rows = Object.keys(data) - .map(key => { - const time = data[key]; - - total += time; - return [key, time]; - }) - .sort((a, b) => b[1] - a[1]) - .slice(0, getListSize()); - - rows.forEach(row => { - row.push(`${(row[1] * 100 / total).toFixed(1)}%`); - row[1] = row[1].toFixed(3); - }); - - rows.unshift(HEADERS); - - const widths = []; - - rows.forEach(row => { - const len = row.length; - - for (let i = 0; i < len; i++) { - const n = row[i].length; - - if (!widths[i] || n > widths[i]) { - widths[i] = n; - } - } - }); - - const table = rows.map(row => ( - row - .map((cell, index) => ALIGN[index](cell, widths[index])) - .join(" | ") - )); - - table.splice(1, 0, widths.map((width, index) => { - const extraAlignment = index !== 0 && index !== widths.length - 1 ? 2 : 1; - - return ALIGN[index](":", width + extraAlignment, "-"); - }).join("|")); - - console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function + let total = 0; + const rows = Object.keys(data) + .map(key => { + const time = data[key]; + + total += time; + return [key, time]; + }) + .sort((a, b) => b[1] - a[1]) + .slice(0, getListSize()); + + rows.forEach(row => { + row.push(`${((row[1] * 100) / total).toFixed(1)}%`); + row[1] = row[1].toFixed(3); + }); + + rows.unshift(HEADERS); + + const widths = []; + + rows.forEach(row => { + const len = row.length; + + for (let i = 0; i < len; i++) { + const n = row[i].length; + + if (!widths[i] || n > widths[i]) { + widths[i] = n; + } + } + }); + + const table = rows.map(row => + row.map((cell, index) => ALIGN[index](cell, widths[index])).join(" | "), + ); + + table.splice( + 1, + 0, + widths + .map((width, index) => { + const extraAlignment = + index !== 0 && index !== widths.length - 1 ? 2 : 1; + + return ALIGN[index](":", width + extraAlignment, "-"); + }) + .join("|"), + ); + + console.log(table.join("\n")); // eslint-disable-line no-console -- Debugging function } /* c8 ignore next */ -module.exports = (function() { - - const data = Object.create(null); - - /** - * Time the run - * @param {any} key key from the data object - * @param {Function} fn function to be called - * @param {boolean} stats if 'stats' is true, return the result and the time difference - * @returns {Function} function to be executed - * @private - */ - function time(key, fn, stats) { - - return function(...args) { - - const t = startTime(); - const result = fn(...args); - const tdiff = endTime(t); - - if (enabled) { - if (typeof data[key] === "undefined") { - data[key] = 0; - } - - data[key] += tdiff; - } - - return stats ? { result, tdiff } : result; - }; - } - - if (enabled) { - process.on("exit", () => { - display(data); - }); - } - - return { - time, - enabled, - getListSize - }; - -}()); +module.exports = (function () { + const data = Object.create(null); + + /** + * Time the run + * @param {any} key key from the data object + * @param {Function} fn function to be called + * @param {boolean} stats if 'stats' is true, return the result and the time difference + * @returns {Function} function to be executed + * @private + */ + function time(key, fn, stats) { + return function (...args) { + const t = startTime(); + const result = fn(...args); + const tdiff = endTime(t); + + if (enabled) { + if (typeof data[key] === "undefined") { + data[key] = 0; + } + + data[key] += tdiff; + } + + return stats ? { result, tdiff } : result; + }; + } + + if (enabled) { + process.on("exit", () => { + display(data); + }); + } + + return { + time, + enabled, + getListSize, + }; +})(); diff --git a/lib/linter/vfile.js b/lib/linter/vfile.js index bb2da0a7795d..4bdbf9912860 100644 --- a/lib/linter/vfile.js +++ b/lib/linter/vfile.js @@ -21,9 +21,9 @@ * @returns {boolean} `true` if the value has a BOM, `false` otherwise. */ function hasUnicodeBOM(value) { - return typeof value === "string" - ? value.charCodeAt(0) === 0xFEFF - : value[0] === 0xEF && value[1] === 0xBB && value[2] === 0xBF; + return typeof value === "string" + ? value.charCodeAt(0) === 0xfeff + : value[0] === 0xef && value[1] === 0xbb && value[2] === 0xbf; } /** @@ -32,26 +32,24 @@ function hasUnicodeBOM(value) { * @returns {string|Uint8Array} The stripped value. */ function stripUnicodeBOM(value) { - - if (!hasUnicodeBOM(value)) { - return value; - } - - if (typeof value === "string") { - - /* - * Check Unicode BOM. - * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. - * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters - */ - return value.slice(1); - } - - /* - * In a Uint8Array, the BOM is represented by three bytes: 0xEF, 0xBB, and 0xBF, - * so we can just remove the first three bytes. - */ - return value.slice(3); + if (!hasUnicodeBOM(value)) { + return value; + } + + if (typeof value === "string") { + /* + * Check Unicode BOM. + * In JavaScript, string data is stored as UTF-16, so BOM is 0xFEFF. + * http://www.ecma-international.org/ecma-262/6.0/#sec-unicode-format-control-characters + */ + return value.slice(1); + } + + /* + * In a Uint8Array, the BOM is represented by three bytes: 0xEF, 0xBB, and 0xBF, + * so we can just remove the first three bytes. + */ + return value.slice(3); } //------------------------------------------------------------------------------ @@ -63,56 +61,55 @@ function stripUnicodeBOM(value) { * @implements {File} */ class VFile { - - /** - * The file path including any processor-created virtual path. - * @type {string} - * @readonly - */ - path; - - /** - * The file path on disk. - * @type {string} - * @readonly - */ - physicalPath; - - /** - * The file contents. - * @type {string|Uint8Array} - * @readonly - */ - body; - - /** - * The raw body of the file, including a BOM if present. - * @type {string|Uint8Array} - * @readonly - */ - rawBody; - - /** - * Indicates whether the file has a byte order mark (BOM). - * @type {boolean} - * @readonly - */ - bom; - - /** - * Creates a new instance. - * @param {string} path The file path. - * @param {string|Uint8Array} body The file contents. - * @param {Object} [options] Additional options. - * @param {string} [options.physicalPath] The file path on disk. - */ - constructor(path, body, { physicalPath } = {}) { - this.path = path; - this.physicalPath = physicalPath ?? path; - this.bom = hasUnicodeBOM(body); - this.body = stripUnicodeBOM(body); - this.rawBody = body; - } + /** + * The file path including any processor-created virtual path. + * @type {string} + * @readonly + */ + path; + + /** + * The file path on disk. + * @type {string} + * @readonly + */ + physicalPath; + + /** + * The file contents. + * @type {string|Uint8Array} + * @readonly + */ + body; + + /** + * The raw body of the file, including a BOM if present. + * @type {string|Uint8Array} + * @readonly + */ + rawBody; + + /** + * Indicates whether the file has a byte order mark (BOM). + * @type {boolean} + * @readonly + */ + bom; + + /** + * Creates a new instance. + * @param {string} path The file path. + * @param {string|Uint8Array} body The file contents. + * @param {Object} [options] Additional options. + * @param {string} [options.physicalPath] The file path on disk. + */ + constructor(path, body, { physicalPath } = {}) { + this.path = path; + this.physicalPath = physicalPath ?? path; + this.bom = hasUnicodeBOM(body); + this.body = stripUnicodeBOM(body); + this.rawBody = body; + } } module.exports = { VFile }; diff --git a/lib/options.js b/lib/options.js index 51d077f2f729..e97f1f9f080e 100644 --- a/lib/options.js +++ b/lib/options.js @@ -75,387 +75,401 @@ const optionator = require("optionator"); * @param {boolean} usingFlatConfig Indicates if flat config is being used. * @returns {Object} The optionator instance. */ -module.exports = function(usingFlatConfig) { +module.exports = function (usingFlatConfig) { + let lookupFlag; - let lookupFlag; + if (usingFlatConfig) { + lookupFlag = { + option: "config-lookup", + type: "Boolean", + default: "true", + description: "Disable look up for eslint.config.js", + }; + } else { + lookupFlag = { + option: "eslintrc", + type: "Boolean", + default: "true", + description: "Disable use of configuration from .eslintrc.*", + }; + } - if (usingFlatConfig) { - lookupFlag = { - option: "config-lookup", - type: "Boolean", - default: "true", - description: "Disable look up for eslint.config.js" - }; - } else { - lookupFlag = { - option: "eslintrc", - type: "Boolean", - default: "true", - description: "Disable use of configuration from .eslintrc.*" - }; - } + let envFlag; - let envFlag; + if (!usingFlatConfig) { + envFlag = { + option: "env", + type: "[String]", + description: "Specify environments", + }; + } - if (!usingFlatConfig) { - envFlag = { - option: "env", - type: "[String]", - description: "Specify environments" - }; - } + let inspectConfigFlag; - let inspectConfigFlag; + if (usingFlatConfig) { + inspectConfigFlag = { + option: "inspect-config", + type: "Boolean", + description: + "Open the config inspector with the current configuration", + }; + } - if (usingFlatConfig) { - inspectConfigFlag = { - option: "inspect-config", - type: "Boolean", - description: "Open the config inspector with the current configuration" - }; - } + let extFlag; - let extFlag; + if (!usingFlatConfig) { + extFlag = { + option: "ext", + type: "[String]", + description: "Specify JavaScript file extensions", + }; + } else { + extFlag = { + option: "ext", + type: "[String]", + description: "Specify additional file extensions to lint", + }; + } - if (!usingFlatConfig) { - extFlag = { - option: "ext", - type: "[String]", - description: "Specify JavaScript file extensions" - }; - } else { - extFlag = { - option: "ext", - type: "[String]", - description: "Specify additional file extensions to lint" - }; - } + let resolvePluginsFlag; - let resolvePluginsFlag; + if (!usingFlatConfig) { + resolvePluginsFlag = { + option: "resolve-plugins-relative-to", + type: "path::String", + description: + "A folder where plugins should be resolved from, CWD by default", + }; + } - if (!usingFlatConfig) { - resolvePluginsFlag = { - option: "resolve-plugins-relative-to", - type: "path::String", - description: "A folder where plugins should be resolved from, CWD by default" - }; - } + let rulesDirFlag; - let rulesDirFlag; + if (!usingFlatConfig) { + rulesDirFlag = { + option: "rulesdir", + type: "[path::String]", + description: + "Load additional rules from this directory. Deprecated: Use rules from plugins", + }; + } - if (!usingFlatConfig) { - rulesDirFlag = { - option: "rulesdir", - type: "[path::String]", - description: "Load additional rules from this directory. Deprecated: Use rules from plugins" - }; - } + let ignorePathFlag; - let ignorePathFlag; + if (!usingFlatConfig) { + ignorePathFlag = { + option: "ignore-path", + type: "path::String", + description: "Specify path of ignore file", + }; + } - if (!usingFlatConfig) { - ignorePathFlag = { - option: "ignore-path", - type: "path::String", - description: "Specify path of ignore file" - }; - } + let statsFlag; - let statsFlag; + if (usingFlatConfig) { + statsFlag = { + option: "stats", + type: "Boolean", + default: "false", + description: "Add statistics to the lint report", + }; + } - if (usingFlatConfig) { - statsFlag = { - option: "stats", - type: "Boolean", - default: "false", - description: "Add statistics to the lint report" - }; - } + let warnIgnoredFlag; - let warnIgnoredFlag; + if (usingFlatConfig) { + warnIgnoredFlag = { + option: "warn-ignored", + type: "Boolean", + default: "true", + description: + "Suppress warnings when the file list includes ignored files", + }; + } - if (usingFlatConfig) { - warnIgnoredFlag = { - option: "warn-ignored", - type: "Boolean", - default: "true", - description: "Suppress warnings when the file list includes ignored files" - }; - } + let flagFlag; - let flagFlag; + if (usingFlatConfig) { + flagFlag = { + option: "flag", + type: "[String]", + description: "Enable a feature flag", + }; + } - if (usingFlatConfig) { - flagFlag = { - option: "flag", - type: "[String]", - description: "Enable a feature flag" - }; - } + let reportUnusedInlineConfigsFlag; - let reportUnusedInlineConfigsFlag; + if (usingFlatConfig) { + reportUnusedInlineConfigsFlag = { + option: "report-unused-inline-configs", + type: "String", + default: void 0, + description: + "Adds reported errors for unused eslint inline config comments", + enum: ["off", "warn", "error", "0", "1", "2"], + }; + } - if (usingFlatConfig) { - reportUnusedInlineConfigsFlag = { - option: "report-unused-inline-configs", - type: "String", - default: void 0, - description: "Adds reported errors for unused eslint inline config comments", - enum: ["off", "warn", "error", "0", "1", "2"] - }; - } - - return optionator({ - prepend: "eslint [options] file.js [file.js] [dir]", - defaults: { - concatRepeatedArrays: true, - mergeRepeatedObjects: true - }, - options: [ - { - heading: "Basic configuration" - }, - lookupFlag, - { - option: "config", - alias: "c", - type: "path::String", - description: usingFlatConfig - ? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs" - : "Use this configuration, overriding .eslintrc.* config options if present" - }, - inspectConfigFlag, - envFlag, - extFlag, - { - option: "global", - type: "[String]", - description: "Define global variables" - }, - { - option: "parser", - type: "String", - description: "Specify the parser to be used" - }, - { - option: "parser-options", - type: "Object", - description: "Specify parser options" - }, - resolvePluginsFlag, - { - heading: "Specify Rules and Plugins" - }, - { - option: "plugin", - type: "[String]", - description: "Specify plugins" - }, - { - option: "rule", - type: "Object", - description: "Specify rules" - }, - rulesDirFlag, - { - heading: "Fix Problems" - }, - { - option: "fix", - type: "Boolean", - default: false, - description: "Automatically fix problems" - }, - { - option: "fix-dry-run", - type: "Boolean", - default: false, - description: "Automatically fix problems without saving the changes to the file system" - }, - { - option: "fix-type", - type: "Array", - description: "Specify the types of fixes to apply (directive, problem, suggestion, layout)" - }, - { - heading: "Ignore Files" - }, - ignorePathFlag, - { - option: "ignore", - type: "Boolean", - default: "true", - description: "Disable use of ignore files and patterns" - }, - { - option: "ignore-pattern", - type: "[String]", - description: `Patterns of files to ignore${usingFlatConfig ? "" : " (in addition to those in .eslintignore)"}`, - concatRepeatedArrays: [true, { - oneValuePerFlag: true - }] - }, - { - heading: "Use stdin" - }, - { - option: "stdin", - type: "Boolean", - default: "false", - description: "Lint code provided on " - }, - { - option: "stdin-filename", - type: "String", - description: "Specify filename to process STDIN as" - }, - { - heading: "Handle Warnings" - }, - { - option: "quiet", - type: "Boolean", - default: "false", - description: "Report errors only" - }, - { - option: "max-warnings", - type: "Int", - default: "-1", - description: "Number of warnings to trigger nonzero exit code" - }, - { - heading: "Output" - }, - { - option: "output-file", - alias: "o", - type: "path::String", - description: "Specify file to write report to" - }, - { - option: "format", - alias: "f", - type: "String", - default: "stylish", - description: "Use a specific output format" - }, - { - option: "color", - type: "Boolean", - alias: "no-color", - description: "Force enabling/disabling of color" - }, - { - heading: "Inline configuration comments" - }, - { - option: "inline-config", - type: "Boolean", - default: "true", - description: "Prevent comments from changing config or rules" - }, - { - option: "report-unused-disable-directives", - type: "Boolean", - default: void 0, - description: "Adds reported errors for unused eslint-disable and eslint-enable directives" - }, - { - option: "report-unused-disable-directives-severity", - type: "String", - default: void 0, - description: "Chooses severity level for reporting unused eslint-disable and eslint-enable directives", - enum: ["off", "warn", "error", "0", "1", "2"] - }, - reportUnusedInlineConfigsFlag, - { - heading: "Caching" - }, - { - option: "cache", - type: "Boolean", - default: "false", - description: "Only check changed files" - }, - { - option: "cache-file", - type: "path::String", - default: ".eslintcache", - description: "Path to the cache file. Deprecated: use --cache-location" - }, - { - option: "cache-location", - type: "path::String", - description: "Path to the cache file or directory" - }, - { - option: "cache-strategy", - dependsOn: ["cache"], - type: "String", - default: "metadata", - enum: ["metadata", "content"], - description: "Strategy to use for detecting changed files in the cache" - }, - { - heading: "Miscellaneous" - }, - { - option: "init", - type: "Boolean", - default: "false", - description: "Run config initialization wizard" - }, - { - option: "env-info", - type: "Boolean", - default: "false", - description: "Output execution environment information" - }, - { - option: "error-on-unmatched-pattern", - type: "Boolean", - default: "true", - description: "Prevent errors when pattern is unmatched" - }, - { - option: "exit-on-fatal-error", - type: "Boolean", - default: "false", - description: "Exit with exit code 2 in case of fatal error" - }, - warnIgnoredFlag, - { - option: "pass-on-no-patterns", - type: "Boolean", - default: false, - description: "Exit with exit code 0 in case no file patterns are passed" - }, - { - option: "debug", - type: "Boolean", - default: false, - description: "Output debugging information" - }, - { - option: "help", - alias: "h", - type: "Boolean", - description: "Show help" - }, - { - option: "version", - alias: "v", - type: "Boolean", - description: "Output the version number" - }, - { - option: "print-config", - type: "path::String", - description: "Print the configuration for the given file" - }, - statsFlag, - flagFlag - ].filter(value => !!value) - }); + return optionator({ + prepend: "eslint [options] file.js [file.js] [dir]", + defaults: { + concatRepeatedArrays: true, + mergeRepeatedObjects: true, + }, + options: [ + { + heading: "Basic configuration", + }, + lookupFlag, + { + option: "config", + alias: "c", + type: "path::String", + description: usingFlatConfig + ? "Use this configuration instead of eslint.config.js, eslint.config.mjs, or eslint.config.cjs" + : "Use this configuration, overriding .eslintrc.* config options if present", + }, + inspectConfigFlag, + envFlag, + extFlag, + { + option: "global", + type: "[String]", + description: "Define global variables", + }, + { + option: "parser", + type: "String", + description: "Specify the parser to be used", + }, + { + option: "parser-options", + type: "Object", + description: "Specify parser options", + }, + resolvePluginsFlag, + { + heading: "Specify Rules and Plugins", + }, + { + option: "plugin", + type: "[String]", + description: "Specify plugins", + }, + { + option: "rule", + type: "Object", + description: "Specify rules", + }, + rulesDirFlag, + { + heading: "Fix Problems", + }, + { + option: "fix", + type: "Boolean", + default: false, + description: "Automatically fix problems", + }, + { + option: "fix-dry-run", + type: "Boolean", + default: false, + description: + "Automatically fix problems without saving the changes to the file system", + }, + { + option: "fix-type", + type: "Array", + description: + "Specify the types of fixes to apply (directive, problem, suggestion, layout)", + }, + { + heading: "Ignore Files", + }, + ignorePathFlag, + { + option: "ignore", + type: "Boolean", + default: "true", + description: "Disable use of ignore files and patterns", + }, + { + option: "ignore-pattern", + type: "[String]", + description: `Patterns of files to ignore${usingFlatConfig ? "" : " (in addition to those in .eslintignore)"}`, + concatRepeatedArrays: [ + true, + { + oneValuePerFlag: true, + }, + ], + }, + { + heading: "Use stdin", + }, + { + option: "stdin", + type: "Boolean", + default: "false", + description: "Lint code provided on ", + }, + { + option: "stdin-filename", + type: "String", + description: "Specify filename to process STDIN as", + }, + { + heading: "Handle Warnings", + }, + { + option: "quiet", + type: "Boolean", + default: "false", + description: "Report errors only", + }, + { + option: "max-warnings", + type: "Int", + default: "-1", + description: "Number of warnings to trigger nonzero exit code", + }, + { + heading: "Output", + }, + { + option: "output-file", + alias: "o", + type: "path::String", + description: "Specify file to write report to", + }, + { + option: "format", + alias: "f", + type: "String", + default: "stylish", + description: "Use a specific output format", + }, + { + option: "color", + type: "Boolean", + alias: "no-color", + description: "Force enabling/disabling of color", + }, + { + heading: "Inline configuration comments", + }, + { + option: "inline-config", + type: "Boolean", + default: "true", + description: "Prevent comments from changing config or rules", + }, + { + option: "report-unused-disable-directives", + type: "Boolean", + default: void 0, + description: + "Adds reported errors for unused eslint-disable and eslint-enable directives", + }, + { + option: "report-unused-disable-directives-severity", + type: "String", + default: void 0, + description: + "Chooses severity level for reporting unused eslint-disable and eslint-enable directives", + enum: ["off", "warn", "error", "0", "1", "2"], + }, + reportUnusedInlineConfigsFlag, + { + heading: "Caching", + }, + { + option: "cache", + type: "Boolean", + default: "false", + description: "Only check changed files", + }, + { + option: "cache-file", + type: "path::String", + default: ".eslintcache", + description: + "Path to the cache file. Deprecated: use --cache-location", + }, + { + option: "cache-location", + type: "path::String", + description: "Path to the cache file or directory", + }, + { + option: "cache-strategy", + dependsOn: ["cache"], + type: "String", + default: "metadata", + enum: ["metadata", "content"], + description: + "Strategy to use for detecting changed files in the cache", + }, + { + heading: "Miscellaneous", + }, + { + option: "init", + type: "Boolean", + default: "false", + description: "Run config initialization wizard", + }, + { + option: "env-info", + type: "Boolean", + default: "false", + description: "Output execution environment information", + }, + { + option: "error-on-unmatched-pattern", + type: "Boolean", + default: "true", + description: "Prevent errors when pattern is unmatched", + }, + { + option: "exit-on-fatal-error", + type: "Boolean", + default: "false", + description: "Exit with exit code 2 in case of fatal error", + }, + warnIgnoredFlag, + { + option: "pass-on-no-patterns", + type: "Boolean", + default: false, + description: + "Exit with exit code 0 in case no file patterns are passed", + }, + { + option: "debug", + type: "Boolean", + default: false, + description: "Output debugging information", + }, + { + option: "help", + alias: "h", + type: "Boolean", + description: "Show help", + }, + { + option: "version", + alias: "v", + type: "Boolean", + description: "Output the version number", + }, + { + option: "print-config", + type: "path::String", + description: "Print the configuration for the given file", + }, + statsFlag, + flagFlag, + ].filter(value => !!value), + }); }; diff --git a/lib/rule-tester/index.js b/lib/rule-tester/index.js index 58f67ee49403..4fa651eb8fe5 100644 --- a/lib/rule-tester/index.js +++ b/lib/rule-tester/index.js @@ -3,5 +3,5 @@ const RuleTester = require("./rule-tester"); module.exports = { - RuleTester + RuleTester, }; diff --git a/lib/rule-tester/rule-tester.js b/lib/rule-tester/rule-tester.js index 4ffcfed56572..b997c54ed0ab 100644 --- a/lib/rule-tester/rule-tester.js +++ b/lib/rule-tester/rule-tester.js @@ -10,19 +10,21 @@ // Requirements //------------------------------------------------------------------------------ -const - assert = require("node:assert"), - util = require("node:util"), - path = require("node:path"), - equal = require("fast-deep-equal"), - Traverser = require("../shared/traverser"), - { getRuleOptionsSchema } = require("../config/flat-config-helpers"), - { Linter, SourceCodeFixer } = require("../linter"), - { interpolate, getPlaceholderMatcher } = require("../linter/interpolate"), - stringify = require("json-stable-stringify-without-jsonify"); +const assert = require("node:assert"), + util = require("node:util"), + path = require("node:path"), + equal = require("fast-deep-equal"), + Traverser = require("../shared/traverser"), + { getRuleOptionsSchema } = require("../config/flat-config-helpers"), + { Linter, SourceCodeFixer } = require("../linter"), + { interpolate, getPlaceholderMatcher } = require("../linter/interpolate"), + stringify = require("json-stable-stringify-without-jsonify"); const { FlatConfigArray } = require("../config/flat-config-array"); -const { defaultConfig } = require("../config/default-config"); +const { + defaultConfig, + defaultRuleTesterConfig, +} = require("../config/default-config"); const ajv = require("../shared/ajv")({ strictDefaults: true }); @@ -41,7 +43,6 @@ const { SourceCode } = require("../languages/js/source-code"); /** @typedef {import("../shared/types").LanguageOptions} LanguageOptions */ /** @typedef {import("../shared/types").Rule} Rule */ - /** * A test case that is expected to pass lint. * @typedef {Object} ValidTestCase @@ -106,30 +107,30 @@ let sharedDefaultConfig = { rules: {} }; * configuration */ const RuleTesterParameters = [ - "name", - "code", - "filename", - "options", - "before", - "after", - "errors", - "output", - "only" + "name", + "code", + "filename", + "options", + "before", + "after", + "errors", + "output", + "only", ]; /* * All allowed property names in error objects. */ const errorObjectParameters = new Set([ - "message", - "messageId", - "data", - "type", - "line", - "column", - "endLine", - "endColumn", - "suggestions" + "message", + "messageId", + "data", + "type", + "line", + "column", + "endLine", + "endColumn", + "suggestions", ]); const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key => `'${key}'`).join(", ")}]`; @@ -137,30 +138,28 @@ const friendlyErrorObjectParameterList = `[${[...errorObjectParameters].map(key * All allowed property names in suggestion objects. */ const suggestionObjectParameters = new Set([ - "desc", - "messageId", - "data", - "output" + "desc", + "messageId", + "data", + "output", ]); const friendlySuggestionObjectParameterList = `[${[...suggestionObjectParameters].map(key => `'${key}'`).join(", ")}]`; /* * Ignored test case properties when checking for test case duplicates. */ -const duplicationIgnoredParameters = new Set([ - "name", - "errors", - "output" -]); +const duplicationIgnoredParameters = new Set(["name", "errors", "output"]); const forbiddenMethods = [ - "applyInlineConfig", - "applyLanguageOptions", - "finalize" + "applyInlineConfig", + "applyLanguageOptions", + "finalize", ]; /** @type {Map} */ -const forbiddenMethodCalls = new Map(forbiddenMethods.map(methodName => ([methodName, new WeakSet()]))); +const forbiddenMethodCalls = new Map( + forbiddenMethods.map(methodName => [methodName, new WeakSet()]), +); const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); @@ -171,23 +170,23 @@ const hasOwnProperty = Function.call.bind(Object.hasOwnProperty); * @returns {any} A cloned value. */ function cloneDeeplyExcludesParent(x) { - if (typeof x === "object" && x !== null) { - if (Array.isArray(x)) { - return x.map(cloneDeeplyExcludesParent); - } + if (typeof x === "object" && x !== null) { + if (Array.isArray(x)) { + return x.map(cloneDeeplyExcludesParent); + } - const retv = {}; + const retv = {}; - for (const key in x) { - if (key !== "parent" && hasOwnProperty(x, key)) { - retv[key] = cloneDeeplyExcludesParent(x[key]); - } - } + for (const key in x) { + if (key !== "parent" && hasOwnProperty(x, key)) { + retv[key] = cloneDeeplyExcludesParent(x[key]); + } + } - return retv; - } + return retv; + } - return x; + return x; } /** @@ -196,18 +195,18 @@ function cloneDeeplyExcludesParent(x) { * @returns {void} */ function freezeDeeply(x) { - if (typeof x === "object" && x !== null) { - if (Array.isArray(x)) { - x.forEach(freezeDeeply); - } else { - for (const key in x) { - if (key !== "parent" && hasOwnProperty(x, key)) { - freezeDeeply(x[key]); - } - } - } - Object.freeze(x); - } + if (typeof x === "object" && x !== null) { + if (Array.isArray(x)) { + x.forEach(freezeDeeply); + } else { + for (const key in x) { + if (key !== "parent" && hasOwnProperty(x, key)) { + freezeDeeply(x[key]); + } + } + } + Object.freeze(x); + } } /** @@ -216,13 +215,13 @@ function freezeDeeply(x) { * @returns {string} The sanitized text. */ function sanitize(text) { - if (typeof text !== "string") { - return ""; - } - return text.replace( - /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls - c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}` - ); + if (typeof text !== "string") { + return ""; + } + return text.replace( + /[\u0000-\u0009\u000b-\u001a]/gu, // eslint-disable-line no-control-regex -- Escaping controls + c => `\\u${c.codePointAt(0).toString(16).padStart(4, "0")}`, + ); } /** @@ -232,25 +231,28 @@ function sanitize(text) { * @returns {void} */ function defineStartEndAsError(objName, node) { - Object.defineProperties(node, { - start: { - get() { - throw new Error(`Use ${objName}.range[0] instead of ${objName}.start`); - }, - configurable: true, - enumerable: false - }, - end: { - get() { - throw new Error(`Use ${objName}.range[1] instead of ${objName}.end`); - }, - configurable: true, - enumerable: false - } - }); + Object.defineProperties(node, { + start: { + get() { + throw new Error( + `Use ${objName}.range[0] instead of ${objName}.start`, + ); + }, + configurable: true, + enumerable: false, + }, + end: { + get() { + throw new Error( + `Use ${objName}.range[1] instead of ${objName}.end`, + ); + }, + configurable: true, + enumerable: false, + }, + }); } - /** * Define `start`/`end` properties of all nodes of the given AST as throwing error. * @param {ASTNode} ast The root node to errorize `start`/`end` properties. @@ -258,9 +260,12 @@ function defineStartEndAsError(objName, node) { * @returns {void} */ function defineStartEndAsErrorInTree(ast, visitorKeys) { - Traverser.traverse(ast, { visitorKeys, enter: defineStartEndAsError.bind(null, "node") }); - ast.tokens.forEach(defineStartEndAsError.bind(null, "token")); - ast.comments.forEach(defineStartEndAsError.bind(null, "token")); + Traverser.traverse(ast, { + visitorKeys, + enter: defineStartEndAsError.bind(null, "node"), + }); + ast.tokens.forEach(defineStartEndAsError.bind(null, "token")); + ast.comments.forEach(defineStartEndAsError.bind(null, "token")); } /** @@ -270,28 +275,27 @@ function defineStartEndAsErrorInTree(ast, visitorKeys) { * @returns {Parser} Wrapped parser object. */ function wrapParser(parser) { - - if (typeof parser.parseForESLint === "function") { - return { - [parserSymbol]: parser, - parseForESLint(...args) { - const ret = parser.parseForESLint(...args); - - defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys); - return ret; - } - }; - } - - return { - [parserSymbol]: parser, - parse(...args) { - const ast = parser.parse(...args); - - defineStartEndAsErrorInTree(ast); - return ast; - } - }; + if (typeof parser.parseForESLint === "function") { + return { + [parserSymbol]: parser, + parseForESLint(...args) { + const ret = parser.parseForESLint(...args); + + defineStartEndAsErrorInTree(ret.ast, ret.visitorKeys); + return ret; + }, + }; + } + + return { + [parserSymbol]: parser, + parse(...args) { + const ast = parser.parse(...args); + + defineStartEndAsErrorInTree(ast); + return ast; + }, + }; } /** @@ -301,25 +305,23 @@ function wrapParser(parser) { * @returns {Function} The function that throws the error. */ function throwForbiddenMethodError(methodName, prototype) { + const original = prototype[methodName]; - const original = prototype[methodName]; + return function (...args) { + const called = forbiddenMethodCalls.get(methodName); - return function(...args) { + /* eslint-disable no-invalid-this -- needed to operate as a method. */ + if (!called.has(this)) { + called.add(this); - const called = forbiddenMethodCalls.get(methodName); + return original.apply(this, args); + } + /* eslint-enable no-invalid-this -- not needed past this point */ - /* eslint-disable no-invalid-this -- needed to operate as a method. */ - if (!called.has(this)) { - called.add(this); - - return original.apply(this, args); - } - /* eslint-enable no-invalid-this -- not needed past this point */ - - throw new Error( - `\`SourceCode#${methodName}()\` cannot be called inside a rule.` - ); - }; + throw new Error( + `\`SourceCode#${methodName}()\` cannot be called inside a rule.`, + ); + }; } /** @@ -328,9 +330,9 @@ function throwForbiddenMethodError(methodName, prototype) { * @returns {string[]} Array of placeholder names */ function getMessagePlaceholders(message) { - const matcher = getPlaceholderMatcher(); + const matcher = getPlaceholderMatcher(); - return Array.from(message.matchAll(matcher), ([, name]) => name.trim()); + return Array.from(message.matchAll(matcher), ([, name]) => name.trim()); } /** @@ -342,17 +344,19 @@ function getMessagePlaceholders(message) { * @returns {string[]} Missing placeholder names */ function getUnsubstitutedMessagePlaceholders(message, raw, data = {}) { - const unsubstituted = getMessagePlaceholders(message); + const unsubstituted = getMessagePlaceholders(message); - if (unsubstituted.length === 0) { - return []; - } + if (unsubstituted.length === 0) { + return []; + } - // Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property - const known = getMessagePlaceholders(raw); - const provided = Object.keys(data); + // Remove false positives by only counting placeholders in the raw message, which were not provided in the data matcher or added with a data property + const known = getMessagePlaceholders(raw); + const provided = Object.keys(data); - return unsubstituted.filter(name => known.includes(name) && !provided.includes(name)); + return unsubstituted.filter( + name => known.includes(name) && !provided.includes(name), + ); } const metaSchemaDescription = ` @@ -381,14 +385,14 @@ const IT_ONLY = Symbol("itOnly"); * @returns {any} Returned value of `method`. */ function itDefaultHandler(text, method) { - try { - return method.call(this); - } catch (err) { - if (err instanceof assert.AssertionError) { - err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`; - } - throw err; - } + try { + return method.call(this); + } catch (err) { + if (err instanceof assert.AssertionError) { + err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`; + } + throw err; + } } /** @@ -399,907 +403,1165 @@ function itDefaultHandler(text, method) { * @returns {any} Returned value of `method`. */ function describeDefaultHandler(text, method) { - return method.call(this); + return method.call(this); } /** * Mocha test wrapper. */ class RuleTester { - - /** - * Creates a new instance of RuleTester. - * @param {Object} [testerConfig] Optional, extra configuration for the tester - */ - constructor(testerConfig = {}) { - - /** - * The configuration to use for this tester. Combination of the tester - * configuration and the default configuration. - * @type {Object} - */ - this.testerConfig = [ - sharedDefaultConfig, - testerConfig, - { rules: { "rule-tester/validate-ast": "error" } } - ]; - - this.linter = new Linter({ configType: "flat" }); - } - - /** - * Set the configuration to use for all future tests - * @param {Object} config the configuration to use. - * @throws {TypeError} If non-object config. - * @returns {void} - */ - static setDefaultConfig(config) { - if (typeof config !== "object" || config === null) { - throw new TypeError("RuleTester.setDefaultConfig: config must be an object"); - } - sharedDefaultConfig = config; - - // Make sure the rules object exists since it is assumed to exist later - sharedDefaultConfig.rules = sharedDefaultConfig.rules || {}; - } - - /** - * Get the current configuration used for all tests - * @returns {Object} the current configuration - */ - static getDefaultConfig() { - return sharedDefaultConfig; - } - - /** - * Reset the configuration to the initial configuration of the tester removing - * any changes made until now. - * @returns {void} - */ - static resetDefaultConfig() { - sharedDefaultConfig = { - rules: { - ...testerDefaultConfig.rules - } - }; - } - - - /* - * If people use `mocha test.js --watch` command, `describe` and `it` function - * instances are different for each execution. So `describe` and `it` should get fresh instance - * always. - */ - static get describe() { - return ( - this[DESCRIBE] || - (typeof describe === "function" ? describe : describeDefaultHandler) - ); - } - - static set describe(value) { - this[DESCRIBE] = value; - } - - static get it() { - return ( - this[IT] || - (typeof it === "function" ? it : itDefaultHandler) - ); - } - - static set it(value) { - this[IT] = value; - } - - /** - * Adds the `only` property to a test to run it in isolation. - * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself. - * @returns {ValidTestCase | InvalidTestCase} The test with `only` set. - */ - static only(item) { - if (typeof item === "string") { - return { code: item, only: true }; - } - - return { ...item, only: true }; - } - - static get itOnly() { - if (typeof this[IT_ONLY] === "function") { - return this[IT_ONLY]; - } - if (typeof this[IT] === "function" && typeof this[IT].only === "function") { - return Function.bind.call(this[IT].only, this[IT]); - } - if (typeof it === "function" && typeof it.only === "function") { - return Function.bind.call(it.only, it); - } - - if (typeof this[DESCRIBE] === "function" || typeof this[IT] === "function") { - throw new Error( - "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + - "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more." - ); - } - if (typeof it === "function") { - throw new Error("The current test framework does not support exclusive tests with `only`."); - } - throw new Error("To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha."); - } - - static set itOnly(value) { - this[IT_ONLY] = value; - } - - - /** - * Adds a new rule test to execute. - * @param {string} ruleName The name of the rule to run. - * @param {Rule} rule The rule to test. - * @param {{ - * valid: (ValidTestCase | string)[], - * invalid: InvalidTestCase[] - * }} test The collection of tests to run. - * @throws {TypeError|Error} If `rule` is not an object with a `create` method, - * or if non-object `test`, or if a required scenario of the given type is missing. - * @returns {void} - */ - run(ruleName, rule, test) { - - const testerConfig = this.testerConfig, - requiredScenarios = ["valid", "invalid"], - scenarioErrors = [], - linter = this.linter, - ruleId = `rule-to-test/${ruleName}`; - - const seenValidTestCases = new Set(); - const seenInvalidTestCases = new Set(); - - if (!rule || typeof rule !== "object" || typeof rule.create !== "function") { - throw new TypeError("Rule must be an object with a `create` method"); - } - - if (!test || typeof test !== "object") { - throw new TypeError(`Test Scenarios for rule ${ruleName} : Could not find test scenario object`); - } - - requiredScenarios.forEach(scenarioType => { - if (!test[scenarioType]) { - scenarioErrors.push(`Could not find any ${scenarioType} test scenarios`); - } - }); - - if (scenarioErrors.length > 0) { - throw new Error([ - `Test Scenarios for rule ${ruleName} is invalid:` - ].concat(scenarioErrors).join("\n")); - } - - const baseConfig = [ - { files: ["**"] }, // Make sure the default config matches for all files - { - plugins: { - - // copy root plugin over - "@": { - - /* - * Parsers are wrapped to detect more errors, so this needs - * to be a new object for each call to run(), otherwise the - * parsers will be wrapped multiple times. - */ - parsers: { - ...defaultConfig[0].plugins["@"].parsers - }, - - /* - * The rules key on the default plugin is a proxy to lazy-load - * just the rules that are needed. So, don't create a new object - * here, just use the default one to keep that performance - * enhancement. - */ - rules: defaultConfig[0].plugins["@"].rules, - languages: defaultConfig[0].plugins["@"].languages - }, - "rule-to-test": { - rules: { - [ruleName]: Object.assign({}, rule, { - - // Create a wrapper rule that freezes the `context` properties. - create(context) { - freezeDeeply(context.options); - freezeDeeply(context.settings); - freezeDeeply(context.parserOptions); - - // freezeDeeply(context.languageOptions); - - return rule.create(context); - } - }) - } - } - }, - language: defaultConfig[0].language - }, - ...defaultConfig.slice(1) - ]; - - /** - * Runs a hook on the given item when it's assigned to the given property - * @param {string|Object} item Item to run the hook on - * @param {string} prop The property having the hook assigned to - * @throws {Error} If the property is not a function or that function throws an error - * @returns {void} - * @private - */ - function runHook(item, prop) { - if (typeof item === "object" && hasOwnProperty(item, prop)) { - assert.strictEqual(typeof item[prop], "function", `Optional test case property '${prop}' must be a function`); - item[prop](); - } - } - - /** - * Run the rule for the given item - * @param {string|Object} item Item to run the rule against - * @throws {Error} If an invalid schema. - * @returns {Object} Eslint run result - * @private - */ - function runRuleForItem(item) { - const flatConfigArrayOptions = { - baseConfig - }; - - if (item.filename) { - flatConfigArrayOptions.basePath = path.parse(item.filename).root || void 0; - } - - const configs = new FlatConfigArray(testerConfig, flatConfigArrayOptions); - - /* - * Modify the returned config so that the parser is wrapped to catch - * access of the start/end properties. This method is called just - * once per code snippet being tested, so each test case gets a clean - * parser. - */ - configs[ConfigArraySymbol.finalizeConfig] = function(...args) { - - // can't do super here :( - const proto = Object.getPrototypeOf(this); - const calculatedConfig = proto[ConfigArraySymbol.finalizeConfig].apply(this, args); - - // wrap the parser to catch start/end property access - if (calculatedConfig.language === jslang) { - calculatedConfig.languageOptions.parser = wrapParser(calculatedConfig.languageOptions.parser); - } - - return calculatedConfig; - }; - - let code, filename, output, beforeAST, afterAST; - - if (typeof item === "string") { - code = item; - } else { - code = item.code; - - /* - * Assumes everything on the item is a config except for the - * parameters used by this tester - */ - const itemConfig = { ...item }; - - for (const parameter of RuleTesterParameters) { - delete itemConfig[parameter]; - } - - /* - * Create the config object from the tester config and this item - * specific configurations. - */ - configs.push(itemConfig); - } - - if (hasOwnProperty(item, "only")) { - assert.ok(typeof item.only === "boolean", "Optional test case property 'only' must be a boolean"); - } - if (hasOwnProperty(item, "filename")) { - assert.ok(typeof item.filename === "string", "Optional test case property 'filename' must be a string"); - filename = item.filename; - } - - let ruleConfig = 1; - - if (hasOwnProperty(item, "options")) { - assert(Array.isArray(item.options), "options must be an array"); - ruleConfig = [1, ...item.options]; - } - - configs.push({ - rules: { - [ruleId]: ruleConfig - } - }); - - let schema; - - try { - schema = getRuleOptionsSchema(rule); - } catch (err) { - err.message += metaSchemaDescription; - throw err; - } - - /* - * Check and throw an error if the schema is an empty object (`schema:{}`), because such schema - * doesn't validate or enforce anything and is therefore considered a possible error. If the intent - * was to skip options validation, `schema:false` should be set instead (explicit opt-out). - * - * For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed - * properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well, - * it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea - * to use inherited properties in schemas because schemas that differ only in inherited properties would end up - * having the same cache entry that would be correct for only one of them. - * - * At this point, `schema` can only be an object or `null`. - */ - if (schema && Object.keys(schema).length === 0) { - throw new Error(`\`schema: {}\` is a no-op${metaSchemaDescription}`); - } - - /* - * Setup AST getters. - * The goal is to check whether or not AST was modified when - * running the rule under test. - */ - configs.push({ - plugins: { - "rule-tester": { - rules: { - "validate-ast": { - create() { - return { - Program(node) { - beforeAST = cloneDeeplyExcludesParent(node); - }, - "Program:exit"(node) { - afterAST = node; - } - }; - } - } - } - } - } - }); - - if (schema) { - ajv.validateSchema(schema); - - if (ajv.errors) { - const errors = ajv.errors.map(error => { - const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath; - - return `\t${field}: ${error.message}`; - }).join("\n"); - - throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]); - } - - /* - * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"), - * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling - * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result, - * the schema is compiled here separately from checking for `validateSchema` errors. - */ - try { - ajv.compile(schema); - } catch (err) { - throw new Error(`Schema for rule ${ruleName} is invalid: ${err.message}`); - } - } - - // check for validation errors - try { - configs.normalizeSync(); - configs.getConfig("test.js"); - } catch (error) { - error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`; - throw error; - } - - // Verify the code. - const { applyLanguageOptions, applyInlineConfig, finalize } = SourceCode.prototype; - let messages; - - try { - forbiddenMethods.forEach(methodName => { - SourceCode.prototype[methodName] = throwForbiddenMethodError(methodName, SourceCode.prototype); - }); - - messages = linter.verify(code, configs, filename); - } finally { - SourceCode.prototype.applyInlineConfig = applyInlineConfig; - SourceCode.prototype.applyLanguageOptions = applyLanguageOptions; - SourceCode.prototype.finalize = finalize; - } - - - const fatalErrorMessage = messages.find(m => m.fatal); - - assert(!fatalErrorMessage, `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`); - - // Verify if autofix makes a syntax error or not. - if (messages.some(m => m.fix)) { - output = SourceCodeFixer.applyFixes(code, messages).output; - const errorMessageInFix = linter.verify(output, configs, filename).find(m => m.fatal); - - assert(!errorMessageInFix, [ - "A fatal parsing error occurred in autofix.", - `Error: ${errorMessageInFix && errorMessageInFix.message}`, - "Autofix output:", - output - ].join("\n")); - } else { - output = code; - } - - return { - messages, - output, - beforeAST, - afterAST: cloneDeeplyExcludesParent(afterAST), - configs, - filename - }; - } - - /** - * Check if the AST was changed - * @param {ASTNode} beforeAST AST node before running - * @param {ASTNode} afterAST AST node after running - * @returns {void} - * @private - */ - function assertASTDidntChange(beforeAST, afterAST) { - if (!equal(beforeAST, afterAST)) { - assert.fail("Rule should not modify AST."); - } - } - - /** - * Check if this test case is a duplicate of one we have seen before. - * @param {string|Object} item test case object - * @param {Set} seenTestCases set of serialized test cases we have seen so far (managed by this function) - * @returns {void} - * @private - */ - function checkDuplicateTestCase(item, seenTestCases) { - if (!isSerializable(item)) { - - /* - * If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check. - * This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions. - */ - return; - } - - const normalizedItem = typeof item === "string" ? { code: item } : item; - const serializedTestCase = stringify(normalizedItem, { - replacer(key, value) { - - // "this" is the currently stringified object --> only ignore top-level properties - return (normalizedItem !== this || !duplicationIgnoredParameters.has(key)) ? value : void 0; - } - }); - - assert( - !seenTestCases.has(serializedTestCase), - "detected duplicate test case" - ); - seenTestCases.add(serializedTestCase); - } - - /** - * Check if the template is valid or not - * all valid cases go through this - * @param {string|Object} item Item to run the rule against - * @returns {void} - * @private - */ - function testValidTemplate(item) { - const code = typeof item === "object" ? item.code : item; - - assert.ok(typeof code === "string", "Test case must specify a string value for 'code'"); - if (item.name) { - assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string"); - } - - checkDuplicateTestCase(item, seenValidTestCases); - - const result = runRuleForItem(item); - const messages = result.messages; - - assert.strictEqual(messages.length, 0, util.format("Should have no errors but had %d: %s", - messages.length, - util.inspect(messages))); - - assertASTDidntChange(result.beforeAST, result.afterAST); - } - - /** - * Asserts that the message matches its expected value. If the expected - * value is a regular expression, it is checked against the actual - * value. - * @param {string} actual Actual value - * @param {string|RegExp} expected Expected value - * @returns {void} - * @private - */ - function assertMessageMatches(actual, expected) { - if (expected instanceof RegExp) { - - // assert.js doesn't have a built-in RegExp match function - assert.ok( - expected.test(actual), - `Expected '${actual}' to match ${expected}` - ); - } else { - assert.strictEqual(actual, expected); - } - } - - /** - * Check if the template is invalid or not - * all invalid cases go through this. - * @param {string|Object} item Item to run the rule against - * @returns {void} - * @private - */ - function testInvalidTemplate(item) { - assert.ok(typeof item.code === "string", "Test case must specify a string value for 'code'"); - if (item.name) { - assert.ok(typeof item.name === "string", "Optional test case property 'name' must be a string"); - } - assert.ok(item.errors || item.errors === 0, - `Did not specify errors for an invalid test of ${ruleName}`); - - if (Array.isArray(item.errors) && item.errors.length === 0) { - assert.fail("Invalid cases must have at least one error"); - } - - checkDuplicateTestCase(item, seenInvalidTestCases); - - const ruleHasMetaMessages = hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"); - const friendlyIDList = ruleHasMetaMessages ? `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]` : null; - - const result = runRuleForItem(item); - const messages = result.messages; - - for (const message of messages) { - if (hasOwnProperty(message, "suggestions")) { - - /** @type {Map} */ - const seenMessageIndices = new Map(); - - for (let i = 0; i < message.suggestions.length; i += 1) { - const suggestionMessage = message.suggestions[i].desc; - const previous = seenMessageIndices.get(suggestionMessage); - - assert.ok(!seenMessageIndices.has(suggestionMessage), `Suggestion message '${suggestionMessage}' reported from suggestion ${i} was previously reported by suggestion ${previous}. Suggestion messages should be unique within an error.`); - seenMessageIndices.set(suggestionMessage, i); - } - } - } - - if (typeof item.errors === "number") { - - if (item.errors === 0) { - assert.fail("Invalid cases must have 'error' value greater than 0"); - } - - assert.strictEqual(messages.length, item.errors, util.format("Should have %d error%s but had %d: %s", - item.errors, - item.errors === 1 ? "" : "s", - messages.length, - util.inspect(messages))); - } else { - assert.strictEqual( - messages.length, item.errors.length, util.format( - "Should have %d error%s but had %d: %s", - item.errors.length, - item.errors.length === 1 ? "" : "s", - messages.length, - util.inspect(messages) - ) - ); - - const hasMessageOfThisRule = messages.some(m => m.ruleId === ruleId); - - for (let i = 0, l = item.errors.length; i < l; i++) { - const error = item.errors[i]; - const message = messages[i]; - - assert(hasMessageOfThisRule, "Error rule name should be the same as the name of the rule being tested"); - - if (typeof error === "string" || error instanceof RegExp) { - - // Just an error message. - assertMessageMatches(message.message, error); - assert.ok(message.suggestions === void 0, `Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`); - } else if (typeof error === "object" && error !== null) { - - /* - * Error object. - * This may have a message, messageId, data, node type, line, and/or - * column. - */ - - Object.keys(error).forEach(propertyName => { - assert.ok( - errorObjectParameters.has(propertyName), - `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.` - ); - }); - - if (hasOwnProperty(error, "message")) { - assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'."); - assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'."); - assertMessageMatches(message.message, error.message); - } else if (hasOwnProperty(error, "messageId")) { - assert.ok( - ruleHasMetaMessages, - "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'." - ); - if (!hasOwnProperty(rule.meta.messages, error.messageId)) { - assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`); - } - assert.strictEqual( - message.messageId, - error.messageId, - `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.` - ); - - const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( - message.message, - rule.meta.messages[message.messageId], - error.data - ); - - assert.ok( - unsubstitutedPlaceholders.length === 0, - `The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.` - ); - - if (hasOwnProperty(error, "data")) { - - /* - * if data was provided, then directly compare the returned message to a synthetic - * interpolated message using the same message ID and data provided in the test. - * See https://github.com/eslint/eslint/issues/9890 for context. - */ - const unformattedOriginalMessage = rule.meta.messages[error.messageId]; - const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data); - - assert.strictEqual( - message.message, - rehydratedMessage, - `Hydrated message "${rehydratedMessage}" does not match "${message.message}"` - ); - } - } else { - assert.fail("Test error must specify either a 'messageId' or 'message'."); - } - - if (error.type) { - assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`); - } - - if (hasOwnProperty(error, "line")) { - assert.strictEqual(message.line, error.line, `Error line should be ${error.line}`); - } - - if (hasOwnProperty(error, "column")) { - assert.strictEqual(message.column, error.column, `Error column should be ${error.column}`); - } - - if (hasOwnProperty(error, "endLine")) { - assert.strictEqual(message.endLine, error.endLine, `Error endLine should be ${error.endLine}`); - } - - if (hasOwnProperty(error, "endColumn")) { - assert.strictEqual(message.endColumn, error.endColumn, `Error endColumn should be ${error.endColumn}`); - } - - assert.ok(!message.suggestions || hasOwnProperty(error, "suggestions"), `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`); - if (hasOwnProperty(error, "suggestions")) { - - // Support asserting there are no suggestions - const expectsSuggestions = Array.isArray(error.suggestions) ? error.suggestions.length > 0 : Boolean(error.suggestions); - const hasSuggestions = message.suggestions !== void 0; - - if (!hasSuggestions && expectsSuggestions) { - assert.ok(!error.suggestions, `Error should have suggestions on error with message: "${message.message}"`); - } else if (hasSuggestions) { - assert.ok(expectsSuggestions, `Error should have no suggestions on error with message: "${message.message}"`); - if (typeof error.suggestions === "number") { - assert.strictEqual(message.suggestions.length, error.suggestions, `Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`); - } else if (Array.isArray(error.suggestions)) { - assert.strictEqual(message.suggestions.length, error.suggestions.length, `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`); - - error.suggestions.forEach((expectedSuggestion, index) => { - assert.ok( - typeof expectedSuggestion === "object" && expectedSuggestion !== null, - "Test suggestion in 'suggestions' array must be an object." - ); - Object.keys(expectedSuggestion).forEach(propertyName => { - assert.ok( - suggestionObjectParameters.has(propertyName), - `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.` - ); - }); - - const actualSuggestion = message.suggestions[index]; - const suggestionPrefix = `Error Suggestion at index ${index}:`; - - if (hasOwnProperty(expectedSuggestion, "desc")) { - assert.ok( - !hasOwnProperty(expectedSuggestion, "data"), - `${suggestionPrefix} Test should not specify both 'desc' and 'data'.` - ); - assert.ok( - !hasOwnProperty(expectedSuggestion, "messageId"), - `${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.` - ); - assert.strictEqual( - actualSuggestion.desc, - expectedSuggestion.desc, - `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.` - ); - } else if (hasOwnProperty(expectedSuggestion, "messageId")) { - assert.ok( - ruleHasMetaMessages, - `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.` - ); - assert.ok( - hasOwnProperty(rule.meta.messages, expectedSuggestion.messageId), - `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.` - ); - assert.strictEqual( - actualSuggestion.messageId, - expectedSuggestion.messageId, - `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.` - ); - - const unsubstitutedPlaceholders = getUnsubstitutedMessagePlaceholders( - actualSuggestion.desc, - rule.meta.messages[expectedSuggestion.messageId], - expectedSuggestion.data - ); - - assert.ok( - unsubstitutedPlaceholders.length === 0, - `The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.` - ); - - if (hasOwnProperty(expectedSuggestion, "data")) { - const unformattedMetaMessage = rule.meta.messages[expectedSuggestion.messageId]; - const rehydratedDesc = interpolate(unformattedMetaMessage, expectedSuggestion.data); - - assert.strictEqual( - actualSuggestion.desc, - rehydratedDesc, - `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".` - ); - } - } else if (hasOwnProperty(expectedSuggestion, "data")) { - assert.fail( - `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.` - ); - } else { - assert.fail( - `${suggestionPrefix} Test must specify either 'messageId' or 'desc'.` - ); - } - - assert.ok(hasOwnProperty(expectedSuggestion, "output"), `${suggestionPrefix} The "output" property is required.`); - const codeWithAppliedSuggestion = SourceCodeFixer.applyFixes(item.code, [actualSuggestion]).output; - - // Verify if suggestion fix makes a syntax error or not. - const errorMessageInSuggestion = - linter.verify(codeWithAppliedSuggestion, result.configs, result.filename).find(m => m.fatal); - - assert(!errorMessageInSuggestion, [ - "A fatal parsing error occurred in suggestion fix.", - `Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`, - "Suggestion output:", - codeWithAppliedSuggestion - ].join("\n")); - - assert.strictEqual(codeWithAppliedSuggestion, expectedSuggestion.output, `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`); - assert.notStrictEqual(expectedSuggestion.output, item.code, `The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`); - }); - } else { - assert.fail("Test error object property 'suggestions' should be an array or a number"); - } - } - } - } else { - - // Message was an unexpected type - assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`); - } - } - } - - if (hasOwnProperty(item, "output")) { - if (item.output === null) { - assert.strictEqual( - result.output, - item.code, - "Expected no autofixes to be suggested" - ); - } else { - assert.strictEqual(result.output, item.output, "Output is incorrect."); - assert.notStrictEqual(item.code, item.output, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null."); - } - } else { - assert.strictEqual( - result.output, - item.code, - "The rule fixed the code. Please add 'output' property." - ); - } - - assertASTDidntChange(result.beforeAST, result.afterAST); - } - - /* - * This creates a mocha test suite and pipes all supplied info through - * one of the templates above. - * The test suites for valid/invalid are created conditionally as - * test runners (eg. vitest) fail for empty test suites. - */ - this.constructor.describe(ruleName, () => { - if (test.valid.length > 0) { - this.constructor.describe("valid", () => { - test.valid.forEach(valid => { - this.constructor[valid.only ? "itOnly" : "it"]( - sanitize(typeof valid === "object" ? valid.name || valid.code : valid), - () => { - try { - runHook(valid, "before"); - testValidTemplate(valid); - } finally { - runHook(valid, "after"); - } - } - ); - }); - }); - } - - if (test.invalid.length > 0) { - this.constructor.describe("invalid", () => { - test.invalid.forEach(invalid => { - this.constructor[invalid.only ? "itOnly" : "it"]( - sanitize(invalid.name || invalid.code), - () => { - try { - runHook(invalid, "before"); - testInvalidTemplate(invalid); - } finally { - runHook(invalid, "after"); - } - } - ); - }); - }); - } - }); - } + /** + * Creates a new instance of RuleTester. + * @param {Object} [testerConfig] Optional, extra configuration for the tester + */ + constructor(testerConfig = {}) { + /** + * The configuration to use for this tester. Combination of the tester + * configuration and the default configuration. + * @type {Object} + */ + this.testerConfig = [ + sharedDefaultConfig, + testerConfig, + { rules: { "rule-tester/validate-ast": "error" } }, + ]; + + this.linter = new Linter({ configType: "flat" }); + } + + /** + * Set the configuration to use for all future tests + * @param {Object} config the configuration to use. + * @throws {TypeError} If non-object config. + * @returns {void} + */ + static setDefaultConfig(config) { + if (typeof config !== "object" || config === null) { + throw new TypeError( + "RuleTester.setDefaultConfig: config must be an object", + ); + } + sharedDefaultConfig = config; + + // Make sure the rules object exists since it is assumed to exist later + sharedDefaultConfig.rules = sharedDefaultConfig.rules || {}; + } + + /** + * Get the current configuration used for all tests + * @returns {Object} the current configuration + */ + static getDefaultConfig() { + return sharedDefaultConfig; + } + + /** + * Reset the configuration to the initial configuration of the tester removing + * any changes made until now. + * @returns {void} + */ + static resetDefaultConfig() { + sharedDefaultConfig = { + rules: { + ...testerDefaultConfig.rules, + }, + }; + } + + /* + * If people use `mocha test.js --watch` command, `describe` and `it` function + * instances are different for each execution. So `describe` and `it` should get fresh instance + * always. + */ + static get describe() { + return ( + this[DESCRIBE] || + (typeof describe === "function" ? describe : describeDefaultHandler) + ); + } + + static set describe(value) { + this[DESCRIBE] = value; + } + + static get it() { + return this[IT] || (typeof it === "function" ? it : itDefaultHandler); + } + + static set it(value) { + this[IT] = value; + } + + /** + * Adds the `only` property to a test to run it in isolation. + * @param {string | ValidTestCase | InvalidTestCase} item A single test to run by itself. + * @returns {ValidTestCase | InvalidTestCase} The test with `only` set. + */ + static only(item) { + if (typeof item === "string") { + return { code: item, only: true }; + } + + return { ...item, only: true }; + } + + static get itOnly() { + if (typeof this[IT_ONLY] === "function") { + return this[IT_ONLY]; + } + if ( + typeof this[IT] === "function" && + typeof this[IT].only === "function" + ) { + return Function.bind.call(this[IT].only, this[IT]); + } + if (typeof it === "function" && typeof it.only === "function") { + return Function.bind.call(it.only, it); + } + + if ( + typeof this[DESCRIBE] === "function" || + typeof this[IT] === "function" + ) { + throw new Error( + "Set `RuleTester.itOnly` to use `only` with a custom test framework.\n" + + "See https://eslint.org/docs/latest/integrate/nodejs-api#customizing-ruletester for more.", + ); + } + if (typeof it === "function") { + throw new Error( + "The current test framework does not support exclusive tests with `only`.", + ); + } + throw new Error( + "To use `only`, use RuleTester with a test framework that provides `it.only()` like Mocha.", + ); + } + + static set itOnly(value) { + this[IT_ONLY] = value; + } + + /** + * Adds a new rule test to execute. + * @param {string} ruleName The name of the rule to run. + * @param {Rule} rule The rule to test. + * @param {{ + * valid: (ValidTestCase | string)[], + * invalid: InvalidTestCase[] + * }} test The collection of tests to run. + * @throws {TypeError|Error} If `rule` is not an object with a `create` method, + * or if non-object `test`, or if a required scenario of the given type is missing. + * @returns {void} + */ + run(ruleName, rule, test) { + const testerConfig = this.testerConfig, + requiredScenarios = ["valid", "invalid"], + scenarioErrors = [], + linter = this.linter, + ruleId = `rule-to-test/${ruleName}`; + + const seenValidTestCases = new Set(); + const seenInvalidTestCases = new Set(); + + if ( + !rule || + typeof rule !== "object" || + typeof rule.create !== "function" + ) { + throw new TypeError( + "Rule must be an object with a `create` method", + ); + } + + if (!test || typeof test !== "object") { + throw new TypeError( + `Test Scenarios for rule ${ruleName} : Could not find test scenario object`, + ); + } + + requiredScenarios.forEach(scenarioType => { + if (!test[scenarioType]) { + scenarioErrors.push( + `Could not find any ${scenarioType} test scenarios`, + ); + } + }); + + if (scenarioErrors.length > 0) { + throw new Error( + [`Test Scenarios for rule ${ruleName} is invalid:`] + .concat(scenarioErrors) + .join("\n"), + ); + } + + const baseConfig = [ + { + plugins: { + // copy root plugin over + "@": { + /* + * Parsers are wrapped to detect more errors, so this needs + * to be a new object for each call to run(), otherwise the + * parsers will be wrapped multiple times. + */ + parsers: { + ...defaultConfig[0].plugins["@"].parsers, + }, + + /* + * The rules key on the default plugin is a proxy to lazy-load + * just the rules that are needed. So, don't create a new object + * here, just use the default one to keep that performance + * enhancement. + */ + rules: defaultConfig[0].plugins["@"].rules, + languages: defaultConfig[0].plugins["@"].languages, + }, + "rule-to-test": { + rules: { + [ruleName]: Object.assign({}, rule, { + // Create a wrapper rule that freezes the `context` properties. + create(context) { + freezeDeeply(context.options); + freezeDeeply(context.settings); + freezeDeeply(context.parserOptions); + + // freezeDeeply(context.languageOptions); + + return rule.create(context); + }, + }), + }, + }, + }, + language: defaultConfig[0].language, + }, + ...defaultRuleTesterConfig, + ]; + + /** + * Runs a hook on the given item when it's assigned to the given property + * @param {string|Object} item Item to run the hook on + * @param {string} prop The property having the hook assigned to + * @throws {Error} If the property is not a function or that function throws an error + * @returns {void} + * @private + */ + function runHook(item, prop) { + if (typeof item === "object" && hasOwnProperty(item, prop)) { + assert.strictEqual( + typeof item[prop], + "function", + `Optional test case property '${prop}' must be a function`, + ); + item[prop](); + } + } + + /** + * Run the rule for the given item + * @param {string|Object} item Item to run the rule against + * @throws {Error} If an invalid schema. + * @returns {Object} Eslint run result + * @private + */ + function runRuleForItem(item) { + const flatConfigArrayOptions = { + baseConfig, + }; + + if (item.filename) { + flatConfigArrayOptions.basePath = + path.parse(item.filename).root || void 0; + } + + const configs = new FlatConfigArray( + testerConfig, + flatConfigArrayOptions, + ); + + /* + * Modify the returned config so that the parser is wrapped to catch + * access of the start/end properties. This method is called just + * once per code snippet being tested, so each test case gets a clean + * parser. + */ + configs[ConfigArraySymbol.finalizeConfig] = function (...args) { + // can't do super here :( + const proto = Object.getPrototypeOf(this); + const calculatedConfig = proto[ + ConfigArraySymbol.finalizeConfig + ].apply(this, args); + + // wrap the parser to catch start/end property access + if (calculatedConfig.language === jslang) { + calculatedConfig.languageOptions.parser = wrapParser( + calculatedConfig.languageOptions.parser, + ); + } + + return calculatedConfig; + }; + + let code, filename, output, beforeAST, afterAST; + + if (typeof item === "string") { + code = item; + } else { + code = item.code; + + /* + * Assumes everything on the item is a config except for the + * parameters used by this tester + */ + const itemConfig = { ...item }; + + for (const parameter of RuleTesterParameters) { + delete itemConfig[parameter]; + } + + /* + * Create the config object from the tester config and this item + * specific configurations. + */ + configs.push(itemConfig); + } + + if (hasOwnProperty(item, "only")) { + assert.ok( + typeof item.only === "boolean", + "Optional test case property 'only' must be a boolean", + ); + } + if (hasOwnProperty(item, "filename")) { + assert.ok( + typeof item.filename === "string", + "Optional test case property 'filename' must be a string", + ); + filename = item.filename; + } + + let ruleConfig = 1; + + if (hasOwnProperty(item, "options")) { + assert(Array.isArray(item.options), "options must be an array"); + ruleConfig = [1, ...item.options]; + } + + configs.push({ + rules: { + [ruleId]: ruleConfig, + }, + }); + + let schema; + + try { + schema = getRuleOptionsSchema(rule); + } catch (err) { + err.message += metaSchemaDescription; + throw err; + } + + /* + * Check and throw an error if the schema is an empty object (`schema:{}`), because such schema + * doesn't validate or enforce anything and is therefore considered a possible error. If the intent + * was to skip options validation, `schema:false` should be set instead (explicit opt-out). + * + * For this purpose, a schema object is considered empty if it doesn't have any own enumerable string-keyed + * properties. While `ajv.compile()` does use enumerable properties from the prototype chain as well, + * it caches compiled schemas by serializing only own enumerable properties, so it's generally not a good idea + * to use inherited properties in schemas because schemas that differ only in inherited properties would end up + * having the same cache entry that would be correct for only one of them. + * + * At this point, `schema` can only be an object or `null`. + */ + if (schema && Object.keys(schema).length === 0) { + throw new Error( + `\`schema: {}\` is a no-op${metaSchemaDescription}`, + ); + } + + /* + * Setup AST getters. + * The goal is to check whether or not AST was modified when + * running the rule under test. + */ + configs.push({ + plugins: { + "rule-tester": { + rules: { + "validate-ast": { + create() { + return { + Program(node) { + beforeAST = + cloneDeeplyExcludesParent(node); + }, + "Program:exit"(node) { + afterAST = node; + }, + }; + }, + }, + }, + }, + }, + }); + + if (schema) { + ajv.validateSchema(schema); + + if (ajv.errors) { + const errors = ajv.errors + .map(error => { + const field = + error.dataPath[0] === "." + ? error.dataPath.slice(1) + : error.dataPath; + + return `\t${field}: ${error.message}`; + }) + .join("\n"); + + throw new Error([ + `Schema for rule ${ruleName} is invalid:`, + errors, + ]); + } + + /* + * `ajv.validateSchema` checks for errors in the structure of the schema (by comparing the schema against a "meta-schema"), + * and it reports those errors individually. However, there are other types of schema errors that only occur when compiling + * the schema (e.g. using invalid defaults in a schema), and only one of these errors can be reported at a time. As a result, + * the schema is compiled here separately from checking for `validateSchema` errors. + */ + try { + ajv.compile(schema); + } catch (err) { + throw new Error( + `Schema for rule ${ruleName} is invalid: ${err.message}`, + ); + } + } + + // check for validation errors + try { + configs.normalizeSync(); + configs.getConfig("test.js"); + } catch (error) { + error.message = `ESLint configuration in rule-tester is invalid: ${error.message}`; + throw error; + } + + // Verify the code. + const { applyLanguageOptions, applyInlineConfig, finalize } = + SourceCode.prototype; + let messages; + + try { + forbiddenMethods.forEach(methodName => { + SourceCode.prototype[methodName] = + throwForbiddenMethodError( + methodName, + SourceCode.prototype, + ); + }); + + messages = linter.verify(code, configs, filename); + } finally { + SourceCode.prototype.applyInlineConfig = applyInlineConfig; + SourceCode.prototype.applyLanguageOptions = + applyLanguageOptions; + SourceCode.prototype.finalize = finalize; + } + + const fatalErrorMessage = messages.find(m => m.fatal); + + assert( + !fatalErrorMessage, + `A fatal parsing error occurred: ${fatalErrorMessage && fatalErrorMessage.message}`, + ); + + // Verify if autofix makes a syntax error or not. + if (messages.some(m => m.fix)) { + output = SourceCodeFixer.applyFixes(code, messages).output; + const errorMessageInFix = linter + .verify(output, configs, filename) + .find(m => m.fatal); + + assert( + !errorMessageInFix, + [ + "A fatal parsing error occurred in autofix.", + `Error: ${errorMessageInFix && errorMessageInFix.message}`, + "Autofix output:", + output, + ].join("\n"), + ); + } else { + output = code; + } + + return { + messages, + output, + beforeAST, + afterAST: cloneDeeplyExcludesParent(afterAST), + configs, + filename, + }; + } + + /** + * Check if the AST was changed + * @param {ASTNode} beforeAST AST node before running + * @param {ASTNode} afterAST AST node after running + * @returns {void} + * @private + */ + function assertASTDidntChange(beforeAST, afterAST) { + if (!equal(beforeAST, afterAST)) { + assert.fail("Rule should not modify AST."); + } + } + + /** + * Check if this test case is a duplicate of one we have seen before. + * @param {string|Object} item test case object + * @param {Set} seenTestCases set of serialized test cases we have seen so far (managed by this function) + * @returns {void} + * @private + */ + function checkDuplicateTestCase(item, seenTestCases) { + if (!isSerializable(item)) { + /* + * If we can't serialize a test case (because it contains a function, RegExp, etc), skip the check. + * This might happen with properties like: options, plugins, settings, languageOptions.parser, languageOptions.parserOptions. + */ + return; + } + + const normalizedItem = + typeof item === "string" ? { code: item } : item; + const serializedTestCase = stringify(normalizedItem, { + replacer(key, value) { + // "this" is the currently stringified object --> only ignore top-level properties + return normalizedItem !== this || + !duplicationIgnoredParameters.has(key) + ? value + : void 0; + }, + }); + + assert( + !seenTestCases.has(serializedTestCase), + "detected duplicate test case", + ); + seenTestCases.add(serializedTestCase); + } + + /** + * Check if the template is valid or not + * all valid cases go through this + * @param {string|Object} item Item to run the rule against + * @returns {void} + * @private + */ + function testValidTemplate(item) { + const code = typeof item === "object" ? item.code : item; + + assert.ok( + typeof code === "string", + "Test case must specify a string value for 'code'", + ); + if (item.name) { + assert.ok( + typeof item.name === "string", + "Optional test case property 'name' must be a string", + ); + } + + checkDuplicateTestCase(item, seenValidTestCases); + + const result = runRuleForItem(item); + const messages = result.messages; + + assert.strictEqual( + messages.length, + 0, + util.format( + "Should have no errors but had %d: %s", + messages.length, + util.inspect(messages), + ), + ); + + assertASTDidntChange(result.beforeAST, result.afterAST); + } + + /** + * Asserts that the message matches its expected value. If the expected + * value is a regular expression, it is checked against the actual + * value. + * @param {string} actual Actual value + * @param {string|RegExp} expected Expected value + * @returns {void} + * @private + */ + function assertMessageMatches(actual, expected) { + if (expected instanceof RegExp) { + // assert.js doesn't have a built-in RegExp match function + assert.ok( + expected.test(actual), + `Expected '${actual}' to match ${expected}`, + ); + } else { + assert.strictEqual(actual, expected); + } + } + + /** + * Check if the template is invalid or not + * all invalid cases go through this. + * @param {string|Object} item Item to run the rule against + * @returns {void} + * @private + */ + function testInvalidTemplate(item) { + assert.ok( + typeof item.code === "string", + "Test case must specify a string value for 'code'", + ); + if (item.name) { + assert.ok( + typeof item.name === "string", + "Optional test case property 'name' must be a string", + ); + } + assert.ok( + item.errors || item.errors === 0, + `Did not specify errors for an invalid test of ${ruleName}`, + ); + + if (Array.isArray(item.errors) && item.errors.length === 0) { + assert.fail("Invalid cases must have at least one error"); + } + + checkDuplicateTestCase(item, seenInvalidTestCases); + + const ruleHasMetaMessages = + hasOwnProperty(rule, "meta") && + hasOwnProperty(rule.meta, "messages"); + const friendlyIDList = ruleHasMetaMessages + ? `[${Object.keys(rule.meta.messages) + .map(key => `'${key}'`) + .join(", ")}]` + : null; + + const result = runRuleForItem(item); + const messages = result.messages; + + for (const message of messages) { + if (hasOwnProperty(message, "suggestions")) { + /** @type {Map} */ + const seenMessageIndices = new Map(); + + for (let i = 0; i < message.suggestions.length; i += 1) { + const suggestionMessage = message.suggestions[i].desc; + const previous = + seenMessageIndices.get(suggestionMessage); + + assert.ok( + !seenMessageIndices.has(suggestionMessage), + `Suggestion message '${suggestionMessage}' reported from suggestion ${i} was previously reported by suggestion ${previous}. Suggestion messages should be unique within an error.`, + ); + seenMessageIndices.set(suggestionMessage, i); + } + } + } + + if (typeof item.errors === "number") { + if (item.errors === 0) { + assert.fail( + "Invalid cases must have 'error' value greater than 0", + ); + } + + assert.strictEqual( + messages.length, + item.errors, + util.format( + "Should have %d error%s but had %d: %s", + item.errors, + item.errors === 1 ? "" : "s", + messages.length, + util.inspect(messages), + ), + ); + } else { + assert.strictEqual( + messages.length, + item.errors.length, + util.format( + "Should have %d error%s but had %d: %s", + item.errors.length, + item.errors.length === 1 ? "" : "s", + messages.length, + util.inspect(messages), + ), + ); + + const hasMessageOfThisRule = messages.some( + m => m.ruleId === ruleId, + ); + + for (let i = 0, l = item.errors.length; i < l; i++) { + const error = item.errors[i]; + const message = messages[i]; + + assert( + hasMessageOfThisRule, + "Error rule name should be the same as the name of the rule being tested", + ); + + if (typeof error === "string" || error instanceof RegExp) { + // Just an error message. + assertMessageMatches(message.message, error); + assert.ok( + message.suggestions === void 0, + `Error at index ${i} has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions.`, + ); + } else if (typeof error === "object" && error !== null) { + /* + * Error object. + * This may have a message, messageId, data, node type, line, and/or + * column. + */ + + Object.keys(error).forEach(propertyName => { + assert.ok( + errorObjectParameters.has(propertyName), + `Invalid error property name '${propertyName}'. Expected one of ${friendlyErrorObjectParameterList}.`, + ); + }); + + if (hasOwnProperty(error, "message")) { + assert.ok( + !hasOwnProperty(error, "messageId"), + "Error should not specify both 'message' and a 'messageId'.", + ); + assert.ok( + !hasOwnProperty(error, "data"), + "Error should not specify both 'data' and 'message'.", + ); + assertMessageMatches( + message.message, + error.message, + ); + } else if (hasOwnProperty(error, "messageId")) { + assert.ok( + ruleHasMetaMessages, + "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'.", + ); + if ( + !hasOwnProperty( + rule.meta.messages, + error.messageId, + ) + ) { + assert( + false, + `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`, + ); + } + assert.strictEqual( + message.messageId, + error.messageId, + `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`, + ); + + const unsubstitutedPlaceholders = + getUnsubstitutedMessagePlaceholders( + message.message, + rule.meta.messages[message.messageId], + error.data, + ); + + assert.ok( + unsubstitutedPlaceholders.length === 0, + `The reported message has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property in the context.report() call.`, + ); + + if (hasOwnProperty(error, "data")) { + /* + * if data was provided, then directly compare the returned message to a synthetic + * interpolated message using the same message ID and data provided in the test. + * See https://github.com/eslint/eslint/issues/9890 for context. + */ + const unformattedOriginalMessage = + rule.meta.messages[error.messageId]; + const rehydratedMessage = interpolate( + unformattedOriginalMessage, + error.data, + ); + + assert.strictEqual( + message.message, + rehydratedMessage, + `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`, + ); + } + } else { + assert.fail( + "Test error must specify either a 'messageId' or 'message'.", + ); + } + + if (error.type) { + assert.strictEqual( + message.nodeType, + error.type, + `Error type should be ${error.type}, found ${message.nodeType}`, + ); + } + + if (hasOwnProperty(error, "line")) { + assert.strictEqual( + message.line, + error.line, + `Error line should be ${error.line}`, + ); + } + + if (hasOwnProperty(error, "column")) { + assert.strictEqual( + message.column, + error.column, + `Error column should be ${error.column}`, + ); + } + + if (hasOwnProperty(error, "endLine")) { + assert.strictEqual( + message.endLine, + error.endLine, + `Error endLine should be ${error.endLine}`, + ); + } + + if (hasOwnProperty(error, "endColumn")) { + assert.strictEqual( + message.endColumn, + error.endColumn, + `Error endColumn should be ${error.endColumn}`, + ); + } + + assert.ok( + !message.suggestions || + hasOwnProperty(error, "suggestions"), + `Error at index ${i} has suggestions. Please specify 'suggestions' property on the test error object.`, + ); + if (hasOwnProperty(error, "suggestions")) { + // Support asserting there are no suggestions + const expectsSuggestions = Array.isArray( + error.suggestions, + ) + ? error.suggestions.length > 0 + : Boolean(error.suggestions); + const hasSuggestions = + message.suggestions !== void 0; + + if (!hasSuggestions && expectsSuggestions) { + assert.ok( + !error.suggestions, + `Error should have suggestions on error with message: "${message.message}"`, + ); + } else if (hasSuggestions) { + assert.ok( + expectsSuggestions, + `Error should have no suggestions on error with message: "${message.message}"`, + ); + if (typeof error.suggestions === "number") { + assert.strictEqual( + message.suggestions.length, + error.suggestions, + `Error should have ${error.suggestions} suggestions. Instead found ${message.suggestions.length} suggestions`, + ); + } else if (Array.isArray(error.suggestions)) { + assert.strictEqual( + message.suggestions.length, + error.suggestions.length, + `Error should have ${error.suggestions.length} suggestions. Instead found ${message.suggestions.length} suggestions`, + ); + + error.suggestions.forEach( + (expectedSuggestion, index) => { + assert.ok( + typeof expectedSuggestion === + "object" && + expectedSuggestion !== null, + "Test suggestion in 'suggestions' array must be an object.", + ); + Object.keys( + expectedSuggestion, + ).forEach(propertyName => { + assert.ok( + suggestionObjectParameters.has( + propertyName, + ), + `Invalid suggestion property name '${propertyName}'. Expected one of ${friendlySuggestionObjectParameterList}.`, + ); + }); + + const actualSuggestion = + message.suggestions[index]; + const suggestionPrefix = `Error Suggestion at index ${index}:`; + + if ( + hasOwnProperty( + expectedSuggestion, + "desc", + ) + ) { + assert.ok( + !hasOwnProperty( + expectedSuggestion, + "data", + ), + `${suggestionPrefix} Test should not specify both 'desc' and 'data'.`, + ); + assert.ok( + !hasOwnProperty( + expectedSuggestion, + "messageId", + ), + `${suggestionPrefix} Test should not specify both 'desc' and 'messageId'.`, + ); + assert.strictEqual( + actualSuggestion.desc, + expectedSuggestion.desc, + `${suggestionPrefix} desc should be "${expectedSuggestion.desc}" but got "${actualSuggestion.desc}" instead.`, + ); + } else if ( + hasOwnProperty( + expectedSuggestion, + "messageId", + ) + ) { + assert.ok( + ruleHasMetaMessages, + `${suggestionPrefix} Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.`, + ); + assert.ok( + hasOwnProperty( + rule.meta.messages, + expectedSuggestion.messageId, + ), + `${suggestionPrefix} Test has invalid messageId '${expectedSuggestion.messageId}', the rule under test allows only one of ${friendlyIDList}.`, + ); + assert.strictEqual( + actualSuggestion.messageId, + expectedSuggestion.messageId, + `${suggestionPrefix} messageId should be '${expectedSuggestion.messageId}' but got '${actualSuggestion.messageId}' instead.`, + ); + + const unsubstitutedPlaceholders = + getUnsubstitutedMessagePlaceholders( + actualSuggestion.desc, + rule.meta.messages[ + expectedSuggestion + .messageId + ], + expectedSuggestion.data, + ); + + assert.ok( + unsubstitutedPlaceholders.length === + 0, + `The message of the suggestion has ${unsubstitutedPlaceholders.length > 1 ? `unsubstituted placeholders: ${unsubstitutedPlaceholders.map(name => `'${name}'`).join(", ")}` : `an unsubstituted placeholder '${unsubstitutedPlaceholders[0]}'`}. Please provide the missing ${unsubstitutedPlaceholders.length > 1 ? "values" : "value"} via the 'data' property for the suggestion in the context.report() call.`, + ); + + if ( + hasOwnProperty( + expectedSuggestion, + "data", + ) + ) { + const unformattedMetaMessage = + rule.meta.messages[ + expectedSuggestion + .messageId + ]; + const rehydratedDesc = + interpolate( + unformattedMetaMessage, + expectedSuggestion.data, + ); + + assert.strictEqual( + actualSuggestion.desc, + rehydratedDesc, + `${suggestionPrefix} Hydrated test desc "${rehydratedDesc}" does not match received desc "${actualSuggestion.desc}".`, + ); + } + } else if ( + hasOwnProperty( + expectedSuggestion, + "data", + ) + ) { + assert.fail( + `${suggestionPrefix} Test must specify 'messageId' if 'data' is used.`, + ); + } else { + assert.fail( + `${suggestionPrefix} Test must specify either 'messageId' or 'desc'.`, + ); + } + + assert.ok( + hasOwnProperty( + expectedSuggestion, + "output", + ), + `${suggestionPrefix} The "output" property is required.`, + ); + const codeWithAppliedSuggestion = + SourceCodeFixer.applyFixes( + item.code, + [actualSuggestion], + ).output; + + // Verify if suggestion fix makes a syntax error or not. + const errorMessageInSuggestion = + linter + .verify( + codeWithAppliedSuggestion, + result.configs, + result.filename, + ) + .find(m => m.fatal); + + assert( + !errorMessageInSuggestion, + [ + "A fatal parsing error occurred in suggestion fix.", + `Error: ${errorMessageInSuggestion && errorMessageInSuggestion.message}`, + "Suggestion output:", + codeWithAppliedSuggestion, + ].join("\n"), + ); + + assert.strictEqual( + codeWithAppliedSuggestion, + expectedSuggestion.output, + `Expected the applied suggestion fix to match the test suggestion output for suggestion at index: ${index} on error with message: "${message.message}"`, + ); + assert.notStrictEqual( + expectedSuggestion.output, + item.code, + `The output of a suggestion should differ from the original source code for suggestion at index: ${index} on error with message: "${message.message}"`, + ); + }, + ); + } else { + assert.fail( + "Test error object property 'suggestions' should be an array or a number", + ); + } + } + } + } else { + // Message was an unexpected type + assert.fail( + `Error should be a string, object, or RegExp, but found (${util.inspect(message)})`, + ); + } + } + } + + if (hasOwnProperty(item, "output")) { + if (item.output === null) { + assert.strictEqual( + result.output, + item.code, + "Expected no autofixes to be suggested", + ); + } else { + assert.strictEqual( + result.output, + item.output, + "Output is incorrect.", + ); + assert.notStrictEqual( + item.code, + item.output, + "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null.", + ); + } + } else { + assert.strictEqual( + result.output, + item.code, + "The rule fixed the code. Please add 'output' property.", + ); + } + + assertASTDidntChange(result.beforeAST, result.afterAST); + } + + /* + * This creates a mocha test suite and pipes all supplied info through + * one of the templates above. + * The test suites for valid/invalid are created conditionally as + * test runners (eg. vitest) fail for empty test suites. + */ + this.constructor.describe(ruleName, () => { + if (test.valid.length > 0) { + this.constructor.describe("valid", () => { + test.valid.forEach(valid => { + this.constructor[valid.only ? "itOnly" : "it"]( + sanitize( + typeof valid === "object" + ? valid.name || valid.code + : valid, + ), + () => { + try { + runHook(valid, "before"); + testValidTemplate(valid); + } finally { + runHook(valid, "after"); + } + }, + ); + }); + }); + } + + if (test.invalid.length > 0) { + this.constructor.describe("invalid", () => { + test.invalid.forEach(invalid => { + this.constructor[invalid.only ? "itOnly" : "it"]( + sanitize(invalid.name || invalid.code), + () => { + try { + runHook(invalid, "before"); + testInvalidTemplate(invalid); + } finally { + runHook(invalid, "after"); + } + }, + ); + }); + }); + } + }); + } } RuleTester[DESCRIBE] = RuleTester[IT] = RuleTester[IT_ONLY] = null; diff --git a/lib/rules/accessor-pairs.js b/lib/rules/accessor-pairs.js index e95c7d06210e..2090e5a757fe 100644 --- a/lib/rules/accessor-pairs.js +++ b/lib/rules/accessor-pairs.js @@ -40,20 +40,23 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the lists have same tokens. */ function areEqualTokenLists(left, right) { - if (left.length !== right.length) { - return false; - } - - for (let i = 0; i < left.length; i++) { - const leftToken = left[i], - rightToken = right[i]; - - if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { - return false; - } - } - - return true; + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + const leftToken = left[i], + rightToken = right[i]; + + if ( + leftToken.type !== rightToken.type || + leftToken.value !== rightToken.value + ) { + return false; + } + } + + return true; } /** @@ -63,18 +66,16 @@ function areEqualTokenLists(left, right) { * @returns {boolean} `true` if the keys are equal. */ function areEqualKeys(left, right) { - if (typeof left === "string" && typeof right === "string") { - - // Statically computed names. - return left === right; - } - if (Array.isArray(left) && Array.isArray(right)) { - - // Token lists. - return areEqualTokenLists(left, right); - } - - return false; + if (typeof left === "string" && typeof right === "string") { + // Statically computed names. + return left === right; + } + if (Array.isArray(left) && Array.isArray(right)) { + // Token lists. + return areEqualTokenLists(left, right); + } + + return false; } /** @@ -83,7 +84,7 @@ function areEqualKeys(left, right) { * @returns {boolean} `true` if the node is of an accessor kind. */ function isAccessorKind(node) { - return node.kind === "get" || node.kind === "set"; + return node.kind === "get" || node.kind === "set"; } /** @@ -95,13 +96,13 @@ function isAccessorKind(node) { * @returns {boolean} `true` if the node is an argument of the specified method call. */ function isArgumentOfMethodCall(node, index, object, property) { - const parent = node.parent; + const parent = node.parent; - return ( - parent.type === "CallExpression" && - astUtils.isSpecificMemberAccess(parent.callee, object, property) && - parent.arguments[index] === node - ); + return ( + parent.type === "CallExpression" && + astUtils.isSpecificMemberAccess(parent.callee, object, property) && + parent.arguments[index] === node + ); } /** @@ -110,24 +111,30 @@ function isArgumentOfMethodCall(node, index, object, property) { * @returns {boolean} `true` if the node is a property descriptor. */ function isPropertyDescriptor(node) { - - // Object.defineProperty(obj, "foo", {set: ...}) - if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") || - isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty") - ) { - return true; - } - - /* - * Object.defineProperties(obj, {foo: {set: ...}}) - * Object.create(proto, {foo: {set: ...}}) - */ - const grandparent = node.parent.parent; - - return grandparent.type === "ObjectExpression" && ( - isArgumentOfMethodCall(grandparent, 1, "Object", "create") || - isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties") - ); + // Object.defineProperty(obj, "foo", {set: ...}) + if ( + isArgumentOfMethodCall(node, 2, "Object", "defineProperty") || + isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty") + ) { + return true; + } + + /* + * Object.defineProperties(obj, {foo: {set: ...}}) + * Object.create(proto, {foo: {set: ...}}) + */ + const grandparent = node.parent.parent; + + return ( + grandparent.type === "ObjectExpression" && + (isArgumentOfMethodCall(grandparent, 1, "Object", "create") || + isArgumentOfMethodCall( + grandparent, + 1, + "Object", + "defineProperties", + )) + ); } //------------------------------------------------------------------------------ @@ -136,215 +143,243 @@ function isPropertyDescriptor(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - enforceForClassMembers: true, - getWithoutSet: false, - setWithoutGet: true - }], - - docs: { - description: "Enforce getter and setter pairs in objects and classes", - recommended: false, - url: "https://eslint.org/docs/latest/rules/accessor-pairs" - }, - - schema: [{ - type: "object", - properties: { - getWithoutSet: { - type: "boolean" - }, - setWithoutGet: { - type: "boolean" - }, - enforceForClassMembers: { - type: "boolean" - } - }, - additionalProperties: false - }], - - messages: { - missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.", - missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.", - missingGetterInObjectLiteral: "Getter is not present for {{ name }}.", - missingSetterInObjectLiteral: "Setter is not present for {{ name }}.", - missingGetterInClass: "Getter is not present for class {{ name }}.", - missingSetterInClass: "Setter is not present for class {{ name }}." - } - }, - create(context) { - const [{ - getWithoutSet: checkGetWithoutSet, - setWithoutGet: checkSetWithoutGet, - enforceForClassMembers - }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports the given node. - * @param {ASTNode} node The node to report. - * @param {string} messageKind "missingGetter" or "missingSetter". - * @returns {void} - * @private - */ - function report(node, messageKind) { - if (node.type === "Property") { - context.report({ - node, - messageId: `${messageKind}InObjectLiteral`, - loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), - data: { name: astUtils.getFunctionNameWithKind(node.value) } - }); - } else if (node.type === "MethodDefinition") { - context.report({ - node, - messageId: `${messageKind}InClass`, - loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), - data: { name: astUtils.getFunctionNameWithKind(node.value) } - }); - } else { - context.report({ - node, - messageId: `${messageKind}InPropertyDescriptor` - }); - } - } - - /** - * Reports each of the nodes in the given list using the same messageId. - * @param {ASTNode[]} nodes Nodes to report. - * @param {string} messageKind "missingGetter" or "missingSetter". - * @returns {void} - * @private - */ - function reportList(nodes, messageKind) { - for (const node of nodes) { - report(node, messageKind); - } - } - - /** - * Checks accessor pairs in the given list of nodes. - * @param {ASTNode[]} nodes The list to check. - * @returns {void} - * @private - */ - function checkList(nodes) { - const accessors = []; - let found = false; - - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - - if (isAccessorKind(node)) { - - // Creates a new `AccessorData` object for the given getter or setter node. - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - // Merges the given `AccessorData` object into the given accessors list. - for (let j = 0; j < accessors.length; j++) { - const accessor = accessors[j]; - - if (areEqualKeys(accessor.key, key)) { - accessor.getters.push(...node.kind === "get" ? [node] : []); - accessor.setters.push(...node.kind === "set" ? [node] : []); - found = true; - break; - } - } - if (!found) { - accessors.push({ - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }); - } - found = false; - } - } - - for (const { getters, setters } of accessors) { - if (checkSetWithoutGet && setters.length && !getters.length) { - reportList(setters, "missingGetter"); - } - if (checkGetWithoutSet && getters.length && !setters.length) { - reportList(getters, "missingSetter"); - } - } - } - - /** - * Checks accessor pairs in an object literal. - * @param {ASTNode} node `ObjectExpression` node to check. - * @returns {void} - * @private - */ - function checkObjectLiteral(node) { - checkList(node.properties.filter(p => p.type === "Property")); - } - - /** - * Checks accessor pairs in a property descriptor. - * @param {ASTNode} node Property descriptor `ObjectExpression` node to check. - * @returns {void} - * @private - */ - function checkPropertyDescriptor(node) { - const namesToCheck = new Set(node.properties - .filter(p => p.type === "Property" && p.kind === "init" && !p.computed) - .map(({ key }) => key.name)); - - const hasGetter = namesToCheck.has("get"); - const hasSetter = namesToCheck.has("set"); - - if (checkSetWithoutGet && hasSetter && !hasGetter) { - report(node, "missingGetter"); - } - if (checkGetWithoutSet && hasGetter && !hasSetter) { - report(node, "missingSetter"); - } - } - - /** - * Checks the given object expression as an object literal and as a possible property descriptor. - * @param {ASTNode} node `ObjectExpression` node to check. - * @returns {void} - * @private - */ - function checkObjectExpression(node) { - checkObjectLiteral(node); - if (isPropertyDescriptor(node)) { - checkPropertyDescriptor(node); - } - } - - /** - * Checks the given class body. - * @param {ASTNode} node `ClassBody` node to check. - * @returns {void} - * @private - */ - function checkClassBody(node) { - const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition"); - - checkList(methodDefinitions.filter(m => m.static)); - checkList(methodDefinitions.filter(m => !m.static)); - } - - const listeners = {}; - - if (checkSetWithoutGet || checkGetWithoutSet) { - listeners.ObjectExpression = checkObjectExpression; - if (enforceForClassMembers) { - listeners.ClassBody = checkClassBody; - } - } - - return listeners; - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + enforceForClassMembers: true, + getWithoutSet: false, + setWithoutGet: true, + }, + ], + + docs: { + description: + "Enforce getter and setter pairs in objects and classes", + recommended: false, + url: "https://eslint.org/docs/latest/rules/accessor-pairs", + }, + + schema: [ + { + type: "object", + properties: { + getWithoutSet: { + type: "boolean", + }, + setWithoutGet: { + type: "boolean", + }, + enforceForClassMembers: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missingGetterInPropertyDescriptor: + "Getter is not present in property descriptor.", + missingSetterInPropertyDescriptor: + "Setter is not present in property descriptor.", + missingGetterInObjectLiteral: + "Getter is not present for {{ name }}.", + missingSetterInObjectLiteral: + "Setter is not present for {{ name }}.", + missingGetterInClass: "Getter is not present for class {{ name }}.", + missingSetterInClass: "Setter is not present for class {{ name }}.", + }, + }, + create(context) { + const [ + { + getWithoutSet: checkGetWithoutSet, + setWithoutGet: checkSetWithoutGet, + enforceForClassMembers, + }, + ] = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports the given node. + * @param {ASTNode} node The node to report. + * @param {string} messageKind "missingGetter" or "missingSetter". + * @returns {void} + * @private + */ + function report(node, messageKind) { + if (node.type === "Property") { + context.report({ + node, + messageId: `${messageKind}InObjectLiteral`, + loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), + data: { + name: astUtils.getFunctionNameWithKind(node.value), + }, + }); + } else if (node.type === "MethodDefinition") { + context.report({ + node, + messageId: `${messageKind}InClass`, + loc: astUtils.getFunctionHeadLoc(node.value, sourceCode), + data: { + name: astUtils.getFunctionNameWithKind(node.value), + }, + }); + } else { + context.report({ + node, + messageId: `${messageKind}InPropertyDescriptor`, + }); + } + } + + /** + * Reports each of the nodes in the given list using the same messageId. + * @param {ASTNode[]} nodes Nodes to report. + * @param {string} messageKind "missingGetter" or "missingSetter". + * @returns {void} + * @private + */ + function reportList(nodes, messageKind) { + for (const node of nodes) { + report(node, messageKind); + } + } + + /** + * Checks accessor pairs in the given list of nodes. + * @param {ASTNode[]} nodes The list to check. + * @returns {void} + * @private + */ + function checkList(nodes) { + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (isAccessorKind(node)) { + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = + name !== null ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push( + ...(node.kind === "get" ? [node] : []), + ); + accessor.setters.push( + ...(node.kind === "set" ? [node] : []), + ); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [], + }); + } + found = false; + } + } + + for (const { getters, setters } of accessors) { + if (checkSetWithoutGet && setters.length && !getters.length) { + reportList(setters, "missingGetter"); + } + if (checkGetWithoutSet && getters.length && !setters.length) { + reportList(getters, "missingSetter"); + } + } + } + + /** + * Checks accessor pairs in an object literal. + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkObjectLiteral(node) { + checkList(node.properties.filter(p => p.type === "Property")); + } + + /** + * Checks accessor pairs in a property descriptor. + * @param {ASTNode} node Property descriptor `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkPropertyDescriptor(node) { + const namesToCheck = new Set( + node.properties + .filter( + p => + p.type === "Property" && + p.kind === "init" && + !p.computed, + ) + .map(({ key }) => key.name), + ); + + const hasGetter = namesToCheck.has("get"); + const hasSetter = namesToCheck.has("set"); + + if (checkSetWithoutGet && hasSetter && !hasGetter) { + report(node, "missingGetter"); + } + if (checkGetWithoutSet && hasGetter && !hasSetter) { + report(node, "missingSetter"); + } + } + + /** + * Checks the given object expression as an object literal and as a possible property descriptor. + * @param {ASTNode} node `ObjectExpression` node to check. + * @returns {void} + * @private + */ + function checkObjectExpression(node) { + checkObjectLiteral(node); + if (isPropertyDescriptor(node)) { + checkPropertyDescriptor(node); + } + } + + /** + * Checks the given class body. + * @param {ASTNode} node `ClassBody` node to check. + * @returns {void} + * @private + */ + function checkClassBody(node) { + const methodDefinitions = node.body.filter( + m => m.type === "MethodDefinition", + ); + + checkList(methodDefinitions.filter(m => m.static)); + checkList(methodDefinitions.filter(m => !m.static)); + } + + const listeners = {}; + + if (checkSetWithoutGet || checkGetWithoutSet) { + listeners.ObjectExpression = checkObjectExpression; + if (enforceForClassMembers) { + listeners.ClassBody = checkClassBody; + } + } + + return listeners; + }, }; diff --git a/lib/rules/array-bracket-newline.js b/lib/rules/array-bracket-newline.js index 328ca05dbe09..991fcea81645 100644 --- a/lib/rules/array-bracket-newline.js +++ b/lib/rules/array-bracket-newline.js @@ -14,266 +14,278 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "array-bracket-newline", - url: "https://eslint.style/rules/js/array-bracket-newline" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "array-bracket-newline", + url: "https://eslint.style/rules/js/array-bracket-newline", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce linebreaks after opening and before closing array brackets", - recommended: false, - url: "https://eslint.org/docs/latest/rules/array-bracket-newline" - }, + docs: { + description: + "Enforce linebreaks after opening and before closing array brackets", + recommended: false, + url: "https://eslint.org/docs/latest/rules/array-bracket-newline", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - oneOf: [ - { - enum: ["always", "never", "consistent"] - }, - { - type: "object", - properties: { - multiline: { - type: "boolean" - }, - minItems: { - type: ["integer", "null"], - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - ], + schema: [ + { + oneOf: [ + { + enum: ["always", "never", "consistent"], + }, + { + type: "object", + properties: { + multiline: { + type: "boolean", + }, + minItems: { + type: ["integer", "null"], + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + ], - messages: { - unexpectedOpeningLinebreak: "There should be no linebreak after '['.", - unexpectedClosingLinebreak: "There should be no linebreak before ']'.", - missingOpeningLinebreak: "A linebreak is required after '['.", - missingClosingLinebreak: "A linebreak is required before ']'." - } - }, + messages: { + unexpectedOpeningLinebreak: + "There should be no linebreak after '['.", + unexpectedClosingLinebreak: + "There should be no linebreak before ']'.", + missingOpeningLinebreak: "A linebreak is required after '['.", + missingClosingLinebreak: "A linebreak is required before ']'.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; + create(context) { + const sourceCode = context.sourceCode; + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- + /** + * Normalizes a given option value. + * @param {string|Object|undefined} option An option value to parse. + * @returns {{multiline: boolean, minItems: number}} Normalized option object. + */ + function normalizeOptionValue(option) { + let consistent = false; + let multiline = false; + let minItems; - /** - * Normalizes a given option value. - * @param {string|Object|undefined} option An option value to parse. - * @returns {{multiline: boolean, minItems: number}} Normalized option object. - */ - function normalizeOptionValue(option) { - let consistent = false; - let multiline = false; - let minItems; + if (option) { + if (option === "consistent") { + consistent = true; + minItems = Number.POSITIVE_INFINITY; + } else if (option === "always" || option.minItems === 0) { + minItems = 0; + } else if (option === "never") { + minItems = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(option.multiline); + minItems = option.minItems || Number.POSITIVE_INFINITY; + } + } else { + consistent = false; + multiline = true; + minItems = Number.POSITIVE_INFINITY; + } - if (option) { - if (option === "consistent") { - consistent = true; - minItems = Number.POSITIVE_INFINITY; - } else if (option === "always" || option.minItems === 0) { - minItems = 0; - } else if (option === "never") { - minItems = Number.POSITIVE_INFINITY; - } else { - multiline = Boolean(option.multiline); - minItems = option.minItems || Number.POSITIVE_INFINITY; - } - } else { - consistent = false; - multiline = true; - minItems = Number.POSITIVE_INFINITY; - } + return { consistent, multiline, minItems }; + } - return { consistent, multiline, minItems }; - } + /** + * Normalizes a given option value. + * @param {string|Object|undefined} options An option value to parse. + * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. + */ + function normalizeOptions(options) { + const value = normalizeOptionValue(options); - /** - * Normalizes a given option value. - * @param {string|Object|undefined} options An option value to parse. - * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. - */ - function normalizeOptions(options) { - const value = normalizeOptionValue(options); + return { ArrayExpression: value, ArrayPattern: value }; + } - return { ArrayExpression: value, ArrayPattern: value }; - } + /** + * Reports that there shouldn't be a linebreak after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "unexpectedOpeningLinebreak", + fix(fixer) { + const nextToken = sourceCode.getTokenAfter(token, { + includeComments: true, + }); - /** - * Reports that there shouldn't be a linebreak after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoBeginningLinebreak(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "unexpectedOpeningLinebreak", - fix(fixer) { - const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }); + if (astUtils.isCommentToken(nextToken)) { + return null; + } - if (astUtils.isCommentToken(nextToken)) { - return null; - } + return fixer.removeRange([ + token.range[1], + nextToken.range[0], + ]); + }, + }); + } - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } + /** + * Reports that there shouldn't be a linebreak before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "unexpectedClosingLinebreak", + fix(fixer) { + const previousToken = sourceCode.getTokenBefore(token, { + includeComments: true, + }); - /** - * Reports that there shouldn't be a linebreak before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoEndingLinebreak(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "unexpectedClosingLinebreak", - fix(fixer) { - const previousToken = sourceCode.getTokenBefore(token, { includeComments: true }); + if (astUtils.isCommentToken(previousToken)) { + return null; + } - if (astUtils.isCommentToken(previousToken)) { - return null; - } + return fixer.removeRange([ + previousToken.range[1], + token.range[0], + ]); + }, + }); + } - return fixer.removeRange([previousToken.range[1], token.range[0]]); - } - }); - } + /** + * Reports that there should be a linebreak after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingOpeningLinebreak", + fix(fixer) { + return fixer.insertTextAfter(token, "\n"); + }, + }); + } - /** - * Reports that there should be a linebreak after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredBeginningLinebreak(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingOpeningLinebreak", - fix(fixer) { - return fixer.insertTextAfter(token, "\n"); - } - }); - } + /** + * Reports that there should be a linebreak before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingLinebreak(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingClosingLinebreak", + fix(fixer) { + return fixer.insertTextBefore(token, "\n"); + }, + }); + } - /** - * Reports that there should be a linebreak before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingLinebreak(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingClosingLinebreak", - fix(fixer) { - return fixer.insertTextBefore(token, "\n"); - } - }); - } + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node. + * @returns {void} + */ + function check(node) { + const elements = node.elements; + const normalizedOptions = normalizeOptions(context.options[0]); + const options = normalizedOptions[node.type]; + const openBracket = sourceCode.getFirstToken(node); + const closeBracket = sourceCode.getLastToken(node); + const firstIncComment = sourceCode.getTokenAfter(openBracket, { + includeComments: true, + }); + const lastIncComment = sourceCode.getTokenBefore(closeBracket, { + includeComments: true, + }); + const first = sourceCode.getTokenAfter(openBracket); + const last = sourceCode.getTokenBefore(closeBracket); - /** - * Reports a given node if it violated this rule. - * @param {ASTNode} node A node to check. This is an ArrayExpression node or an ArrayPattern node. - * @returns {void} - */ - function check(node) { - const elements = node.elements; - const normalizedOptions = normalizeOptions(context.options[0]); - const options = normalizedOptions[node.type]; - const openBracket = sourceCode.getFirstToken(node); - const closeBracket = sourceCode.getLastToken(node); - const firstIncComment = sourceCode.getTokenAfter(openBracket, { includeComments: true }); - const lastIncComment = sourceCode.getTokenBefore(closeBracket, { includeComments: true }); - const first = sourceCode.getTokenAfter(openBracket); - const last = sourceCode.getTokenBefore(closeBracket); + const needsLinebreaks = + elements.length >= options.minItems || + (options.multiline && + elements.length > 0 && + firstIncComment.loc.start.line !== + lastIncComment.loc.end.line) || + (elements.length === 0 && + firstIncComment.type === "Block" && + firstIncComment.loc.start.line !== + lastIncComment.loc.end.line && + firstIncComment === lastIncComment) || + (options.consistent && + openBracket.loc.end.line !== first.loc.start.line); - const needsLinebreaks = ( - elements.length >= options.minItems || - ( - options.multiline && - elements.length > 0 && - firstIncComment.loc.start.line !== lastIncComment.loc.end.line - ) || - ( - elements.length === 0 && - firstIncComment.type === "Block" && - firstIncComment.loc.start.line !== lastIncComment.loc.end.line && - firstIncComment === lastIncComment - ) || - ( - options.consistent && - openBracket.loc.end.line !== first.loc.start.line - ) - ); + /* + * Use tokens or comments to check multiline or not. + * But use only tokens to check whether linebreaks are needed. + * This allows: + * var arr = [ // eslint-disable-line foo + * 'a' + * ] + */ - /* - * Use tokens or comments to check multiline or not. - * But use only tokens to check whether linebreaks are needed. - * This allows: - * var arr = [ // eslint-disable-line foo - * 'a' - * ] - */ + if (needsLinebreaks) { + if (astUtils.isTokenOnSameLine(openBracket, first)) { + reportRequiredBeginningLinebreak(node, openBracket); + } + if (astUtils.isTokenOnSameLine(last, closeBracket)) { + reportRequiredEndingLinebreak(node, closeBracket); + } + } else { + if (!astUtils.isTokenOnSameLine(openBracket, first)) { + reportNoBeginningLinebreak(node, openBracket); + } + if (!astUtils.isTokenOnSameLine(last, closeBracket)) { + reportNoEndingLinebreak(node, closeBracket); + } + } + } - if (needsLinebreaks) { - if (astUtils.isTokenOnSameLine(openBracket, first)) { - reportRequiredBeginningLinebreak(node, openBracket); - } - if (astUtils.isTokenOnSameLine(last, closeBracket)) { - reportRequiredEndingLinebreak(node, closeBracket); - } - } else { - if (!astUtils.isTokenOnSameLine(openBracket, first)) { - reportNoBeginningLinebreak(node, openBracket); - } - if (!astUtils.isTokenOnSameLine(last, closeBracket)) { - reportNoEndingLinebreak(node, closeBracket); - } - } - } + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - ArrayPattern: check, - ArrayExpression: check - }; - } + return { + ArrayPattern: check, + ArrayExpression: check, + }; + }, }; diff --git a/lib/rules/array-bracket-spacing.js b/lib/rules/array-bracket-spacing.js index f61addbe6ad4..b851f6d7d637 100644 --- a/lib/rules/array-bracket-spacing.js +++ b/lib/rules/array-bracket-spacing.js @@ -13,250 +13,289 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "array-bracket-spacing", - url: "https://eslint.style/rules/js/array-bracket-spacing" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "array-bracket-spacing", + url: "https://eslint.style/rules/js/array-bracket-spacing", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce consistent spacing inside array brackets", - recommended: false, - url: "https://eslint.org/docs/latest/rules/array-bracket-spacing" - }, + docs: { + description: "Enforce consistent spacing inside array brackets", + recommended: false, + url: "https://eslint.org/docs/latest/rules/array-bracket-spacing", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - singleValue: { - type: "boolean" - }, - objectsInArrays: { - type: "boolean" - }, - arraysInArrays: { - type: "boolean" - } - }, - additionalProperties: false - } - ], + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + singleValue: { + type: "boolean", + }, + objectsInArrays: { + type: "boolean", + }, + arraysInArrays: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], - messages: { - unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", - unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", - missingSpaceAfter: "A space is required after '{{tokenValue}}'.", - missingSpaceBefore: "A space is required before '{{tokenValue}}'." - } - }, - create(context) { - const spaced = context.options[0] === "always", - sourceCode = context.sourceCode; + messages: { + unexpectedSpaceAfter: + "There should be no space after '{{tokenValue}}'.", + unexpectedSpaceBefore: + "There should be no space before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'.", + missingSpaceBefore: "A space is required before '{{tokenValue}}'.", + }, + }, + create(context) { + const spaced = context.options[0] === "always", + sourceCode = context.sourceCode; - /** - * Determines whether an option is set, relative to the spacing option. - * If spaced is "always", then check whether option is set to false. - * If spaced is "never", then check whether option is set to true. - * @param {Object} option The option to exclude. - * @returns {boolean} Whether or not the property is excluded. - */ - function isOptionSet(option) { - return context.options[1] ? context.options[1][option] === !spaced : false; - } + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] + ? context.options[1][option] === !spaced + : false; + } - const options = { - spaced, - singleElementException: isOptionSet("singleValue"), - objectsInArraysException: isOptionSet("objectsInArrays"), - arraysInArraysException: isOptionSet("arraysInArrays") - }; + const options = { + spaced, + singleElementException: isOptionSet("singleValue"), + objectsInArraysException: isOptionSet("objectsInArrays"), + arraysInArraysException: isOptionSet("arraysInArrays"), + }; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Reports that there shouldn't be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoBeginningSpace(node, token) { - const nextToken = sourceCode.getTokenAfter(token); + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + const nextToken = sourceCode.getTokenAfter(token); - context.report({ - node, - loc: { start: token.loc.end, end: nextToken.loc.start }, - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + token.range[1], + nextToken.range[0], + ]); + }, + }); + } - /** - * Reports that there shouldn't be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoEndingSpace(node, token) { - const previousToken = sourceCode.getTokenBefore(token); + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + const previousToken = sourceCode.getTokenBefore(token); - context.report({ - node, - loc: { start: previousToken.loc.end, end: token.loc.start }, - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.removeRange([previousToken.range[1], token.range[0]]); - } - }); - } + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + previousToken.range[1], + token.range[0], + ]); + }, + }); + } - /** - * Reports that there should be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredBeginningSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingSpaceAfter", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceAfter", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } - /** - * Reports that there should be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingSpaceBefore", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceBefore", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } - /** - * Determines if a node is an object type - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node is an object type. - */ - function isObjectType(node) { - return node && (node.type === "ObjectExpression" || node.type === "ObjectPattern"); - } + /** + * Determines if a node is an object type + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node is an object type. + */ + function isObjectType(node) { + return ( + node && + (node.type === "ObjectExpression" || + node.type === "ObjectPattern") + ); + } - /** - * Determines if a node is an array type - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node is an array type. - */ - function isArrayType(node) { - return node && (node.type === "ArrayExpression" || node.type === "ArrayPattern"); - } + /** + * Determines if a node is an array type + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node is an array type. + */ + function isArrayType(node) { + return ( + node && + (node.type === "ArrayExpression" || + node.type === "ArrayPattern") + ); + } - /** - * Validates the spacing around array brackets - * @param {ASTNode} node The node we're checking for spacing - * @returns {void} - */ - function validateArraySpacing(node) { - if (options.spaced && node.elements.length === 0) { - return; - } + /** + * Validates the spacing around array brackets + * @param {ASTNode} node The node we're checking for spacing + * @returns {void} + */ + function validateArraySpacing(node) { + if (options.spaced && node.elements.length === 0) { + return; + } - const first = sourceCode.getFirstToken(node), - second = sourceCode.getFirstToken(node, 1), - last = node.typeAnnotation - ? sourceCode.getTokenBefore(node.typeAnnotation) - : sourceCode.getLastToken(node), - penultimate = sourceCode.getTokenBefore(last), - firstElement = node.elements[0], - lastElement = node.elements.at(-1); + const first = sourceCode.getFirstToken(node), + second = sourceCode.getFirstToken(node, 1), + last = node.typeAnnotation + ? sourceCode.getTokenBefore(node.typeAnnotation) + : sourceCode.getLastToken(node), + penultimate = sourceCode.getTokenBefore(last), + firstElement = node.elements[0], + lastElement = node.elements.at(-1); - const openingBracketMustBeSpaced = - options.objectsInArraysException && isObjectType(firstElement) || - options.arraysInArraysException && isArrayType(firstElement) || - options.singleElementException && node.elements.length === 1 - ? !options.spaced : options.spaced; + const openingBracketMustBeSpaced = + (options.objectsInArraysException && + isObjectType(firstElement)) || + (options.arraysInArraysException && + isArrayType(firstElement)) || + (options.singleElementException && node.elements.length === 1) + ? !options.spaced + : options.spaced; - const closingBracketMustBeSpaced = - options.objectsInArraysException && isObjectType(lastElement) || - options.arraysInArraysException && isArrayType(lastElement) || - options.singleElementException && node.elements.length === 1 - ? !options.spaced : options.spaced; + const closingBracketMustBeSpaced = + (options.objectsInArraysException && + isObjectType(lastElement)) || + (options.arraysInArraysException && isArrayType(lastElement)) || + (options.singleElementException && node.elements.length === 1) + ? !options.spaced + : options.spaced; - if (astUtils.isTokenOnSameLine(first, second)) { - if (openingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(first, second)) { - reportRequiredBeginningSpace(node, first); - } - if (!openingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(first, second)) { - reportNoBeginningSpace(node, first); - } - } + if (astUtils.isTokenOnSameLine(first, second)) { + if ( + openingBracketMustBeSpaced && + !sourceCode.isSpaceBetweenTokens(first, second) + ) { + reportRequiredBeginningSpace(node, first); + } + if ( + !openingBracketMustBeSpaced && + sourceCode.isSpaceBetweenTokens(first, second) + ) { + reportNoBeginningSpace(node, first); + } + } - if (first !== penultimate && astUtils.isTokenOnSameLine(penultimate, last)) { - if (closingBracketMustBeSpaced && !sourceCode.isSpaceBetweenTokens(penultimate, last)) { - reportRequiredEndingSpace(node, last); - } - if (!closingBracketMustBeSpaced && sourceCode.isSpaceBetweenTokens(penultimate, last)) { - reportNoEndingSpace(node, last); - } - } - } + if ( + first !== penultimate && + astUtils.isTokenOnSameLine(penultimate, last) + ) { + if ( + closingBracketMustBeSpaced && + !sourceCode.isSpaceBetweenTokens(penultimate, last) + ) { + reportRequiredEndingSpace(node, last); + } + if ( + !closingBracketMustBeSpaced && + sourceCode.isSpaceBetweenTokens(penultimate, last) + ) { + reportNoEndingSpace(node, last); + } + } + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - ArrayPattern: validateArraySpacing, - ArrayExpression: validateArraySpacing - }; - } + return { + ArrayPattern: validateArraySpacing, + ArrayExpression: validateArraySpacing, + }; + }, }; diff --git a/lib/rules/array-callback-return.js b/lib/rules/array-callback-return.js index 974fea8cdd1b..8d640a9b86a0 100644 --- a/lib/rules/array-callback-return.js +++ b/lib/rules/array-callback-return.js @@ -16,7 +16,8 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; -const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; +const TARGET_METHODS = + /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|reduce(?:Right)?|some|sort|toSorted)$/u; /** * Checks a given node is a member access which has the specified name's @@ -26,7 +27,7 @@ const TARGET_METHODS = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach * the specified name's property. The node may be a `(Chain|Member)Expression` node. */ function isTargetMethod(node) { - return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS); + return astUtils.isSpecificMemberAccess(node, null, TARGET_METHODS); } /** @@ -35,14 +36,13 @@ function isTargetMethod(node) { * @returns {boolean} True if any segment is reachable; false otherwise. */ function isAnySegmentReachable(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; } /** @@ -52,10 +52,10 @@ function isAnySegmentReachable(segments) { * or else `Array.prototype.` if it is an instance method. */ function fullMethodName(arrayMethodName) { - if (["from", "of", "isArray"].includes(arrayMethodName)) { - return "Array.".concat(arrayMethodName); - } - return "Array.prototype.".concat(arrayMethodName); + if (["from", "of", "isArray"].includes(arrayMethodName)) { + return "Array.".concat(arrayMethodName); + } + return "Array.prototype.".concat(arrayMethodName); } /** @@ -67,73 +67,72 @@ function fullMethodName(arrayMethodName) { * null otherwise. */ function getArrayMethodName(node) { - let currentNode = node; - - while (currentNode) { - const parent = currentNode.parent; - - switch (parent.type) { - - /* - * Looks up the destination. e.g., - * foo.every(nativeFoo || function foo() { ... }); - */ - case "LogicalExpression": - case "ConditionalExpression": - case "ChainExpression": - currentNode = parent; - break; - - /* - * If the upper function is IIFE, checks the destination of the return value. - * e.g. - * foo.every((function() { - * // setup... - * return function callback() { ... }; - * })()); - */ - case "ReturnStatement": { - const func = astUtils.getUpperFunction(parent); - - if (func === null || !astUtils.isCallee(func)) { - return null; - } - currentNode = func.parent; - break; - } - - /* - * e.g. - * Array.from([], function() {}); - * list.every(function() {}); - */ - case "CallExpression": - if (astUtils.isArrayFromMethod(parent.callee)) { - if ( - parent.arguments.length >= 2 && - parent.arguments[1] === currentNode - ) { - return "from"; - } - } - if (isTargetMethod(parent.callee)) { - if ( - parent.arguments.length >= 1 && - parent.arguments[0] === currentNode - ) { - return astUtils.getStaticPropertyName(parent.callee); - } - } - return null; - - // Otherwise this node is not target. - default: - return null; - } - } - - /* c8 ignore next */ - return null; + let currentNode = node; + + while (currentNode) { + const parent = currentNode.parent; + + switch (parent.type) { + /* + * Looks up the destination. e.g., + * foo.every(nativeFoo || function foo() { ... }); + */ + case "LogicalExpression": + case "ConditionalExpression": + case "ChainExpression": + currentNode = parent; + break; + + /* + * If the upper function is IIFE, checks the destination of the return value. + * e.g. + * foo.every((function() { + * // setup... + * return function callback() { ... }; + * })()); + */ + case "ReturnStatement": { + const func = astUtils.getUpperFunction(parent); + + if (func === null || !astUtils.isCallee(func)) { + return null; + } + currentNode = func.parent; + break; + } + + /* + * e.g. + * Array.from([], function() {}); + * list.every(function() {}); + */ + case "CallExpression": + if (astUtils.isArrayFromMethod(parent.callee)) { + if ( + parent.arguments.length >= 2 && + parent.arguments[1] === currentNode + ) { + return "from"; + } + } + if (isTargetMethod(parent.callee)) { + if ( + parent.arguments.length >= 1 && + parent.arguments[0] === currentNode + ) { + return astUtils.getStaticPropertyName(parent.callee); + } + } + return null; + + // Otherwise this node is not target. + default: + return null; + } + } + + /* c8 ignore next */ + return null; } /** @@ -142,7 +141,7 @@ function getArrayMethodName(node) { * @returns {boolean} - `true` if the node is a void expression */ function isExpressionVoid(node) { - return node.type === "UnaryExpression" && node.operator === "void"; + return node.type === "UnaryExpression" && node.operator === "void"; } /** @@ -153,39 +152,40 @@ function isExpressionVoid(node) { * @returns {Array} - An array of fix objects to apply to the node. */ function voidPrependFixer(sourceCode, node, fixer) { - - const requiresParens = - - // prepending `void ` will fail if the node has a lower precedence than void - astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && - - // check if there are parentheses around the node to avoid redundant parentheses - !astUtils.isParenthesised(sourceCode, node); - - // avoid parentheses issues - const returnOrArrowToken = sourceCode.getTokenBefore( - node, - node.parent.type === "ArrowFunctionExpression" - ? astUtils.isArrowToken - - // isReturnToken - : token => token.type === "Keyword" && token.value === "return" - ); - - const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); - - const prependSpace = - - // is return token, as => allows void to be adjacent - returnOrArrowToken.value === "return" && - - // If two tokens (return and "(") are adjacent - returnOrArrowToken.range[1] === firstToken.range[0]; - - return [ - fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), - fixer.insertTextAfter(node, requiresParens ? ")" : "") - ]; + const requiresParens = + // prepending `void ` will fail if the node has a lower precedence than void + astUtils.getPrecedence(node) < + astUtils.getPrecedence({ + type: "UnaryExpression", + operator: "void", + }) && + // check if there are parentheses around the node to avoid redundant parentheses + !astUtils.isParenthesised(sourceCode, node); + + // avoid parentheses issues + const returnOrArrowToken = sourceCode.getTokenBefore( + node, + node.parent.type === "ArrowFunctionExpression" + ? astUtils.isArrowToken + : // isReturnToken + token => token.type === "Keyword" && token.value === "return", + ); + + const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); + + const prependSpace = + // is return token, as => allows void to be adjacent + returnOrArrowToken.value === "return" && + // If two tokens (return and "(") are adjacent + returnOrArrowToken.range[1] === firstToken.range[0]; + + return [ + fixer.insertTextBefore( + firstToken, + `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`, + ), + fixer.insertTextAfter(node, requiresParens ? ")" : ""), + ]; } /** @@ -196,14 +196,17 @@ function voidPrependFixer(sourceCode, node, fixer) { * @returns {Array} - An array of fix objects to apply to the node. */ function curlyWrapFixer(sourceCode, node, fixer) { - const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); - const firstToken = sourceCode.getTokenAfter(arrowToken); - const lastToken = sourceCode.getLastToken(node); - - return [ - fixer.insertTextBefore(firstToken, "{"), - fixer.insertTextAfter(lastToken, "}") - ]; + const arrowToken = sourceCode.getTokenBefore( + node.body, + astUtils.isArrowToken, + ); + const firstToken = sourceCode.getTokenAfter(arrowToken); + const lastToken = sourceCode.getLastToken(node); + + return [ + fixer.insertTextBefore(firstToken, "{"), + fixer.insertTextAfter(lastToken, "}"), + ]; } //------------------------------------------------------------------------------ @@ -212,237 +215,280 @@ function curlyWrapFixer(sourceCode, node, fixer) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - allowImplicit: false, - checkForEach: false, - allowVoid: false - }], - - docs: { - description: "Enforce `return` statements in callbacks of array methods", - recommended: false, - url: "https://eslint.org/docs/latest/rules/array-callback-return" - }, - - // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive - hasSuggestions: true, - - schema: [ - { - type: "object", - properties: { - allowImplicit: { - type: "boolean" - }, - checkForEach: { - type: "boolean" - }, - allowVoid: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - expectedAtEnd: "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.", - expectedInside: "{{arrayMethodName}}() expects a return value from {{name}}.", - expectedReturnValue: "{{arrayMethodName}}() expects a return value from {{name}}.", - expectedNoReturnValue: "{{arrayMethodName}}() expects no useless return value from {{name}}.", - wrapBraces: "Wrap the expression in `{}`.", - prependVoid: "Prepend `void` to the expression." - } - }, - - create(context) { - const [options] = context.options; - const sourceCode = context.sourceCode; - - let funcInfo = { - arrayMethodName: null, - upper: null, - codePath: null, - hasReturn: false, - shouldCheck: false, - node: null - }; - - /** - * Checks whether or not the last code path segment is reachable. - * Then reports this function if the segment is reachable. - * - * If the last code path segment is reachable, there are paths which are not - * returned or thrown. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkLastSegment(node) { - - if (!funcInfo.shouldCheck) { - return; - } - - const messageAndSuggestions = { messageId: "", suggest: [] }; - - if (funcInfo.arrayMethodName === "forEach") { - if (options.checkForEach && node.type === "ArrowFunctionExpression" && node.expression) { - - if (options.allowVoid) { - if (isExpressionVoid(node.body)) { - return; - } - - messageAndSuggestions.messageId = "expectedNoReturnValue"; - messageAndSuggestions.suggest = [ - { - messageId: "wrapBraces", - fix(fixer) { - return curlyWrapFixer(sourceCode, node, fixer); - } - }, - { - messageId: "prependVoid", - fix(fixer) { - return voidPrependFixer(sourceCode, node.body, fixer); - } - } - ]; - } else { - messageAndSuggestions.messageId = "expectedNoReturnValue"; - messageAndSuggestions.suggest = [{ - messageId: "wrapBraces", - fix(fixer) { - return curlyWrapFixer(sourceCode, node, fixer); - } - }]; - } - } - } else { - if (node.body.type === "BlockStatement" && isAnySegmentReachable(funcInfo.currentSegments)) { - messageAndSuggestions.messageId = funcInfo.hasReturn ? "expectedAtEnd" : "expectedInside"; - } - } - - if (messageAndSuggestions.messageId) { - const name = astUtils.getFunctionNameWithKind(node); - - context.report({ - node, - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - messageId: messageAndSuggestions.messageId, - data: { name, arrayMethodName: fullMethodName(funcInfo.arrayMethodName) }, - suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null - }); - } - } - - return { - - // Stacks this function's information. - onCodePathStart(codePath, node) { - - let methodName = null; - - if (TARGET_NODE_TYPE.test(node.type)) { - methodName = getArrayMethodName(node); - } - - funcInfo = { - arrayMethodName: methodName, - upper: funcInfo, - codePath, - hasReturn: false, - shouldCheck: - methodName && - !node.async && - !node.generator, - node, - currentSegments: new Set() - }; - }, - - // Pops this function's information. - onCodePathEnd() { - funcInfo = funcInfo.upper; - }, - - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - - // Checks the return statement is valid. - ReturnStatement(node) { - - if (!funcInfo.shouldCheck) { - return; - } - - funcInfo.hasReturn = true; - - const messageAndSuggestions = { messageId: "", suggest: [] }; - - if (funcInfo.arrayMethodName === "forEach") { - - // if checkForEach: true, returning a value at any path inside a forEach is not allowed - if (options.checkForEach && node.argument) { - - if (options.allowVoid) { - if (isExpressionVoid(node.argument)) { - return; - } - - messageAndSuggestions.messageId = "expectedNoReturnValue"; - messageAndSuggestions.suggest = [{ - messageId: "prependVoid", - fix(fixer) { - return voidPrependFixer(sourceCode, node.argument, fixer); - } - }]; - } else { - messageAndSuggestions.messageId = "expectedNoReturnValue"; - } - } - } else { - - // if allowImplicit: false, should also check node.argument - if (!options.allowImplicit && !node.argument) { - messageAndSuggestions.messageId = "expectedReturnValue"; - } - } - - if (messageAndSuggestions.messageId) { - context.report({ - node, - messageId: messageAndSuggestions.messageId, - data: { - name: astUtils.getFunctionNameWithKind(funcInfo.node), - arrayMethodName: fullMethodName(funcInfo.arrayMethodName) - }, - suggest: messageAndSuggestions.suggest.length !== 0 ? messageAndSuggestions.suggest : null - }); - } - }, - - // Reports a given function if the last path is reachable. - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + allowImplicit: false, + checkForEach: false, + allowVoid: false, + }, + ], + + docs: { + description: + "Enforce `return` statements in callbacks of array methods", + recommended: false, + url: "https://eslint.org/docs/latest/rules/array-callback-return", + }, + + // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- false positive + hasSuggestions: true, + + schema: [ + { + type: "object", + properties: { + allowImplicit: { + type: "boolean", + }, + checkForEach: { + type: "boolean", + }, + allowVoid: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + expectedAtEnd: + "{{arrayMethodName}}() expects a value to be returned at the end of {{name}}.", + expectedInside: + "{{arrayMethodName}}() expects a return value from {{name}}.", + expectedReturnValue: + "{{arrayMethodName}}() expects a return value from {{name}}.", + expectedNoReturnValue: + "{{arrayMethodName}}() expects no useless return value from {{name}}.", + wrapBraces: "Wrap the expression in `{}`.", + prependVoid: "Prepend `void` to the expression.", + }, + }, + + create(context) { + const [options] = context.options; + const sourceCode = context.sourceCode; + + let funcInfo = { + arrayMethodName: null, + upper: null, + codePath: null, + hasReturn: false, + shouldCheck: false, + node: null, + }; + + /** + * Checks whether or not the last code path segment is reachable. + * Then reports this function if the segment is reachable. + * + * If the last code path segment is reachable, there are paths which are not + * returned or thrown. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkLastSegment(node) { + if (!funcInfo.shouldCheck) { + return; + } + + const messageAndSuggestions = { messageId: "", suggest: [] }; + + if (funcInfo.arrayMethodName === "forEach") { + if ( + options.checkForEach && + node.type === "ArrowFunctionExpression" && + node.expression + ) { + if (options.allowVoid) { + if (isExpressionVoid(node.body)) { + return; + } + + messageAndSuggestions.messageId = + "expectedNoReturnValue"; + messageAndSuggestions.suggest = [ + { + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer( + sourceCode, + node, + fixer, + ); + }, + }, + { + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer( + sourceCode, + node.body, + fixer, + ); + }, + }, + ]; + } else { + messageAndSuggestions.messageId = + "expectedNoReturnValue"; + messageAndSuggestions.suggest = [ + { + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer( + sourceCode, + node, + fixer, + ); + }, + }, + ]; + } + } + } else { + if ( + node.body.type === "BlockStatement" && + isAnySegmentReachable(funcInfo.currentSegments) + ) { + messageAndSuggestions.messageId = funcInfo.hasReturn + ? "expectedAtEnd" + : "expectedInside"; + } + } + + if (messageAndSuggestions.messageId) { + const name = astUtils.getFunctionNameWithKind(node); + + context.report({ + node, + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + messageId: messageAndSuggestions.messageId, + data: { + name, + arrayMethodName: fullMethodName( + funcInfo.arrayMethodName, + ), + }, + suggest: + messageAndSuggestions.suggest.length !== 0 + ? messageAndSuggestions.suggest + : null, + }); + } + } + + return { + // Stacks this function's information. + onCodePathStart(codePath, node) { + let methodName = null; + + if (TARGET_NODE_TYPE.test(node.type)) { + methodName = getArrayMethodName(node); + } + + funcInfo = { + arrayMethodName: methodName, + upper: funcInfo, + codePath, + hasReturn: false, + shouldCheck: methodName && !node.async && !node.generator, + node, + currentSegments: new Set(), + }; + }, + + // Pops this function's information. + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Checks the return statement is valid. + ReturnStatement(node) { + if (!funcInfo.shouldCheck) { + return; + } + + funcInfo.hasReturn = true; + + const messageAndSuggestions = { messageId: "", suggest: [] }; + + if (funcInfo.arrayMethodName === "forEach") { + // if checkForEach: true, returning a value at any path inside a forEach is not allowed + if (options.checkForEach && node.argument) { + if (options.allowVoid) { + if (isExpressionVoid(node.argument)) { + return; + } + + messageAndSuggestions.messageId = + "expectedNoReturnValue"; + messageAndSuggestions.suggest = [ + { + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer( + sourceCode, + node.argument, + fixer, + ); + }, + }, + ]; + } else { + messageAndSuggestions.messageId = + "expectedNoReturnValue"; + } + } + } else { + // if allowImplicit: false, should also check node.argument + if (!options.allowImplicit && !node.argument) { + messageAndSuggestions.messageId = "expectedReturnValue"; + } + } + + if (messageAndSuggestions.messageId) { + context.report({ + node, + messageId: messageAndSuggestions.messageId, + data: { + name: astUtils.getFunctionNameWithKind( + funcInfo.node, + ), + arrayMethodName: fullMethodName( + funcInfo.arrayMethodName, + ), + }, + suggest: + messageAndSuggestions.suggest.length !== 0 + ? messageAndSuggestions.suggest + : null, + }); + } + }, + + // Reports a given function if the last path is reachable. + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + }; + }, }; diff --git a/lib/rules/array-element-newline.js b/lib/rules/array-element-newline.js index dc739598f536..b62de15f4986 100644 --- a/lib/rules/array-element-newline.js +++ b/lib/rules/array-element-newline.js @@ -14,316 +14,361 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "array-element-newline", - url: "https://eslint.style/rules/js/array-element-newline" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce line breaks after each array element", - recommended: false, - url: "https://eslint.org/docs/latest/rules/array-element-newline" - }, - - fixable: "whitespace", - - schema: { - definitions: { - basicConfig: { - oneOf: [ - { - enum: ["always", "never", "consistent"] - }, - { - type: "object", - properties: { - multiline: { - type: "boolean" - }, - minItems: { - type: ["integer", "null"], - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - }, - type: "array", - items: [ - { - oneOf: [ - { - $ref: "#/definitions/basicConfig" - }, - { - type: "object", - properties: { - ArrayExpression: { - $ref: "#/definitions/basicConfig" - }, - ArrayPattern: { - $ref: "#/definitions/basicConfig" - } - }, - additionalProperties: false, - minProperties: 1 - } - ] - } - ] - }, - - messages: { - unexpectedLineBreak: "There should be no linebreak here.", - missingLineBreak: "There should be a linebreak after this element." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Normalizes a given option value. - * @param {string|Object|undefined} providedOption An option value to parse. - * @returns {{multiline: boolean, minItems: number}} Normalized option object. - */ - function normalizeOptionValue(providedOption) { - let consistent = false; - let multiline = false; - let minItems; - - const option = providedOption || "always"; - - if (!option || option === "always" || option.minItems === 0) { - minItems = 0; - } else if (option === "never") { - minItems = Number.POSITIVE_INFINITY; - } else if (option === "consistent") { - consistent = true; - minItems = Number.POSITIVE_INFINITY; - } else { - multiline = Boolean(option.multiline); - minItems = option.minItems || Number.POSITIVE_INFINITY; - } - - return { consistent, multiline, minItems }; - } - - /** - * Normalizes a given option value. - * @param {string|Object|undefined} options An option value to parse. - * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. - */ - function normalizeOptions(options) { - if (options && (options.ArrayExpression || options.ArrayPattern)) { - let expressionOptions, patternOptions; - - if (options.ArrayExpression) { - expressionOptions = normalizeOptionValue(options.ArrayExpression); - } - - if (options.ArrayPattern) { - patternOptions = normalizeOptionValue(options.ArrayPattern); - } - - return { ArrayExpression: expressionOptions, ArrayPattern: patternOptions }; - } - - const value = normalizeOptionValue(options); - - return { ArrayExpression: value, ArrayPattern: value }; - } - - /** - * Reports that there shouldn't be a line break after the first token - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoLineBreak(token) { - const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); - - context.report({ - loc: { - start: tokenBefore.loc.end, - end: token.loc.start - }, - messageId: "unexpectedLineBreak", - fix(fixer) { - if (astUtils.isCommentToken(tokenBefore)) { - return null; - } - - if (!astUtils.isTokenOnSameLine(tokenBefore, token)) { - return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " "); - } - - /* - * This will check if the comma is on the same line as the next element - * Following array: - * [ - * 1 - * , 2 - * , 3 - * ] - * - * will be fixed to: - * [ - * 1, 2, 3 - * ] - */ - const twoTokensBefore = sourceCode.getTokenBefore(tokenBefore, { includeComments: true }); - - if (astUtils.isCommentToken(twoTokensBefore)) { - return null; - } - - return fixer.replaceTextRange([twoTokensBefore.range[1], tokenBefore.range[0]], ""); - - } - }); - } - - /** - * Reports that there should be a line break after the first token - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredLineBreak(token) { - const tokenBefore = sourceCode.getTokenBefore(token, { includeComments: true }); - - context.report({ - loc: { - start: tokenBefore.loc.end, - end: token.loc.start - }, - messageId: "missingLineBreak", - fix(fixer) { - return fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n"); - } - }); - } - - /** - * Reports a given node if it violated this rule. - * @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node. - * @returns {void} - */ - function check(node) { - const elements = node.elements; - const normalizedOptions = normalizeOptions(context.options[0]); - const options = normalizedOptions[node.type]; - - if (!options) { - return; - } - - let elementBreak = false; - - /* - * MULTILINE: true - * loop through every element and check - * if at least one element has linebreaks inside - * this ensures that following is not valid (due to elements are on the same line): - * - * [ - * 1, - * 2, - * 3 - * ] - */ - if (options.multiline) { - elementBreak = elements - .filter(element => element !== null) - .some(element => element.loc.start.line !== element.loc.end.line); - } - - let linebreaksCount = 0; - - for (let i = 0; i < node.elements.length; i++) { - const element = node.elements[i]; - - const previousElement = elements[i - 1]; - - if (i === 0 || element === null || previousElement === null) { - continue; - } - - const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); - const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); - const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); - - if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { - linebreaksCount++; - } - } - - const needsLinebreaks = ( - elements.length >= options.minItems || - ( - options.multiline && - elementBreak - ) || - ( - options.consistent && - linebreaksCount > 0 && - linebreaksCount < node.elements.length - ) - ); - - elements.forEach((element, i) => { - const previousElement = elements[i - 1]; - - if (i === 0 || element === null || previousElement === null) { - return; - } - - const commaToken = sourceCode.getFirstTokenBetween(previousElement, element, astUtils.isCommaToken); - const lastTokenOfPreviousElement = sourceCode.getTokenBefore(commaToken); - const firstTokenOfCurrentElement = sourceCode.getTokenAfter(commaToken); - - if (needsLinebreaks) { - if (astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { - reportRequiredLineBreak(firstTokenOfCurrentElement); - } - } else { - if (!astUtils.isTokenOnSameLine(lastTokenOfPreviousElement, firstTokenOfCurrentElement)) { - reportNoLineBreak(firstTokenOfCurrentElement); - } - } - }); - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - ArrayPattern: check, - ArrayExpression: check - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "array-element-newline", + url: "https://eslint.style/rules/js/array-element-newline", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce line breaks after each array element", + recommended: false, + url: "https://eslint.org/docs/latest/rules/array-element-newline", + }, + + fixable: "whitespace", + + schema: { + definitions: { + basicConfig: { + oneOf: [ + { + enum: ["always", "never", "consistent"], + }, + { + type: "object", + properties: { + multiline: { + type: "boolean", + }, + minItems: { + type: ["integer", "null"], + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + }, + type: "array", + items: [ + { + oneOf: [ + { + $ref: "#/definitions/basicConfig", + }, + { + type: "object", + properties: { + ArrayExpression: { + $ref: "#/definitions/basicConfig", + }, + ArrayPattern: { + $ref: "#/definitions/basicConfig", + }, + }, + additionalProperties: false, + minProperties: 1, + }, + ], + }, + ], + }, + + messages: { + unexpectedLineBreak: "There should be no linebreak here.", + missingLineBreak: "There should be a linebreak after this element.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} providedOption An option value to parse. + * @returns {{multiline: boolean, minItems: number}} Normalized option object. + */ + function normalizeOptionValue(providedOption) { + let consistent = false; + let multiline = false; + let minItems; + + const option = providedOption || "always"; + + if (!option || option === "always" || option.minItems === 0) { + minItems = 0; + } else if (option === "never") { + minItems = Number.POSITIVE_INFINITY; + } else if (option === "consistent") { + consistent = true; + minItems = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(option.multiline); + minItems = option.minItems || Number.POSITIVE_INFINITY; + } + + return { consistent, multiline, minItems }; + } + + /** + * Normalizes a given option value. + * @param {string|Object|undefined} options An option value to parse. + * @returns {{ArrayExpression: {multiline: boolean, minItems: number}, ArrayPattern: {multiline: boolean, minItems: number}}} Normalized option object. + */ + function normalizeOptions(options) { + if (options && (options.ArrayExpression || options.ArrayPattern)) { + let expressionOptions, patternOptions; + + if (options.ArrayExpression) { + expressionOptions = normalizeOptionValue( + options.ArrayExpression, + ); + } + + if (options.ArrayPattern) { + patternOptions = normalizeOptionValue(options.ArrayPattern); + } + + return { + ArrayExpression: expressionOptions, + ArrayPattern: patternOptions, + }; + } + + const value = normalizeOptionValue(options); + + return { ArrayExpression: value, ArrayPattern: value }; + } + + /** + * Reports that there shouldn't be a line break after the first token + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoLineBreak(token) { + const tokenBefore = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + + context.report({ + loc: { + start: tokenBefore.loc.end, + end: token.loc.start, + }, + messageId: "unexpectedLineBreak", + fix(fixer) { + if (astUtils.isCommentToken(tokenBefore)) { + return null; + } + + if (!astUtils.isTokenOnSameLine(tokenBefore, token)) { + return fixer.replaceTextRange( + [tokenBefore.range[1], token.range[0]], + " ", + ); + } + + /* + * This will check if the comma is on the same line as the next element + * Following array: + * [ + * 1 + * , 2 + * , 3 + * ] + * + * will be fixed to: + * [ + * 1, 2, 3 + * ] + */ + const twoTokensBefore = sourceCode.getTokenBefore( + tokenBefore, + { includeComments: true }, + ); + + if (astUtils.isCommentToken(twoTokensBefore)) { + return null; + } + + return fixer.replaceTextRange( + [twoTokensBefore.range[1], tokenBefore.range[0]], + "", + ); + }, + }); + } + + /** + * Reports that there should be a line break after the first token + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredLineBreak(token) { + const tokenBefore = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + + context.report({ + loc: { + start: tokenBefore.loc.end, + end: token.loc.start, + }, + messageId: "missingLineBreak", + fix(fixer) { + return fixer.replaceTextRange( + [tokenBefore.range[1], token.range[0]], + "\n", + ); + }, + }); + } + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ObjectExpression node or an ObjectPattern node. + * @returns {void} + */ + function check(node) { + const elements = node.elements; + const normalizedOptions = normalizeOptions(context.options[0]); + const options = normalizedOptions[node.type]; + + if (!options) { + return; + } + + let elementBreak = false; + + /* + * MULTILINE: true + * loop through every element and check + * if at least one element has linebreaks inside + * this ensures that following is not valid (due to elements are on the same line): + * + * [ + * 1, + * 2, + * 3 + * ] + */ + if (options.multiline) { + elementBreak = elements + .filter(element => element !== null) + .some( + element => + element.loc.start.line !== element.loc.end.line, + ); + } + + let linebreaksCount = 0; + + for (let i = 0; i < node.elements.length; i++) { + const element = node.elements[i]; + + const previousElement = elements[i - 1]; + + if (i === 0 || element === null || previousElement === null) { + continue; + } + + const commaToken = sourceCode.getFirstTokenBetween( + previousElement, + element, + astUtils.isCommaToken, + ); + const lastTokenOfPreviousElement = + sourceCode.getTokenBefore(commaToken); + const firstTokenOfCurrentElement = + sourceCode.getTokenAfter(commaToken); + + if ( + !astUtils.isTokenOnSameLine( + lastTokenOfPreviousElement, + firstTokenOfCurrentElement, + ) + ) { + linebreaksCount++; + } + } + + const needsLinebreaks = + elements.length >= options.minItems || + (options.multiline && elementBreak) || + (options.consistent && + linebreaksCount > 0 && + linebreaksCount < node.elements.length); + + elements.forEach((element, i) => { + const previousElement = elements[i - 1]; + + if (i === 0 || element === null || previousElement === null) { + return; + } + + const commaToken = sourceCode.getFirstTokenBetween( + previousElement, + element, + astUtils.isCommaToken, + ); + const lastTokenOfPreviousElement = + sourceCode.getTokenBefore(commaToken); + const firstTokenOfCurrentElement = + sourceCode.getTokenAfter(commaToken); + + if (needsLinebreaks) { + if ( + astUtils.isTokenOnSameLine( + lastTokenOfPreviousElement, + firstTokenOfCurrentElement, + ) + ) { + reportRequiredLineBreak(firstTokenOfCurrentElement); + } + } else { + if ( + !astUtils.isTokenOnSameLine( + lastTokenOfPreviousElement, + firstTokenOfCurrentElement, + ) + ) { + reportNoLineBreak(firstTokenOfCurrentElement); + } + } + }); + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + ArrayPattern: check, + ArrayExpression: check, + }; + }, }; diff --git a/lib/rules/arrow-body-style.js b/lib/rules/arrow-body-style.js index 4a652dc2171a..0539090751b6 100644 --- a/lib/rules/arrow-body-style.js +++ b/lib/rules/arrow-body-style.js @@ -16,284 +16,403 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always", "never"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["as-needed"] - }, - { - type: "object", - properties: { - requireReturnForObjectLiteral: { type: "boolean" } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - fixable: "code", - - messages: { - unexpectedOtherBlock: "Unexpected block statement surrounding arrow body.", - unexpectedEmptyBlock: "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", - unexpectedObjectBlock: "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", - unexpectedSingleBlock: "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", - expectedBlock: "Expected block statement surrounding arrow body." - } - }, - - create(context) { - const options = context.options; - const always = options[0] === "always"; - const asNeeded = options[0] === "as-needed"; - const never = options[0] === "never"; - const requireReturnForObjectLiteral = options[1] && options[1].requireReturnForObjectLiteral; - const sourceCode = context.sourceCode; - let funcInfo = null; - - /** - * Checks whether the given node has ASI problem or not. - * @param {Token} token The token to check. - * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed. - */ - function hasASIProblem(token) { - return token && token.type === "Punctuator" && /^[([/`+-]/u.test(token.value); - } - - /** - * Gets the closing parenthesis by the given node. - * @param {ASTNode} node first node after an opening parenthesis. - * @returns {Token} The found closing parenthesis token. - */ - function findClosingParen(node) { - let nodeToCheck = node; - - while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) { - nodeToCheck = nodeToCheck.parent; - } - return sourceCode.getTokenAfter(nodeToCheck); - } - - /** - * Check whether the node is inside of a for loop's init - * @param {ASTNode} node node is inside for loop - * @returns {boolean} `true` if the node is inside of a for loop, else `false` - */ - function isInsideForLoopInitializer(node) { - if (node && node.parent) { - if (node.parent.type === "ForStatement" && node.parent.init === node) { - return true; - } - return isInsideForLoopInitializer(node.parent); - } - return false; - } - - /** - * Determines whether a arrow function body needs braces - * @param {ASTNode} node The arrow function node. - * @returns {void} - */ - function validate(node) { - const arrowBody = node.body; - - if (arrowBody.type === "BlockStatement") { - const blockBody = arrowBody.body; - - if (blockBody.length !== 1 && !never) { - return; - } - - if (asNeeded && requireReturnForObjectLiteral && blockBody[0].type === "ReturnStatement" && - blockBody[0].argument && blockBody[0].argument.type === "ObjectExpression") { - return; - } - - if (never || asNeeded && blockBody[0].type === "ReturnStatement") { - let messageId; - - if (blockBody.length === 0) { - messageId = "unexpectedEmptyBlock"; - } else if (blockBody.length > 1 || blockBody[0].type !== "ReturnStatement") { - messageId = "unexpectedOtherBlock"; - } else if (blockBody[0].argument === null) { - messageId = "unexpectedSingleBlock"; - } else if (astUtils.isOpeningBraceToken(sourceCode.getFirstToken(blockBody[0], { skip: 1 }))) { - messageId = "unexpectedObjectBlock"; - } else { - messageId = "unexpectedSingleBlock"; - } - - context.report({ - node, - loc: arrowBody.loc, - messageId, - fix(fixer) { - const fixes = []; - - if (blockBody.length !== 1 || - blockBody[0].type !== "ReturnStatement" || - !blockBody[0].argument || - hasASIProblem(sourceCode.getTokenAfter(arrowBody)) - ) { - return fixes; - } - - const openingBrace = sourceCode.getFirstToken(arrowBody); - const closingBrace = sourceCode.getLastToken(arrowBody); - const firstValueToken = sourceCode.getFirstToken(blockBody[0], 1); - const lastValueToken = sourceCode.getLastToken(blockBody[0]); - const commentsExist = - sourceCode.commentsExistBetween(openingBrace, firstValueToken) || - sourceCode.commentsExistBetween(lastValueToken, closingBrace); - - /* - * Remove tokens around the return value. - * If comments don't exist, remove extra spaces as well. - */ - if (commentsExist) { - fixes.push( - fixer.remove(openingBrace), - fixer.remove(closingBrace), - fixer.remove(sourceCode.getTokenAfter(openingBrace)) // return keyword - ); - } else { - fixes.push( - fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]), - fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]]) - ); - } - - /* - * If the first token of the return value is `{` or the return value is a sequence expression, - * enclose the return value by parentheses to avoid syntax error. - */ - if (astUtils.isOpeningBraceToken(firstValueToken) || blockBody[0].argument.type === "SequenceExpression" || (funcInfo.hasInOperator && isInsideForLoopInitializer(node))) { - if (!astUtils.isParenthesised(sourceCode, blockBody[0].argument)) { - fixes.push( - fixer.insertTextBefore(firstValueToken, "("), - fixer.insertTextAfter(lastValueToken, ")") - ); - } - } - - /* - * If the last token of the return statement is semicolon, remove it. - * Non-block arrow body is an expression, not a statement. - */ - if (astUtils.isSemicolonToken(lastValueToken)) { - fixes.push(fixer.remove(lastValueToken)); - } - - return fixes; - } - }); - } - } else { - if (always || (asNeeded && requireReturnForObjectLiteral && arrowBody.type === "ObjectExpression")) { - context.report({ - node, - loc: arrowBody.loc, - messageId: "expectedBlock", - fix(fixer) { - const fixes = []; - const arrowToken = sourceCode.getTokenBefore(arrowBody, astUtils.isArrowToken); - const [firstTokenAfterArrow, secondTokenAfterArrow] = sourceCode.getTokensAfter(arrowToken, { count: 2 }); - const lastToken = sourceCode.getLastToken(node); - - let parenthesisedObjectLiteral = null; - - if ( - astUtils.isOpeningParenToken(firstTokenAfterArrow) && - astUtils.isOpeningBraceToken(secondTokenAfterArrow) - ) { - const braceNode = sourceCode.getNodeByRangeIndex(secondTokenAfterArrow.range[0]); - - if (braceNode.type === "ObjectExpression") { - parenthesisedObjectLiteral = braceNode; - } - } - - // If the value is object literal, remove parentheses which were forced by syntax. - if (parenthesisedObjectLiteral) { - const openingParenToken = firstTokenAfterArrow; - const openingBraceToken = secondTokenAfterArrow; - - if (astUtils.isTokenOnSameLine(openingParenToken, openingBraceToken)) { - fixes.push(fixer.replaceText(openingParenToken, "{return ")); - } else { - - // Avoid ASI - fixes.push( - fixer.replaceText(openingParenToken, "{"), - fixer.insertTextBefore(openingBraceToken, "return ") - ); - } - - // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo() - fixes.push(fixer.remove(findClosingParen(parenthesisedObjectLiteral))); - fixes.push(fixer.insertTextAfter(lastToken, "}")); - - } else { - fixes.push(fixer.insertTextBefore(firstTokenAfterArrow, "{return ")); - fixes.push(fixer.insertTextAfter(lastToken, "}")); - } - - return fixes; - } - }); - } - } - } - - return { - "BinaryExpression[operator='in']"() { - let info = funcInfo; - - while (info) { - info.hasInOperator = true; - info = info.upper; - } - }, - ArrowFunctionExpression() { - funcInfo = { - upper: funcInfo, - hasInOperator: false - }; - }, - "ArrowFunctionExpression:exit"(node) { - validate(node); - funcInfo = funcInfo.upper; - } - }; - } + meta: { + 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", + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always", "never"], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["as-needed"], + }, + { + type: "object", + properties: { + requireReturnForObjectLiteral: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + fixable: "code", + + messages: { + unexpectedOtherBlock: + "Unexpected block statement surrounding arrow body.", + unexpectedEmptyBlock: + "Unexpected block statement surrounding arrow body; put a value of `undefined` immediately after the `=>`.", + unexpectedObjectBlock: + "Unexpected block statement surrounding arrow body; parenthesize the returned value and move it immediately after the `=>`.", + unexpectedSingleBlock: + "Unexpected block statement surrounding arrow body; move the returned value immediately after the `=>`.", + expectedBlock: "Expected block statement surrounding arrow body.", + }, + }, + + create(context) { + const options = context.options; + const always = options[0] === "always"; + const asNeeded = options[0] === "as-needed"; + const never = options[0] === "never"; + const requireReturnForObjectLiteral = + options[1] && options[1].requireReturnForObjectLiteral; + const sourceCode = context.sourceCode; + let funcInfo = null; + + /** + * Checks whether the given node has ASI problem or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if it changes semantics if `;` or `}` followed by the token are removed. + */ + function hasASIProblem(token) { + return ( + token && + token.type === "Punctuator" && + /^[([/`+-]/u.test(token.value) + ); + } + + /** + * Gets the closing parenthesis by the given node. + * @param {ASTNode} node first node after an opening parenthesis. + * @returns {Token} The found closing parenthesis token. + */ + function findClosingParen(node) { + let nodeToCheck = node; + + while (!astUtils.isParenthesised(sourceCode, nodeToCheck)) { + nodeToCheck = nodeToCheck.parent; + } + return sourceCode.getTokenAfter(nodeToCheck); + } + + /** + * Check whether the node is inside of a for loop's init + * @param {ASTNode} node node is inside for loop + * @returns {boolean} `true` if the node is inside of a for loop, else `false` + */ + function isInsideForLoopInitializer(node) { + if (node && node.parent) { + if ( + node.parent.type === "ForStatement" && + node.parent.init === node + ) { + return true; + } + return isInsideForLoopInitializer(node.parent); + } + return false; + } + + /** + * Determines whether a arrow function body needs braces + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function validate(node) { + const arrowBody = node.body; + + if (arrowBody.type === "BlockStatement") { + const blockBody = arrowBody.body; + + if (blockBody.length !== 1 && !never) { + return; + } + + if ( + asNeeded && + requireReturnForObjectLiteral && + blockBody[0].type === "ReturnStatement" && + blockBody[0].argument && + blockBody[0].argument.type === "ObjectExpression" + ) { + return; + } + + if ( + never || + (asNeeded && blockBody[0].type === "ReturnStatement") + ) { + let messageId; + + if (blockBody.length === 0) { + messageId = "unexpectedEmptyBlock"; + } else if ( + blockBody.length > 1 || + blockBody[0].type !== "ReturnStatement" + ) { + messageId = "unexpectedOtherBlock"; + } else if (blockBody[0].argument === null) { + messageId = "unexpectedSingleBlock"; + } else if ( + astUtils.isOpeningBraceToken( + sourceCode.getFirstToken(blockBody[0], { skip: 1 }), + ) + ) { + messageId = "unexpectedObjectBlock"; + } else { + messageId = "unexpectedSingleBlock"; + } + + context.report({ + node, + loc: arrowBody.loc, + messageId, + fix(fixer) { + const fixes = []; + + if ( + blockBody.length !== 1 || + blockBody[0].type !== "ReturnStatement" || + !blockBody[0].argument || + hasASIProblem( + sourceCode.getTokenAfter(arrowBody), + ) + ) { + return fixes; + } + + const openingBrace = + sourceCode.getFirstToken(arrowBody); + const closingBrace = + sourceCode.getLastToken(arrowBody); + const firstValueToken = sourceCode.getFirstToken( + blockBody[0], + 1, + ); + const lastValueToken = sourceCode.getLastToken( + blockBody[0], + ); + const commentsExist = + sourceCode.commentsExistBetween( + openingBrace, + firstValueToken, + ) || + sourceCode.commentsExistBetween( + lastValueToken, + closingBrace, + ); + + /* + * Remove tokens around the return value. + * If comments don't exist, remove extra spaces as well. + */ + if (commentsExist) { + fixes.push( + fixer.remove(openingBrace), + fixer.remove(closingBrace), + fixer.remove( + sourceCode.getTokenAfter(openingBrace), + ), // return keyword + ); + } else { + fixes.push( + fixer.removeRange([ + openingBrace.range[0], + firstValueToken.range[0], + ]), + fixer.removeRange([ + lastValueToken.range[1], + closingBrace.range[1], + ]), + ); + } + + /* + * If the first token of the return value is `{` or the return value is a sequence expression, + * enclose the return value by parentheses to avoid syntax error. + */ + if ( + astUtils.isOpeningBraceToken(firstValueToken) || + blockBody[0].argument.type === + "SequenceExpression" || + (funcInfo.hasInOperator && + isInsideForLoopInitializer(node)) + ) { + if ( + !astUtils.isParenthesised( + sourceCode, + blockBody[0].argument, + ) + ) { + fixes.push( + fixer.insertTextBefore( + firstValueToken, + "(", + ), + fixer.insertTextAfter( + lastValueToken, + ")", + ), + ); + } + } + + /* + * If the last token of the return statement is semicolon, remove it. + * Non-block arrow body is an expression, not a statement. + */ + if (astUtils.isSemicolonToken(lastValueToken)) { + fixes.push(fixer.remove(lastValueToken)); + } + + return fixes; + }, + }); + } + } else { + if ( + always || + (asNeeded && + requireReturnForObjectLiteral && + arrowBody.type === "ObjectExpression") + ) { + context.report({ + node, + loc: arrowBody.loc, + messageId: "expectedBlock", + fix(fixer) { + const fixes = []; + const arrowToken = sourceCode.getTokenBefore( + arrowBody, + astUtils.isArrowToken, + ); + const [ + firstTokenAfterArrow, + secondTokenAfterArrow, + ] = sourceCode.getTokensAfter(arrowToken, { + count: 2, + }); + const lastToken = sourceCode.getLastToken(node); + + let parenthesisedObjectLiteral = null; + + if ( + astUtils.isOpeningParenToken( + firstTokenAfterArrow, + ) && + astUtils.isOpeningBraceToken( + secondTokenAfterArrow, + ) + ) { + const braceNode = + sourceCode.getNodeByRangeIndex( + secondTokenAfterArrow.range[0], + ); + + if (braceNode.type === "ObjectExpression") { + parenthesisedObjectLiteral = braceNode; + } + } + + // If the value is object literal, remove parentheses which were forced by syntax. + if (parenthesisedObjectLiteral) { + const openingParenToken = firstTokenAfterArrow; + const openingBraceToken = secondTokenAfterArrow; + + if ( + astUtils.isTokenOnSameLine( + openingParenToken, + openingBraceToken, + ) + ) { + fixes.push( + fixer.replaceText( + openingParenToken, + "{return ", + ), + ); + } else { + // Avoid ASI + fixes.push( + fixer.replaceText( + openingParenToken, + "{", + ), + fixer.insertTextBefore( + openingBraceToken, + "return ", + ), + ); + } + + // Closing paren for the object doesn't have to be lastToken, e.g.: () => ({}).foo() + fixes.push( + fixer.remove( + findClosingParen( + parenthesisedObjectLiteral, + ), + ), + ); + fixes.push( + fixer.insertTextAfter(lastToken, "}"), + ); + } else { + fixes.push( + fixer.insertTextBefore( + firstTokenAfterArrow, + "{return ", + ), + ); + fixes.push( + fixer.insertTextAfter(lastToken, "}"), + ); + } + + return fixes; + }, + }); + } + } + } + + return { + "BinaryExpression[operator='in']"() { + let info = funcInfo; + + while (info) { + info.hasInOperator = true; + info = info.upper; + } + }, + ArrowFunctionExpression() { + funcInfo = { + upper: funcInfo, + hasInOperator: false, + }; + }, + "ArrowFunctionExpression:exit"(node) { + validate(node); + funcInfo = funcInfo.upper; + }, + }; + }, }; diff --git a/lib/rules/arrow-parens.js b/lib/rules/arrow-parens.js index 2d094162b1e5..833c40d5f66f 100644 --- a/lib/rules/arrow-parens.js +++ b/lib/rules/arrow-parens.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the function has block body. */ function hasBlockBody(node) { - return node.body.type === "BlockStatement"; + return node.body.type === "BlockStatement"; } //------------------------------------------------------------------------------ @@ -30,175 +30,208 @@ function hasBlockBody(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "arrow-parens", - url: "https://eslint.style/rules/js/arrow-parens" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require parentheses around arrow function arguments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/arrow-parens" - }, - - fixable: "code", - - schema: [ - { - enum: ["always", "as-needed"] - }, - { - type: "object", - properties: { - requireForBlockBody: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedParens: "Unexpected parentheses around single function argument.", - expectedParens: "Expected parentheses around arrow function argument.", - - unexpectedParensInline: "Unexpected parentheses around single function argument having a body with no curly braces.", - expectedParensBlock: "Expected parentheses around arrow function argument having a body with curly braces." - } - }, - - create(context) { - const asNeeded = context.options[0] === "as-needed"; - const requireForBlockBody = asNeeded && context.options[1] && context.options[1].requireForBlockBody === true; - - const sourceCode = context.sourceCode; - - /** - * Finds opening paren of parameters for the given arrow function, if it exists. - * It is assumed that the given arrow function has exactly one parameter. - * @param {ASTNode} node `ArrowFunctionExpression` node. - * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters. - */ - function findOpeningParenOfParams(node) { - const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]); - - if ( - tokenBeforeParams && - astUtils.isOpeningParenToken(tokenBeforeParams) && - node.range[0] <= tokenBeforeParams.range[0] - ) { - return tokenBeforeParams; - } - - return null; - } - - /** - * Finds closing paren of parameters for the given arrow function. - * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter. - * @param {ASTNode} node `ArrowFunctionExpression` node. - * @returns {Token} the closing paren of parameters. - */ - function getClosingParenOfParams(node) { - return sourceCode.getTokenAfter(node.params[0], astUtils.isClosingParenToken); - } - - /** - * Determines whether the given arrow function has comments inside parens of parameters. - * It is assumed that the given arrow function has parens of parameters. - * @param {ASTNode} node `ArrowFunctionExpression` node. - * @param {Token} openingParen Opening paren of parameters. - * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters. - */ - function hasCommentsInParensOfParams(node, openingParen) { - return sourceCode.commentsExistBetween(openingParen, getClosingParenOfParams(node)); - } - - /** - * Determines whether the given arrow function has unexpected tokens before opening paren of parameters, - * in which case it will be assumed that the existing parens of parameters are necessary. - * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account. - * Example: (a) => b - * @param {ASTNode} node `ArrowFunctionExpression` node. - * @param {Token} openingParen Opening paren of parameters. - * @returns {boolean} `true` if the function has at least one unexpected token. - */ - function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) { - const expectedCount = node.async ? 1 : 0; - - return sourceCode.getFirstToken(node, { skip: expectedCount }) !== openingParen; - } - - return { - "ArrowFunctionExpression[params.length=1]"(node) { - const shouldHaveParens = !asNeeded || requireForBlockBody && hasBlockBody(node); - const openingParen = findOpeningParenOfParams(node); - const hasParens = openingParen !== null; - const [param] = node.params; - - if (shouldHaveParens && !hasParens) { - context.report({ - node, - messageId: requireForBlockBody ? "expectedParensBlock" : "expectedParens", - loc: param.loc, - *fix(fixer) { - yield fixer.insertTextBefore(param, "("); - yield fixer.insertTextAfter(param, ")"); - } - }); - } - - if ( - !shouldHaveParens && - hasParens && - param.type === "Identifier" && - !param.typeAnnotation && - !node.returnType && - !hasCommentsInParensOfParams(node, openingParen) && - !hasUnexpectedTokensBeforeOpeningParen(node, openingParen) - ) { - context.report({ - node, - messageId: requireForBlockBody ? "unexpectedParensInline" : "unexpectedParens", - loc: param.loc, - *fix(fixer) { - const tokenBeforeOpeningParen = sourceCode.getTokenBefore(openingParen); - const closingParen = getClosingParenOfParams(node); - - if ( - tokenBeforeOpeningParen && - tokenBeforeOpeningParen.range[1] === openingParen.range[0] && - !astUtils.canTokensBeAdjacent(tokenBeforeOpeningParen, sourceCode.getFirstToken(param)) - ) { - yield fixer.insertTextBefore(openingParen, " "); - } - - // remove parens, whitespace inside parens, and possible trailing comma - yield fixer.removeRange([openingParen.range[0], param.range[0]]); - yield fixer.removeRange([param.range[1], closingParen.range[1]]); - } - }); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "arrow-parens", + url: "https://eslint.style/rules/js/arrow-parens", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require parentheses around arrow function arguments", + recommended: false, + url: "https://eslint.org/docs/latest/rules/arrow-parens", + }, + + fixable: "code", + + schema: [ + { + enum: ["always", "as-needed"], + }, + { + type: "object", + properties: { + requireForBlockBody: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedParens: + "Unexpected parentheses around single function argument.", + expectedParens: + "Expected parentheses around arrow function argument.", + + unexpectedParensInline: + "Unexpected parentheses around single function argument having a body with no curly braces.", + expectedParensBlock: + "Expected parentheses around arrow function argument having a body with curly braces.", + }, + }, + + create(context) { + const asNeeded = context.options[0] === "as-needed"; + const requireForBlockBody = + asNeeded && + context.options[1] && + context.options[1].requireForBlockBody === true; + + const sourceCode = context.sourceCode; + + /** + * Finds opening paren of parameters for the given arrow function, if it exists. + * It is assumed that the given arrow function has exactly one parameter. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @returns {Token|null} the opening paren, or `null` if the given arrow function doesn't have parens of parameters. + */ + function findOpeningParenOfParams(node) { + const tokenBeforeParams = sourceCode.getTokenBefore(node.params[0]); + + if ( + tokenBeforeParams && + astUtils.isOpeningParenToken(tokenBeforeParams) && + node.range[0] <= tokenBeforeParams.range[0] + ) { + return tokenBeforeParams; + } + + return null; + } + + /** + * Finds closing paren of parameters for the given arrow function. + * It is assumed that the given arrow function has parens of parameters and that it has exactly one parameter. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @returns {Token} the closing paren of parameters. + */ + function getClosingParenOfParams(node) { + return sourceCode.getTokenAfter( + node.params[0], + astUtils.isClosingParenToken, + ); + } + + /** + * Determines whether the given arrow function has comments inside parens of parameters. + * It is assumed that the given arrow function has parens of parameters. + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @param {Token} openingParen Opening paren of parameters. + * @returns {boolean} `true` if the function has at least one comment inside of parens of parameters. + */ + function hasCommentsInParensOfParams(node, openingParen) { + return sourceCode.commentsExistBetween( + openingParen, + getClosingParenOfParams(node), + ); + } + + /** + * Determines whether the given arrow function has unexpected tokens before opening paren of parameters, + * in which case it will be assumed that the existing parens of parameters are necessary. + * Only tokens within the range of the arrow function (tokens that are part of the arrow function) are taken into account. + * Example: (a) => b + * @param {ASTNode} node `ArrowFunctionExpression` node. + * @param {Token} openingParen Opening paren of parameters. + * @returns {boolean} `true` if the function has at least one unexpected token. + */ + function hasUnexpectedTokensBeforeOpeningParen(node, openingParen) { + const expectedCount = node.async ? 1 : 0; + + return ( + sourceCode.getFirstToken(node, { skip: expectedCount }) !== + openingParen + ); + } + + return { + "ArrowFunctionExpression[params.length=1]"(node) { + const shouldHaveParens = + !asNeeded || (requireForBlockBody && hasBlockBody(node)); + const openingParen = findOpeningParenOfParams(node); + const hasParens = openingParen !== null; + const [param] = node.params; + + if (shouldHaveParens && !hasParens) { + context.report({ + node, + messageId: requireForBlockBody + ? "expectedParensBlock" + : "expectedParens", + loc: param.loc, + *fix(fixer) { + yield fixer.insertTextBefore(param, "("); + yield fixer.insertTextAfter(param, ")"); + }, + }); + } + + if ( + !shouldHaveParens && + hasParens && + param.type === "Identifier" && + !param.typeAnnotation && + !node.returnType && + !hasCommentsInParensOfParams(node, openingParen) && + !hasUnexpectedTokensBeforeOpeningParen(node, openingParen) + ) { + context.report({ + node, + messageId: requireForBlockBody + ? "unexpectedParensInline" + : "unexpectedParens", + loc: param.loc, + *fix(fixer) { + const tokenBeforeOpeningParen = + sourceCode.getTokenBefore(openingParen); + const closingParen = getClosingParenOfParams(node); + + if ( + tokenBeforeOpeningParen && + tokenBeforeOpeningParen.range[1] === + openingParen.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBeforeOpeningParen, + sourceCode.getFirstToken(param), + ) + ) { + yield fixer.insertTextBefore(openingParen, " "); + } + + // remove parens, whitespace inside parens, and possible trailing comma + yield fixer.removeRange([ + openingParen.range[0], + param.range[0], + ]); + yield fixer.removeRange([ + param.range[1], + closingParen.range[1], + ]); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/arrow-spacing.js b/lib/rules/arrow-spacing.js index 93d9763024d8..f8aec901a229 100644 --- a/lib/rules/arrow-spacing.js +++ b/lib/rules/arrow-spacing.js @@ -17,166 +17,172 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "arrow-spacing", - url: "https://eslint.style/rules/js/arrow-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before and after the arrow in arrow functions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/arrow-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - before: { - type: "boolean", - default: true - }, - after: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - messages: { - expectedBefore: "Missing space before =>.", - unexpectedBefore: "Unexpected space before =>.", - - expectedAfter: "Missing space after =>.", - unexpectedAfter: "Unexpected space after =>." - } - }, - - create(context) { - - // merge rules with default - const rule = Object.assign({}, context.options[0]); - - rule.before = rule.before !== false; - rule.after = rule.after !== false; - - const sourceCode = context.sourceCode; - - /** - * Get tokens of arrow(`=>`) and before/after arrow. - * @param {ASTNode} node The arrow function node. - * @returns {Object} Tokens of arrow and before/after arrow. - */ - function getTokens(node) { - const arrow = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); - - return { - before: sourceCode.getTokenBefore(arrow), - arrow, - after: sourceCode.getTokenAfter(arrow) - }; - } - - /** - * Count spaces before/after arrow(`=>`) token. - * @param {Object} tokens Tokens before/after arrow. - * @returns {Object} count of space before/after arrow. - */ - function countSpaces(tokens) { - const before = tokens.arrow.range[0] - tokens.before.range[1]; - const after = tokens.after.range[0] - tokens.arrow.range[1]; - - return { before, after }; - } - - /** - * Determines whether space(s) before after arrow(`=>`) is satisfy rule. - * if before/after value is `true`, there should be space(s). - * if before/after value is `false`, there should be no space. - * @param {ASTNode} node The arrow function node. - * @returns {void} - */ - function spaces(node) { - const tokens = getTokens(node); - const countSpace = countSpaces(tokens); - - if (rule.before) { - - // should be space(s) before arrow - if (countSpace.before === 0) { - context.report({ - node: tokens.before, - messageId: "expectedBefore", - fix(fixer) { - return fixer.insertTextBefore(tokens.arrow, " "); - } - }); - } - } else { - - // should be no space before arrow - if (countSpace.before > 0) { - context.report({ - node: tokens.before, - messageId: "unexpectedBefore", - fix(fixer) { - return fixer.removeRange([tokens.before.range[1], tokens.arrow.range[0]]); - } - }); - } - } - - if (rule.after) { - - // should be space(s) after arrow - if (countSpace.after === 0) { - context.report({ - node: tokens.after, - messageId: "expectedAfter", - fix(fixer) { - return fixer.insertTextAfter(tokens.arrow, " "); - } - }); - } - } else { - - // should be no space after arrow - if (countSpace.after > 0) { - context.report({ - node: tokens.after, - messageId: "unexpectedAfter", - fix(fixer) { - return fixer.removeRange([tokens.arrow.range[1], tokens.after.range[0]]); - } - }); - } - } - } - - return { - ArrowFunctionExpression: spaces - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "arrow-spacing", + url: "https://eslint.style/rules/js/arrow-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing before and after the arrow in arrow functions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/arrow-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: true, + }, + after: { + type: "boolean", + default: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + expectedBefore: "Missing space before =>.", + unexpectedBefore: "Unexpected space before =>.", + + expectedAfter: "Missing space after =>.", + unexpectedAfter: "Unexpected space after =>.", + }, + }, + + create(context) { + // merge rules with default + const rule = Object.assign({}, context.options[0]); + + rule.before = rule.before !== false; + rule.after = rule.after !== false; + + const sourceCode = context.sourceCode; + + /** + * Get tokens of arrow(`=>`) and before/after arrow. + * @param {ASTNode} node The arrow function node. + * @returns {Object} Tokens of arrow and before/after arrow. + */ + function getTokens(node) { + const arrow = sourceCode.getTokenBefore( + node.body, + astUtils.isArrowToken, + ); + + return { + before: sourceCode.getTokenBefore(arrow), + arrow, + after: sourceCode.getTokenAfter(arrow), + }; + } + + /** + * Count spaces before/after arrow(`=>`) token. + * @param {Object} tokens Tokens before/after arrow. + * @returns {Object} count of space before/after arrow. + */ + function countSpaces(tokens) { + const before = tokens.arrow.range[0] - tokens.before.range[1]; + const after = tokens.after.range[0] - tokens.arrow.range[1]; + + return { before, after }; + } + + /** + * Determines whether space(s) before after arrow(`=>`) is satisfy rule. + * if before/after value is `true`, there should be space(s). + * if before/after value is `false`, there should be no space. + * @param {ASTNode} node The arrow function node. + * @returns {void} + */ + function spaces(node) { + const tokens = getTokens(node); + const countSpace = countSpaces(tokens); + + if (rule.before) { + // should be space(s) before arrow + if (countSpace.before === 0) { + context.report({ + node: tokens.before, + messageId: "expectedBefore", + fix(fixer) { + return fixer.insertTextBefore(tokens.arrow, " "); + }, + }); + } + } else { + // should be no space before arrow + if (countSpace.before > 0) { + context.report({ + node: tokens.before, + messageId: "unexpectedBefore", + fix(fixer) { + return fixer.removeRange([ + tokens.before.range[1], + tokens.arrow.range[0], + ]); + }, + }); + } + } + + if (rule.after) { + // should be space(s) after arrow + if (countSpace.after === 0) { + context.report({ + node: tokens.after, + messageId: "expectedAfter", + fix(fixer) { + return fixer.insertTextAfter(tokens.arrow, " "); + }, + }); + } + } else { + // should be no space after arrow + if (countSpace.after > 0) { + context.report({ + node: tokens.after, + messageId: "unexpectedAfter", + fix(fixer) { + return fixer.removeRange([ + tokens.arrow.range[1], + tokens.after.range[0], + ]); + }, + }); + } + } + } + + return { + ArrowFunctionExpression: spaces, + }; + }, }; diff --git a/lib/rules/block-scoped-var.js b/lib/rules/block-scoped-var.js index d65fc074bb17..6b60c57df799 100644 --- a/lib/rules/block-scoped-var.js +++ b/lib/rules/block-scoped-var.js @@ -10,126 +10,128 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce the use of variables within the scope they are defined", - recommended: false, - url: "https://eslint.org/docs/latest/rules/block-scoped-var" - }, - - schema: [], - - messages: { - outOfScope: "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context." - } - }, - - create(context) { - let stack = []; - const sourceCode = context.sourceCode; - - /** - * Makes a block scope. - * @param {ASTNode} node A node of a scope. - * @returns {void} - */ - function enterScope(node) { - stack.push(node.range); - } - - /** - * Pops the last block scope. - * @returns {void} - */ - function exitScope() { - stack.pop(); - } - - /** - * Reports a given reference. - * @param {eslint-scope.Reference} reference A reference to report. - * @param {eslint-scope.Definition} definition A definition for which to report reference. - * @returns {void} - */ - function report(reference, definition) { - const identifier = reference.identifier; - const definitionPosition = definition.name.loc.start; - - context.report({ - node: identifier, - messageId: "outOfScope", - data: { - name: identifier.name, - definitionLine: definitionPosition.line, - definitionColumn: definitionPosition.column + 1 - } - }); - } - - /** - * Finds and reports references which are outside of valid scopes. - * @param {ASTNode} node A node to get variables. - * @returns {void} - */ - function checkForVariables(node) { - if (node.kind !== "var") { - return; - } - - // Defines a predicate to check whether or not a given reference is outside of valid scope. - const scopeRange = stack.at(-1); - - /** - * Check if a reference is out of scope - * @param {ASTNode} reference node to examine - * @returns {boolean} True is its outside the scope - * @private - */ - function isOutsideOfScope(reference) { - const idRange = reference.identifier.range; - - return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; - } - - // Gets declared variables, and checks its references. - const variables = sourceCode.getDeclaredVariables(node); - - for (let i = 0; i < variables.length; ++i) { - - // Reports. - variables[i] - .references - .filter(isOutsideOfScope) - .forEach(ref => report(ref, variables[i].defs.find(def => def.parent === node))); - } - } - - return { - Program(node) { - stack = [node.range]; - }, - - // Manages scopes. - BlockStatement: enterScope, - "BlockStatement:exit": exitScope, - ForStatement: enterScope, - "ForStatement:exit": exitScope, - ForInStatement: enterScope, - "ForInStatement:exit": exitScope, - ForOfStatement: enterScope, - "ForOfStatement:exit": exitScope, - SwitchStatement: enterScope, - "SwitchStatement:exit": exitScope, - CatchClause: enterScope, - "CatchClause:exit": exitScope, - StaticBlock: enterScope, - "StaticBlock:exit": exitScope, - - // Finds and reports references which are outside of valid scope. - VariableDeclaration: checkForVariables - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce the use of variables within the scope they are defined", + recommended: false, + url: "https://eslint.org/docs/latest/rules/block-scoped-var", + }, + + schema: [], + + messages: { + outOfScope: + "'{{name}}' declared on line {{definitionLine}} column {{definitionColumn}} is used outside of binding context.", + }, + }, + + create(context) { + let stack = []; + const sourceCode = context.sourceCode; + + /** + * Makes a block scope. + * @param {ASTNode} node A node of a scope. + * @returns {void} + */ + function enterScope(node) { + stack.push(node.range); + } + + /** + * Pops the last block scope. + * @returns {void} + */ + function exitScope() { + stack.pop(); + } + + /** + * Reports a given reference. + * @param {eslint-scope.Reference} reference A reference to report. + * @param {eslint-scope.Definition} definition A definition for which to report reference. + * @returns {void} + */ + function report(reference, definition) { + const identifier = reference.identifier; + const definitionPosition = definition.name.loc.start; + + context.report({ + node: identifier, + messageId: "outOfScope", + data: { + name: identifier.name, + definitionLine: definitionPosition.line, + definitionColumn: definitionPosition.column + 1, + }, + }); + } + + /** + * Finds and reports references which are outside of valid scopes. + * @param {ASTNode} node A node to get variables. + * @returns {void} + */ + function checkForVariables(node) { + if (node.kind !== "var") { + return; + } + + // Defines a predicate to check whether or not a given reference is outside of valid scope. + const scopeRange = stack.at(-1); + + /** + * Check if a reference is out of scope + * @param {ASTNode} reference node to examine + * @returns {boolean} True is its outside the scope + * @private + */ + function isOutsideOfScope(reference) { + const idRange = reference.identifier.range; + + return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; + } + + // Gets declared variables, and checks its references. + const variables = sourceCode.getDeclaredVariables(node); + + for (let i = 0; i < variables.length; ++i) { + // Reports. + variables[i].references.filter(isOutsideOfScope).forEach(ref => + report( + ref, + variables[i].defs.find(def => def.parent === node), + ), + ); + } + } + + return { + Program(node) { + stack = [node.range]; + }, + + // Manages scopes. + BlockStatement: enterScope, + "BlockStatement:exit": exitScope, + ForStatement: enterScope, + "ForStatement:exit": exitScope, + ForInStatement: enterScope, + "ForInStatement:exit": exitScope, + ForOfStatement: enterScope, + "ForOfStatement:exit": exitScope, + SwitchStatement: enterScope, + "SwitchStatement:exit": exitScope, + CatchClause: enterScope, + "CatchClause:exit": exitScope, + StaticBlock: enterScope, + "StaticBlock:exit": exitScope, + + // Finds and reports references which are outside of valid scope. + VariableDeclaration: checkForVariables, + }; + }, }; diff --git a/lib/rules/block-spacing.js b/lib/rules/block-spacing.js index 98e80bb38d85..a8f1048979ca 100644 --- a/lib/rules/block-spacing.js +++ b/lib/rules/block-spacing.js @@ -14,179 +14,189 @@ const util = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "block-spacing", - url: "https://eslint.style/rules/js/block-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow or enforce spaces inside of blocks after opening block and before closing block", - recommended: false, - url: "https://eslint.org/docs/latest/rules/block-spacing" - }, - - fixable: "whitespace", - - schema: [ - { enum: ["always", "never"] } - ], - - messages: { - missing: "Requires a space {{location}} '{{token}}'.", - extra: "Unexpected space(s) {{location}} '{{token}}'." - } - }, - - create(context) { - const always = (context.options[0] !== "never"), - messageId = always ? "missing" : "extra", - sourceCode = context.sourceCode; - - /** - * Gets the open brace token from a given node. - * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get. - * @returns {Token} The token of the open brace. - */ - function getOpenBrace(node) { - if (node.type === "SwitchStatement") { - if (node.cases.length > 0) { - return sourceCode.getTokenBefore(node.cases[0]); - } - return sourceCode.getLastToken(node, 1); - } - - if (node.type === "StaticBlock") { - return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token - } - - // "BlockStatement" - return sourceCode.getFirstToken(node); - } - - /** - * Checks whether or not: - * - given tokens are on same line. - * - there is/isn't a space between given tokens. - * @param {Token} left A token to check. - * @param {Token} right The token which is next to `left`. - * @returns {boolean} - * When the option is `"always"`, `true` if there are one or more spaces between given tokens. - * When the option is `"never"`, `true` if there are not any spaces between given tokens. - * If given tokens are not on same line, it's always `true`. - */ - function isValid(left, right) { - return ( - !util.isTokenOnSameLine(left, right) || - sourceCode.isSpaceBetweenTokens(left, right) === always - ); - } - - /** - * Checks and reports invalid spacing style inside braces. - * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check. - * @returns {void} - */ - function checkSpacingInsideBraces(node) { - - // Gets braces and the first/last token of content. - const openBrace = getOpenBrace(node); - const closeBrace = sourceCode.getLastToken(node); - const firstToken = sourceCode.getTokenAfter(openBrace, { includeComments: true }); - const lastToken = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); - - // Skip if the node is invalid or empty. - if (openBrace.type !== "Punctuator" || - openBrace.value !== "{" || - closeBrace.type !== "Punctuator" || - closeBrace.value !== "}" || - firstToken === closeBrace - ) { - return; - } - - // Skip line comments for option never - if (!always && firstToken.type === "Line") { - return; - } - - // Check. - if (!isValid(openBrace, firstToken)) { - let loc = openBrace.loc; - - if (messageId === "extra") { - loc = { - start: openBrace.loc.end, - end: firstToken.loc.start - }; - } - - context.report({ - node, - loc, - messageId, - data: { - location: "after", - token: openBrace.value - }, - fix(fixer) { - if (always) { - return fixer.insertTextBefore(firstToken, " "); - } - - return fixer.removeRange([openBrace.range[1], firstToken.range[0]]); - } - }); - } - if (!isValid(lastToken, closeBrace)) { - let loc = closeBrace.loc; - - if (messageId === "extra") { - loc = { - start: lastToken.loc.end, - end: closeBrace.loc.start - }; - } - context.report({ - node, - loc, - messageId, - data: { - location: "before", - token: closeBrace.value - }, - fix(fixer) { - if (always) { - return fixer.insertTextAfter(lastToken, " "); - } - - return fixer.removeRange([lastToken.range[1], closeBrace.range[0]]); - } - }); - } - } - - return { - BlockStatement: checkSpacingInsideBraces, - StaticBlock: checkSpacingInsideBraces, - SwitchStatement: checkSpacingInsideBraces - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "block-spacing", + url: "https://eslint.style/rules/js/block-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Disallow or enforce spaces inside of blocks after opening block and before closing block", + recommended: false, + url: "https://eslint.org/docs/latest/rules/block-spacing", + }, + + fixable: "whitespace", + + schema: [{ enum: ["always", "never"] }], + + messages: { + missing: "Requires a space {{location}} '{{token}}'.", + extra: "Unexpected space(s) {{location}} '{{token}}'.", + }, + }, + + create(context) { + const always = context.options[0] !== "never", + messageId = always ? "missing" : "extra", + sourceCode = context.sourceCode; + + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to get. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + if (node.cases.length > 0) { + return sourceCode.getTokenBefore(node.cases[0]); + } + return sourceCode.getLastToken(node, 1); + } + + if (node.type === "StaticBlock") { + return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + } + + // "BlockStatement" + return sourceCode.getFirstToken(node); + } + + /** + * Checks whether or not: + * - given tokens are on same line. + * - there is/isn't a space between given tokens. + * @param {Token} left A token to check. + * @param {Token} right The token which is next to `left`. + * @returns {boolean} + * When the option is `"always"`, `true` if there are one or more spaces between given tokens. + * When the option is `"never"`, `true` if there are not any spaces between given tokens. + * If given tokens are not on same line, it's always `true`. + */ + function isValid(left, right) { + return ( + !util.isTokenOnSameLine(left, right) || + sourceCode.isSpaceBetweenTokens(left, right) === always + ); + } + + /** + * Checks and reports invalid spacing style inside braces. + * @param {ASTNode} node A BlockStatement/StaticBlock/SwitchStatement node to check. + * @returns {void} + */ + function checkSpacingInsideBraces(node) { + // Gets braces and the first/last token of content. + const openBrace = getOpenBrace(node); + const closeBrace = sourceCode.getLastToken(node); + const firstToken = sourceCode.getTokenAfter(openBrace, { + includeComments: true, + }); + const lastToken = sourceCode.getTokenBefore(closeBrace, { + includeComments: true, + }); + + // Skip if the node is invalid or empty. + if ( + openBrace.type !== "Punctuator" || + openBrace.value !== "{" || + closeBrace.type !== "Punctuator" || + closeBrace.value !== "}" || + firstToken === closeBrace + ) { + return; + } + + // Skip line comments for option never + if (!always && firstToken.type === "Line") { + return; + } + + // Check. + if (!isValid(openBrace, firstToken)) { + let loc = openBrace.loc; + + if (messageId === "extra") { + loc = { + start: openBrace.loc.end, + end: firstToken.loc.start, + }; + } + + context.report({ + node, + loc, + messageId, + data: { + location: "after", + token: openBrace.value, + }, + fix(fixer) { + if (always) { + return fixer.insertTextBefore(firstToken, " "); + } + + return fixer.removeRange([ + openBrace.range[1], + firstToken.range[0], + ]); + }, + }); + } + if (!isValid(lastToken, closeBrace)) { + let loc = closeBrace.loc; + + if (messageId === "extra") { + loc = { + start: lastToken.loc.end, + end: closeBrace.loc.start, + }; + } + context.report({ + node, + loc, + messageId, + data: { + location: "before", + token: closeBrace.value, + }, + fix(fixer) { + if (always) { + return fixer.insertTextAfter(lastToken, " "); + } + + return fixer.removeRange([ + lastToken.range[1], + closeBrace.range[0], + ]); + }, + }); + } + } + + return { + BlockStatement: checkSpacingInsideBraces, + StaticBlock: checkSpacingInsideBraces, + SwitchStatement: checkSpacingInsideBraces, + }; + }, }; diff --git a/lib/rules/brace-style.js b/lib/rules/brace-style.js index f4fc0fd72b40..40b4ae31bc11 100644 --- a/lib/rules/brace-style.js +++ b/lib/rules/brace-style.js @@ -14,202 +14,265 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "brace-style", - url: "https://eslint.style/rules/js/brace-style" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent brace style for blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/brace-style" - }, - - schema: [ - { - enum: ["1tbs", "stroustrup", "allman"] - }, - { - type: "object", - properties: { - allowSingleLine: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - fixable: "whitespace", - - messages: { - nextLineOpen: "Opening curly brace does not appear on the same line as controlling statement.", - sameLineOpen: "Opening curly brace appears on the same line as controlling statement.", - blockSameLine: "Statement inside of curly braces should be on next line.", - nextLineClose: "Closing curly brace does not appear on the same line as the subsequent block.", - singleLineClose: "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", - sameLineClose: "Closing curly brace appears on the same line as the subsequent block." - } - }, - - create(context) { - const style = context.options[0] || "1tbs", - params = context.options[1] || {}, - sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Fixes a place where a newline unexpectedly appears - * @param {Token} firstToken The token before the unexpected newline - * @param {Token} secondToken The token after the unexpected newline - * @returns {Function} A fixer function to remove the newlines between the tokens - */ - function removeNewlineBetween(firstToken, secondToken) { - const textRange = [firstToken.range[1], secondToken.range[0]]; - const textBetween = sourceCode.text.slice(textRange[0], textRange[1]); - - // Don't do a fix if there is a comment between the tokens - if (textBetween.trim()) { - return null; - } - return fixer => fixer.replaceTextRange(textRange, " "); - } - - /** - * Validates a pair of curly brackets based on the user's config - * @param {Token} openingCurly The opening curly bracket - * @param {Token} closingCurly The closing curly bracket - * @returns {void} - */ - function validateCurlyPair(openingCurly, closingCurly) { - const tokenBeforeOpeningCurly = sourceCode.getTokenBefore(openingCurly); - const tokenAfterOpeningCurly = sourceCode.getTokenAfter(openingCurly); - const tokenBeforeClosingCurly = sourceCode.getTokenBefore(closingCurly); - const singleLineException = params.allowSingleLine && astUtils.isTokenOnSameLine(openingCurly, closingCurly); - - if (style !== "allman" && !astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly)) { - context.report({ - node: openingCurly, - messageId: "nextLineOpen", - fix: removeNewlineBetween(tokenBeforeOpeningCurly, openingCurly) - }); - } - - if (style === "allman" && astUtils.isTokenOnSameLine(tokenBeforeOpeningCurly, openingCurly) && !singleLineException) { - context.report({ - node: openingCurly, - messageId: "sameLineOpen", - fix: fixer => fixer.insertTextBefore(openingCurly, "\n") - }); - } - - if (astUtils.isTokenOnSameLine(openingCurly, tokenAfterOpeningCurly) && tokenAfterOpeningCurly !== closingCurly && !singleLineException) { - context.report({ - node: openingCurly, - messageId: "blockSameLine", - fix: fixer => fixer.insertTextAfter(openingCurly, "\n") - }); - } - - if (tokenBeforeClosingCurly !== openingCurly && !singleLineException && astUtils.isTokenOnSameLine(tokenBeforeClosingCurly, closingCurly)) { - context.report({ - node: closingCurly, - messageId: "singleLineClose", - fix: fixer => fixer.insertTextBefore(closingCurly, "\n") - }); - } - } - - /** - * Validates the location of a token that appears before a keyword (e.g. a newline before `else`) - * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`). - * @returns {void} - */ - function validateCurlyBeforeKeyword(curlyToken) { - const keywordToken = sourceCode.getTokenAfter(curlyToken); - - if (style === "1tbs" && !astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { - context.report({ - node: curlyToken, - messageId: "nextLineClose", - fix: removeNewlineBetween(curlyToken, keywordToken) - }); - } - - if (style !== "1tbs" && astUtils.isTokenOnSameLine(curlyToken, keywordToken)) { - context.report({ - node: curlyToken, - messageId: "sameLineClose", - fix: fixer => fixer.insertTextAfter(curlyToken, "\n") - }); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - BlockStatement(node) { - if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { - validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); - } - }, - StaticBlock(node) { - validateCurlyPair( - sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token - sourceCode.getLastToken(node) - ); - }, - ClassBody(node) { - validateCurlyPair(sourceCode.getFirstToken(node), sourceCode.getLastToken(node)); - }, - SwitchStatement(node) { - const closingCurly = sourceCode.getLastToken(node); - const openingCurly = sourceCode.getTokenBefore(node.cases.length ? node.cases[0] : closingCurly); - - validateCurlyPair(openingCurly, closingCurly); - }, - IfStatement(node) { - if (node.consequent.type === "BlockStatement" && node.alternate) { - - // Handle the keyword after the `if` block (before `else`) - validateCurlyBeforeKeyword(sourceCode.getLastToken(node.consequent)); - } - }, - TryStatement(node) { - - // Handle the keyword after the `try` block (before `catch` or `finally`) - validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block)); - - if (node.handler && node.finalizer) { - - // Handle the keyword after the `catch` block (before `finally`) - validateCurlyBeforeKeyword(sourceCode.getLastToken(node.handler.body)); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "brace-style", + url: "https://eslint.style/rules/js/brace-style", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent brace style for blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/brace-style", + }, + + schema: [ + { + enum: ["1tbs", "stroustrup", "allman"], + }, + { + type: "object", + properties: { + allowSingleLine: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + fixable: "whitespace", + + messages: { + nextLineOpen: + "Opening curly brace does not appear on the same line as controlling statement.", + sameLineOpen: + "Opening curly brace appears on the same line as controlling statement.", + blockSameLine: + "Statement inside of curly braces should be on next line.", + nextLineClose: + "Closing curly brace does not appear on the same line as the subsequent block.", + singleLineClose: + "Closing curly brace should be on the same line as opening curly brace or on the line after the previous block.", + sameLineClose: + "Closing curly brace appears on the same line as the subsequent block.", + }, + }, + + create(context) { + const style = context.options[0] || "1tbs", + params = context.options[1] || {}, + sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Fixes a place where a newline unexpectedly appears + * @param {Token} firstToken The token before the unexpected newline + * @param {Token} secondToken The token after the unexpected newline + * @returns {Function} A fixer function to remove the newlines between the tokens + */ + function removeNewlineBetween(firstToken, secondToken) { + const textRange = [firstToken.range[1], secondToken.range[0]]; + const textBetween = sourceCode.text.slice( + textRange[0], + textRange[1], + ); + + // Don't do a fix if there is a comment between the tokens + if (textBetween.trim()) { + return null; + } + return fixer => fixer.replaceTextRange(textRange, " "); + } + + /** + * Validates a pair of curly brackets based on the user's config + * @param {Token} openingCurly The opening curly bracket + * @param {Token} closingCurly The closing curly bracket + * @returns {void} + */ + function validateCurlyPair(openingCurly, closingCurly) { + const tokenBeforeOpeningCurly = + sourceCode.getTokenBefore(openingCurly); + const tokenAfterOpeningCurly = + sourceCode.getTokenAfter(openingCurly); + const tokenBeforeClosingCurly = + sourceCode.getTokenBefore(closingCurly); + const singleLineException = + params.allowSingleLine && + astUtils.isTokenOnSameLine(openingCurly, closingCurly); + + if ( + style !== "allman" && + !astUtils.isTokenOnSameLine( + tokenBeforeOpeningCurly, + openingCurly, + ) + ) { + context.report({ + node: openingCurly, + messageId: "nextLineOpen", + fix: removeNewlineBetween( + tokenBeforeOpeningCurly, + openingCurly, + ), + }); + } + + if ( + style === "allman" && + astUtils.isTokenOnSameLine( + tokenBeforeOpeningCurly, + openingCurly, + ) && + !singleLineException + ) { + context.report({ + node: openingCurly, + messageId: "sameLineOpen", + fix: fixer => fixer.insertTextBefore(openingCurly, "\n"), + }); + } + + if ( + astUtils.isTokenOnSameLine( + openingCurly, + tokenAfterOpeningCurly, + ) && + tokenAfterOpeningCurly !== closingCurly && + !singleLineException + ) { + context.report({ + node: openingCurly, + messageId: "blockSameLine", + fix: fixer => fixer.insertTextAfter(openingCurly, "\n"), + }); + } + + if ( + tokenBeforeClosingCurly !== openingCurly && + !singleLineException && + astUtils.isTokenOnSameLine( + tokenBeforeClosingCurly, + closingCurly, + ) + ) { + context.report({ + node: closingCurly, + messageId: "singleLineClose", + fix: fixer => fixer.insertTextBefore(closingCurly, "\n"), + }); + } + } + + /** + * Validates the location of a token that appears before a keyword (e.g. a newline before `else`) + * @param {Token} curlyToken The closing curly token. This is assumed to precede a keyword token (such as `else` or `finally`). + * @returns {void} + */ + function validateCurlyBeforeKeyword(curlyToken) { + const keywordToken = sourceCode.getTokenAfter(curlyToken); + + if ( + style === "1tbs" && + !astUtils.isTokenOnSameLine(curlyToken, keywordToken) + ) { + context.report({ + node: curlyToken, + messageId: "nextLineClose", + fix: removeNewlineBetween(curlyToken, keywordToken), + }); + } + + if ( + style !== "1tbs" && + astUtils.isTokenOnSameLine(curlyToken, keywordToken) + ) { + context.report({ + node: curlyToken, + messageId: "sameLineClose", + fix: fixer => fixer.insertTextAfter(curlyToken, "\n"), + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + BlockStatement(node) { + if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { + validateCurlyPair( + sourceCode.getFirstToken(node), + sourceCode.getLastToken(node), + ); + } + }, + StaticBlock(node) { + validateCurlyPair( + sourceCode.getFirstToken(node, { skip: 1 }), // skip the `static` token + sourceCode.getLastToken(node), + ); + }, + ClassBody(node) { + validateCurlyPair( + sourceCode.getFirstToken(node), + sourceCode.getLastToken(node), + ); + }, + SwitchStatement(node) { + const closingCurly = sourceCode.getLastToken(node); + const openingCurly = sourceCode.getTokenBefore( + node.cases.length ? node.cases[0] : closingCurly, + ); + + validateCurlyPair(openingCurly, closingCurly); + }, + IfStatement(node) { + if ( + node.consequent.type === "BlockStatement" && + node.alternate + ) { + // Handle the keyword after the `if` block (before `else`) + validateCurlyBeforeKeyword( + sourceCode.getLastToken(node.consequent), + ); + } + }, + TryStatement(node) { + // Handle the keyword after the `try` block (before `catch` or `finally`) + validateCurlyBeforeKeyword(sourceCode.getLastToken(node.block)); + + if (node.handler && node.finalizer) { + // Handle the keyword after the `catch` block (before `finally`) + validateCurlyBeforeKeyword( + sourceCode.getLastToken(node.handler.body), + ); + } + }, + }; + }, }; diff --git a/lib/rules/callback-return.js b/lib/rules/callback-return.js index 4f4579a2281b..6f64cc769c25 100644 --- a/lib/rules/callback-return.js +++ b/lib/rules/callback-return.js @@ -11,193 +11,206 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - recommended: false, - url: "https://eslint.org/docs/latest/rules/callback-return" - }, - - schema: [{ - type: "array", - items: { type: "string" } - }], - - messages: { - missingReturn: "Expected return with your callback function." - } - }, - - create(context) { - - const callbacks = context.options[0] || ["callback", "cb", "next"], - sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Find the closest parent matching a list of types. - * @param {ASTNode} node The node whose parents we are searching - * @param {Array} types The node types to match - * @returns {ASTNode} The matched node or undefined. - */ - function findClosestParentOfType(node, types) { - if (!node.parent) { - return null; - } - if (!types.includes(node.parent.type)) { - return findClosestParentOfType(node.parent, types); - } - return node.parent; - } - - /** - * Check to see if a node contains only identifiers - * @param {ASTNode} node The node to check - * @returns {boolean} Whether or not the node contains only identifiers - */ - function containsOnlyIdentifiers(node) { - if (node.type === "Identifier") { - return true; - } - - if (node.type === "MemberExpression") { - if (node.object.type === "Identifier") { - return true; - } - if (node.object.type === "MemberExpression") { - return containsOnlyIdentifiers(node.object); - } - } - - return false; - } - - /** - * Check to see if a CallExpression is in our callback list. - * @param {ASTNode} node The node to check against our callback names list. - * @returns {boolean} Whether or not this function matches our callback name. - */ - function isCallback(node) { - return containsOnlyIdentifiers(node.callee) && callbacks.includes(sourceCode.getText(node.callee)); - } - - /** - * Determines whether or not the callback is part of a callback expression. - * @param {ASTNode} node The callback node - * @param {ASTNode} parentNode The expression node - * @returns {boolean} Whether or not this is part of a callback expression - */ - function isCallbackExpression(node, parentNode) { - - // ensure the parent node exists and is an expression - if (!parentNode || parentNode.type !== "ExpressionStatement") { - return false; - } - - // cb() - if (parentNode.expression === node) { - return true; - } - - // special case for cb && cb() and similar - if (parentNode.expression.type === "BinaryExpression" || parentNode.expression.type === "LogicalExpression") { - if (parentNode.expression.right === node) { - return true; - } - } - - return false; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - CallExpression(node) { - - // if we're not a callback we can return - if (!isCallback(node)) { - return; - } - - // find the closest block, return or loop - const closestBlock = findClosestParentOfType(node, ["BlockStatement", "ReturnStatement", "ArrowFunctionExpression"]) || {}; - - // if our parent is a return we know we're ok - if (closestBlock.type === "ReturnStatement") { - return; - } - - // arrow functions don't always have blocks and implicitly return - if (closestBlock.type === "ArrowFunctionExpression") { - return; - } - - // block statements are part of functions and most if statements - if (closestBlock.type === "BlockStatement") { - - // find the last item in the block - const lastItem = closestBlock.body.at(-1); - - // if the callback is the last thing in a block that might be ok - if (isCallbackExpression(node, lastItem)) { - - const parentType = closestBlock.parent.type; - - // but only if the block is part of a function - if (parentType === "FunctionExpression" || - parentType === "FunctionDeclaration" || - parentType === "ArrowFunctionExpression" - ) { - return; - } - - } - - // ending a block with a return is also ok - if (lastItem.type === "ReturnStatement") { - - // but only if the callback is immediately before - if (isCallbackExpression(node, closestBlock.body.at(-2))) { - return; - } - } - - } - - // as long as you're the child of a function at this point you should be asked to return - if (findClosestParentOfType(node, ["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"])) { - context.report({ node, messageId: "missingReturn" }); - } - - } - - }; - } + meta: { + 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", + recommended: false, + url: "https://eslint.org/docs/latest/rules/callback-return", + }, + + schema: [ + { + type: "array", + items: { type: "string" }, + }, + ], + + messages: { + missingReturn: "Expected return with your callback function.", + }, + }, + + create(context) { + const callbacks = context.options[0] || ["callback", "cb", "next"], + sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Find the closest parent matching a list of types. + * @param {ASTNode} node The node whose parents we are searching + * @param {Array} types The node types to match + * @returns {ASTNode} The matched node or undefined. + */ + function findClosestParentOfType(node, types) { + if (!node.parent) { + return null; + } + if (!types.includes(node.parent.type)) { + return findClosestParentOfType(node.parent, types); + } + return node.parent; + } + + /** + * Check to see if a node contains only identifiers + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the node contains only identifiers + */ + function containsOnlyIdentifiers(node) { + if (node.type === "Identifier") { + return true; + } + + if (node.type === "MemberExpression") { + if (node.object.type === "Identifier") { + return true; + } + if (node.object.type === "MemberExpression") { + return containsOnlyIdentifiers(node.object); + } + } + + return false; + } + + /** + * Check to see if a CallExpression is in our callback list. + * @param {ASTNode} node The node to check against our callback names list. + * @returns {boolean} Whether or not this function matches our callback name. + */ + function isCallback(node) { + return ( + containsOnlyIdentifiers(node.callee) && + callbacks.includes(sourceCode.getText(node.callee)) + ); + } + + /** + * Determines whether or not the callback is part of a callback expression. + * @param {ASTNode} node The callback node + * @param {ASTNode} parentNode The expression node + * @returns {boolean} Whether or not this is part of a callback expression + */ + function isCallbackExpression(node, parentNode) { + // ensure the parent node exists and is an expression + if (!parentNode || parentNode.type !== "ExpressionStatement") { + return false; + } + + // cb() + if (parentNode.expression === node) { + return true; + } + + // special case for cb && cb() and similar + if ( + parentNode.expression.type === "BinaryExpression" || + parentNode.expression.type === "LogicalExpression" + ) { + if (parentNode.expression.right === node) { + return true; + } + } + + return false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + CallExpression(node) { + // if we're not a callback we can return + if (!isCallback(node)) { + return; + } + + // find the closest block, return or loop + const closestBlock = + findClosestParentOfType(node, [ + "BlockStatement", + "ReturnStatement", + "ArrowFunctionExpression", + ]) || {}; + + // if our parent is a return we know we're ok + if (closestBlock.type === "ReturnStatement") { + return; + } + + // arrow functions don't always have blocks and implicitly return + if (closestBlock.type === "ArrowFunctionExpression") { + return; + } + + // block statements are part of functions and most if statements + if (closestBlock.type === "BlockStatement") { + // find the last item in the block + const lastItem = closestBlock.body.at(-1); + + // if the callback is the last thing in a block that might be ok + if (isCallbackExpression(node, lastItem)) { + const parentType = closestBlock.parent.type; + + // but only if the block is part of a function + if ( + parentType === "FunctionExpression" || + parentType === "FunctionDeclaration" || + parentType === "ArrowFunctionExpression" + ) { + return; + } + } + + // ending a block with a return is also ok + if (lastItem.type === "ReturnStatement") { + // but only if the callback is immediately before + if ( + isCallbackExpression(node, closestBlock.body.at(-2)) + ) { + return; + } + } + } + + // as long as you're the child of a function at this point you should be asked to return + if ( + findClosestParentOfType(node, [ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + ]) + ) { + context.report({ node, messageId: "missingReturn" }); + } + }, + }; + }, }; diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index 7bc75ea5e64e..2d56d7d99f6a 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -17,395 +17,406 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - ignoreDestructuring: { - type: "boolean" - }, - ignoreImports: { - type: "boolean" - }, - ignoreGlobals: { - type: "boolean" - }, - properties: { - enum: ["always", "never"] - }, - allow: { - type: "array", - items: { - type: "string" - }, - minItems: 0, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - messages: { - notCamelCase: "Identifier '{{name}}' is not in camel case.", - notCamelCasePrivate: "#{{name}} is not in camel case." - } - }, - - create(context) { - const [{ - allow, - ignoreDestructuring, - ignoreGlobals, - ignoreImports, - properties - }] = context.options; - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reported = new Set(); - - /** - * Checks if a string contains an underscore and isn't all upper-case - * @param {string} name The string to check. - * @returns {boolean} if the string is underscored - * @private - */ - function isUnderscored(name) { - const nameBody = name.replace(/^_+|_+$/gu, ""); - - // if there's an underscore, it might be A_CONSTANT, which is okay - return nameBody.includes("_") && nameBody !== nameBody.toUpperCase(); - } - - /** - * Checks if a string match the ignore list - * @param {string} name The string to check. - * @returns {boolean} if the string is ignored - * @private - */ - function isAllowed(name) { - return allow.some( - entry => name === entry || name.match(new RegExp(entry, "u")) - ); - } - - /** - * Checks if a given name is good or not. - * @param {string} name The name to check. - * @returns {boolean} `true` if the name is good. - * @private - */ - function isGoodName(name) { - return !isUnderscored(name) || isAllowed(name); - } - - /** - * Checks if a given identifier reference or member expression is an assignment - * target. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if the node is an assignment target. - */ - function isAssignmentTarget(node) { - const parent = node.parent; - - switch (parent.type) { - case "AssignmentExpression": - case "AssignmentPattern": - return parent.left === node; - - case "Property": - return ( - parent.parent.type === "ObjectPattern" && - parent.value === node - ); - case "ArrayPattern": - case "RestElement": - return true; - - default: - return false; - } - } - - /** - * Checks if a given binding identifier uses the original name as-is. - * - If it's in object destructuring or object expression, the original name is its property name. - * - If it's in import declaration, the original name is its exported name. - * @param {ASTNode} node The `Identifier` node to check. - * @returns {boolean} `true` if the identifier uses the original name as-is. - */ - function equalsToOriginalName(node) { - const localName = node.name; - const valueNode = node.parent.type === "AssignmentPattern" - ? node.parent - : node; - const parent = valueNode.parent; - - switch (parent.type) { - case "Property": - return ( - (parent.parent.type === "ObjectPattern" || parent.parent.type === "ObjectExpression") && - parent.value === valueNode && - !parent.computed && - parent.key.type === "Identifier" && - parent.key.name === localName - ); - - case "ImportSpecifier": - return ( - parent.local === node && - astUtils.getModuleExportName(parent.imported) === localName - ); - - default: - return false; - } - } - - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - if (reported.has(node.range[0])) { - return; - } - reported.add(node.range[0]); - - // Report it. - context.report({ - node, - messageId: node.type === "PrivateIdentifier" - ? "notCamelCasePrivate" - : "notCamelCase", - data: { name: node.name } - }); - } - - /** - * Reports an identifier reference or a binding identifier. - * @param {ASTNode} node The `Identifier` node to report. - * @returns {void} - */ - function reportReferenceId(node) { - - /* - * For backward compatibility, if it's in callings then ignore it. - * Not sure why it is. - */ - if ( - node.parent.type === "CallExpression" || - node.parent.type === "NewExpression" - ) { - return; - } - - /* - * For backward compatibility, if it's a default value of - * destructuring/parameters then ignore it. - * Not sure why it is. - */ - if ( - node.parent.type === "AssignmentPattern" && - node.parent.right === node - ) { - return; - } - - /* - * The `ignoreDestructuring` flag skips the identifiers that uses - * the property name as-is. - */ - if (ignoreDestructuring && equalsToOriginalName(node)) { - return; - } - - /* - * Import attribute keys are always ignored - */ - if (astUtils.isImportAttributeKey(node)) { - return; - } - - report(node); - } - - return { - - // Report camelcase of global variable references ------------------ - Program(node) { - const scope = sourceCode.getScope(node); - - if (!ignoreGlobals) { - - // Defined globals in config files or directive comments. - for (const variable of scope.variables) { - if ( - variable.identifiers.length > 0 || - isGoodName(variable.name) - ) { - continue; - } - for (const reference of variable.references) { - - /* - * For backward compatibility, this rule reports read-only - * references as well. - */ - reportReferenceId(reference.identifier); - } - } - } - - // Undefined globals. - for (const reference of scope.through) { - const id = reference.identifier; - - if (isGoodName(id.name) || astUtils.isImportAttributeKey(id)) { - continue; - } - - /* - * For backward compatibility, this rule reports read-only - * references as well. - */ - reportReferenceId(id); - } - }, - - // Report camelcase of declared variables -------------------------- - [[ - "VariableDeclaration", - "FunctionDeclaration", - "FunctionExpression", - "ArrowFunctionExpression", - "ClassDeclaration", - "ClassExpression", - "CatchClause" - ]](node) { - for (const variable of sourceCode.getDeclaredVariables(node)) { - if (isGoodName(variable.name)) { - continue; - } - const id = variable.identifiers[0]; - - // Report declaration. - if (!(ignoreDestructuring && equalsToOriginalName(id))) { - report(id); - } - - /* - * For backward compatibility, report references as well. - * It looks unnecessary because declarations are reported. - */ - for (const reference of variable.references) { - if (reference.init) { - continue; // Skip the write references of initializers. - } - reportReferenceId(reference.identifier); - } - } - }, - - // Report camelcase in properties ---------------------------------- - [[ - "ObjectExpression > Property[computed!=true] > Identifier.key", - "MethodDefinition[computed!=true] > Identifier.key", - "PropertyDefinition[computed!=true] > Identifier.key", - "MethodDefinition > PrivateIdentifier.key", - "PropertyDefinition > PrivateIdentifier.key" - ]](node) { - if (properties === "never" || astUtils.isImportAttributeKey(node) || isGoodName(node.name)) { - return; - } - report(node); - }, - "MemberExpression[computed!=true] > Identifier.property"(node) { - if ( - properties === "never" || - !isAssignmentTarget(node.parent) || // ← ignore read-only references. - isGoodName(node.name) - ) { - return; - } - report(node); - }, - - // Report camelcase in import -------------------------------------- - ImportDeclaration(node) { - for (const variable of sourceCode.getDeclaredVariables(node)) { - if (isGoodName(variable.name)) { - continue; - } - const id = variable.identifiers[0]; - - // Report declaration. - if (!(ignoreImports && equalsToOriginalName(id))) { - report(id); - } - - /* - * For backward compatibility, report references as well. - * It looks unnecessary because declarations are reported. - */ - for (const reference of variable.references) { - reportReferenceId(reference.identifier); - } - } - }, - - // Report camelcase in re-export ----------------------------------- - [[ - "ExportAllDeclaration > Identifier.exported", - "ExportSpecifier > Identifier.exported" - ]](node) { - if (isGoodName(node.name)) { - return; - } - report(node); - }, - - // Report camelcase in labels -------------------------------------- - [[ - "LabeledStatement > Identifier.label", - - /* - * For backward compatibility, report references as well. - * It looks unnecessary because declarations are reported. - */ - "BreakStatement > Identifier.label", - "ContinueStatement > Identifier.label" - ]](node) { - if (isGoodName(node.name)) { - return; - } - report(node); - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + ignoreDestructuring: { + type: "boolean", + }, + ignoreImports: { + type: "boolean", + }, + ignoreGlobals: { + type: "boolean", + }, + properties: { + enum: ["always", "never"], + }, + allow: { + type: "array", + items: { + type: "string", + }, + minItems: 0, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + notCamelCase: "Identifier '{{name}}' is not in camel case.", + notCamelCasePrivate: "#{{name}} is not in camel case.", + }, + }, + + create(context) { + const [ + { + allow, + ignoreDestructuring, + ignoreGlobals, + ignoreImports, + properties, + }, + ] = context.options; + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // contains reported nodes to avoid reporting twice on destructuring with shorthand notation + const reported = new Set(); + + /** + * Checks if a string contains an underscore and isn't all upper-case + * @param {string} name The string to check. + * @returns {boolean} if the string is underscored + * @private + */ + function isUnderscored(name) { + const nameBody = name.replace(/^_+|_+$/gu, ""); + + // if there's an underscore, it might be A_CONSTANT, which is okay + return ( + nameBody.includes("_") && nameBody !== nameBody.toUpperCase() + ); + } + + /** + * Checks if a string match the ignore list + * @param {string} name The string to check. + * @returns {boolean} if the string is ignored + * @private + */ + function isAllowed(name) { + return allow.some( + entry => name === entry || name.match(new RegExp(entry, "u")), + ); + } + + /** + * Checks if a given name is good or not. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is good. + * @private + */ + function isGoodName(name) { + return !isUnderscored(name) || isAllowed(name); + } + + /** + * Checks if a given identifier reference or member expression is an assignment + * target. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is an assignment target. + */ + function isAssignmentTarget(node) { + const parent = node.parent; + + switch (parent.type) { + case "AssignmentExpression": + case "AssignmentPattern": + return parent.left === node; + + case "Property": + return ( + parent.parent.type === "ObjectPattern" && + parent.value === node + ); + case "ArrayPattern": + case "RestElement": + return true; + + default: + return false; + } + } + + /** + * Checks if a given binding identifier uses the original name as-is. + * - If it's in object destructuring or object expression, the original name is its property name. + * - If it's in import declaration, the original name is its exported name. + * @param {ASTNode} node The `Identifier` node to check. + * @returns {boolean} `true` if the identifier uses the original name as-is. + */ + function equalsToOriginalName(node) { + const localName = node.name; + const valueNode = + node.parent.type === "AssignmentPattern" ? node.parent : node; + const parent = valueNode.parent; + + switch (parent.type) { + case "Property": + return ( + (parent.parent.type === "ObjectPattern" || + parent.parent.type === "ObjectExpression") && + parent.value === valueNode && + !parent.computed && + parent.key.type === "Identifier" && + parent.key.name === localName + ); + + case "ImportSpecifier": + return ( + parent.local === node && + astUtils.getModuleExportName(parent.imported) === + localName + ); + + default: + return false; + } + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + if (reported.has(node.range[0])) { + return; + } + reported.add(node.range[0]); + + // Report it. + context.report({ + node, + messageId: + node.type === "PrivateIdentifier" + ? "notCamelCasePrivate" + : "notCamelCase", + data: { name: node.name }, + }); + } + + /** + * Reports an identifier reference or a binding identifier. + * @param {ASTNode} node The `Identifier` node to report. + * @returns {void} + */ + function reportReferenceId(node) { + /* + * For backward compatibility, if it's in callings then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "CallExpression" || + node.parent.type === "NewExpression" + ) { + return; + } + + /* + * For backward compatibility, if it's a default value of + * destructuring/parameters then ignore it. + * Not sure why it is. + */ + if ( + node.parent.type === "AssignmentPattern" && + node.parent.right === node + ) { + return; + } + + /* + * The `ignoreDestructuring` flag skips the identifiers that uses + * the property name as-is. + */ + if (ignoreDestructuring && equalsToOriginalName(node)) { + return; + } + + /* + * Import attribute keys are always ignored + */ + if (astUtils.isImportAttributeKey(node)) { + return; + } + + report(node); + } + + return { + // Report camelcase of global variable references ------------------ + Program(node) { + const scope = sourceCode.getScope(node); + + if (!ignoreGlobals) { + // Defined globals in config files or directive comments. + for (const variable of scope.variables) { + if ( + variable.identifiers.length > 0 || + isGoodName(variable.name) + ) { + continue; + } + for (const reference of variable.references) { + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(reference.identifier); + } + } + } + + // Undefined globals. + for (const reference of scope.through) { + const id = reference.identifier; + + if ( + isGoodName(id.name) || + astUtils.isImportAttributeKey(id) + ) { + continue; + } + + /* + * For backward compatibility, this rule reports read-only + * references as well. + */ + reportReferenceId(id); + } + }, + + // Report camelcase of declared variables -------------------------- + [[ + "VariableDeclaration", + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + "ClassDeclaration", + "ClassExpression", + "CatchClause", + ]](node) { + for (const variable of sourceCode.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; + } + const id = variable.identifiers[0]; + + // Report declaration. + if (!(ignoreDestructuring && equalsToOriginalName(id))) { + report(id); + } + + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + if (reference.init) { + continue; // Skip the write references of initializers. + } + reportReferenceId(reference.identifier); + } + } + }, + + // Report camelcase in properties ---------------------------------- + [[ + "ObjectExpression > Property[computed!=true] > Identifier.key", + "MethodDefinition[computed!=true] > Identifier.key", + "PropertyDefinition[computed!=true] > Identifier.key", + "MethodDefinition > PrivateIdentifier.key", + "PropertyDefinition > PrivateIdentifier.key", + ]](node) { + if ( + properties === "never" || + astUtils.isImportAttributeKey(node) || + isGoodName(node.name) + ) { + return; + } + report(node); + }, + "MemberExpression[computed!=true] > Identifier.property"(node) { + if ( + properties === "never" || + !isAssignmentTarget(node.parent) || // ← ignore read-only references. + isGoodName(node.name) + ) { + return; + } + report(node); + }, + + // Report camelcase in import -------------------------------------- + ImportDeclaration(node) { + for (const variable of sourceCode.getDeclaredVariables(node)) { + if (isGoodName(variable.name)) { + continue; + } + const id = variable.identifiers[0]; + + // Report declaration. + if (!(ignoreImports && equalsToOriginalName(id))) { + report(id); + } + + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + for (const reference of variable.references) { + reportReferenceId(reference.identifier); + } + } + }, + + // Report camelcase in re-export ----------------------------------- + [[ + "ExportAllDeclaration > Identifier.exported", + "ExportSpecifier > Identifier.exported", + ]](node) { + if (isGoodName(node.name)) { + return; + } + report(node); + }, + + // Report camelcase in labels -------------------------------------- + [[ + "LabeledStatement > Identifier.label", + + /* + * For backward compatibility, report references as well. + * It looks unnecessary because declarations are reported. + */ + "BreakStatement > Identifier.label", + "ContinueStatement > Identifier.label", + ]](node) { + if (isGoodName(node.name)) { + return; + } + report(node); + }, + }; + }, }; diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js index 79646363d5eb..72125858e736 100644 --- a/lib/rules/capitalized-comments.js +++ b/lib/rules/capitalized-comments.js @@ -15,9 +15,9 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, - WHITESPACE = /\s/gu, - MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u, // TODO: Combine w/ max-len pattern? - LETTER_PATTERN = /\p{L}/u; + WHITESPACE = /\s/gu, + MAYBE_URL = /^\s*[^:/?#\s]+:\/\/[^?#]/u, // TODO: Combine w/ max-len pattern? + LETTER_PATTERN = /\p{L}/u; /* * Base schema body for defining the basic capitalization rule, ignorePattern, @@ -25,24 +25,24 @@ const DEFAULT_IGNORE_PATTERN = astUtils.COMMENTS_IGNORE_PATTERN, * This can be used in a few different ways in the actual schema. */ const SCHEMA_BODY = { - type: "object", - properties: { - ignorePattern: { - type: "string" - }, - ignoreInlineComments: { - type: "boolean" - }, - ignoreConsecutiveComments: { - type: "boolean" - } - }, - additionalProperties: false + type: "object", + properties: { + ignorePattern: { + type: "string", + }, + ignoreInlineComments: { + type: "boolean", + }, + ignoreConsecutiveComments: { + type: "boolean", + }, + }, + additionalProperties: false, }; const DEFAULTS = { - ignorePattern: "", - ignoreInlineComments: false, - ignoreConsecutiveComments: false + ignorePattern: "", + ignoreInlineComments: false, + ignoreConsecutiveComments: false, }; /** @@ -59,7 +59,7 @@ const DEFAULTS = { * @returns {Object} The normalized options. */ function getNormalizedOptions(rawOptions, which) { - return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); + return Object.assign({}, DEFAULTS, rawOptions[which] || rawOptions); } /** @@ -69,10 +69,10 @@ function getNormalizedOptions(rawOptions, which) { * normalized options objects. */ function getAllNormalizedOptions(rawOptions = {}) { - return { - Line: getNormalizedOptions(rawOptions, "line"), - Block: getNormalizedOptions(rawOptions, "block") - }; + return { + Line: getNormalizedOptions(rawOptions, "line"), + Block: getNormalizedOptions(rawOptions, "block"), + }; } /** @@ -84,15 +84,15 @@ function getAllNormalizedOptions(rawOptions = {}) { * @returns {void} */ function createRegExpForIgnorePatterns(normalizedOptions) { - Object.keys(normalizedOptions).forEach(key => { - const ignorePatternStr = normalizedOptions[key].ignorePattern; + Object.keys(normalizedOptions).forEach(key => { + const ignorePatternStr = normalizedOptions[key].ignorePattern; - if (ignorePatternStr) { - const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); + if (ignorePatternStr) { + const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); - normalizedOptions[key].ignorePatternRegExp = regExp; - } - }); + normalizedOptions[key].ignorePatternRegExp = regExp; + } + }); } //------------------------------------------------------------------------------ @@ -101,204 +101,225 @@ function createRegExpForIgnorePatterns(normalizedOptions) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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", - - schema: [ - { enum: ["always", "never"] }, - { - oneOf: [ - SCHEMA_BODY, - { - type: "object", - properties: { - line: SCHEMA_BODY, - block: SCHEMA_BODY - }, - additionalProperties: false - } - ] - } - ], - - messages: { - unexpectedLowercaseComment: "Comments should not begin with a lowercase character.", - unexpectedUppercaseComment: "Comments should not begin with an uppercase character." - } - }, - - create(context) { - - const capitalize = context.options[0] || "always", - normalizedOptions = getAllNormalizedOptions(context.options[1]), - sourceCode = context.sourceCode; - - createRegExpForIgnorePatterns(normalizedOptions); - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Checks whether a comment is an inline comment. - * - * For the purpose of this rule, a comment is inline if: - * 1. The comment is preceded by a token on the same line; and - * 2. The command is followed by a token on the same line. - * - * Note that the comment itself need not be single-line! - * - * Also, it follows from this definition that only block comments can - * be considered as possibly inline. This is because line comments - * would consume any following tokens on the same line as the comment. - * @param {ASTNode} comment The comment node to check. - * @returns {boolean} True if the comment is an inline comment, false - * otherwise. - */ - function isInlineComment(comment) { - const previousToken = sourceCode.getTokenBefore(comment, { includeComments: true }), - nextToken = sourceCode.getTokenAfter(comment, { includeComments: true }); - - return Boolean( - previousToken && - nextToken && - comment.loc.start.line === previousToken.loc.end.line && - comment.loc.end.line === nextToken.loc.start.line - ); - } - - /** - * Determine if a comment follows another comment. - * @param {ASTNode} comment The comment to check. - * @returns {boolean} True if the comment follows a valid comment. - */ - function isConsecutiveComment(comment) { - const previousTokenOrComment = sourceCode.getTokenBefore(comment, { includeComments: true }); - - return Boolean( - previousTokenOrComment && - ["Block", "Line"].includes(previousTokenOrComment.type) - ); - } - - /** - * Check a comment to determine if it is valid for this rule. - * @param {ASTNode} comment The comment node to process. - * @param {Object} options The options for checking this comment. - * @returns {boolean} True if the comment is valid, false otherwise. - */ - function isCommentValid(comment, options) { - - // 1. Check for default ignore pattern. - if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { - return true; - } - - // 2. Check for custom ignore pattern. - const commentWithoutAsterisks = comment.value - .replace(/\*/gu, ""); - - if (options.ignorePatternRegExp && options.ignorePatternRegExp.test(commentWithoutAsterisks)) { - return true; - } - - // 3. Check for inline comments. - if (options.ignoreInlineComments && isInlineComment(comment)) { - return true; - } - - // 4. Is this a consecutive comment (and are we tolerating those)? - if (options.ignoreConsecutiveComments && isConsecutiveComment(comment)) { - return true; - } - - // 5. Does the comment start with a possible URL? - if (MAYBE_URL.test(commentWithoutAsterisks)) { - return true; - } - - // 6. Is the initial word character a letter? - const commentWordCharsOnly = commentWithoutAsterisks - .replace(WHITESPACE, ""); - - if (commentWordCharsOnly.length === 0) { - return true; - } - - // Get the first Unicode character (1 or 2 code units). - const [firstWordChar] = commentWordCharsOnly; - - if (!LETTER_PATTERN.test(firstWordChar)) { - return true; - } - - // 7. Check the case of the initial word character. - const isUppercase = firstWordChar !== firstWordChar.toLocaleLowerCase(), - isLowercase = firstWordChar !== firstWordChar.toLocaleUpperCase(); - - if (capitalize === "always" && isLowercase) { - return false; - } - if (capitalize === "never" && isUppercase) { - return false; - } - - return true; - } - - /** - * Process a comment to determine if it needs to be reported. - * @param {ASTNode} comment The comment node to process. - * @returns {void} - */ - function processComment(comment) { - const options = normalizedOptions[comment.type], - commentValid = isCommentValid(comment, options); - - if (!commentValid) { - const messageId = capitalize === "always" - ? "unexpectedLowercaseComment" - : "unexpectedUppercaseComment"; - - context.report({ - node: null, // Intentionally using loc instead - loc: comment.loc, - messageId, - fix(fixer) { - const match = comment.value.match(LETTER_PATTERN); - const char = match[0]; - - // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*) - const charIndex = comment.range[0] + match.index + 2; - - return fixer.replaceTextRange( - [charIndex, charIndex + char.length], - capitalize === "always" ? char.toLocaleUpperCase() : char.toLocaleLowerCase() - ); - } - }); - } - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - Program() { - const comments = sourceCode.getAllComments(); - - comments.filter(token => token.type !== "Shebang").forEach(processComment); - } - }; - } + meta: { + type: "suggestion", + + 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", + + schema: [ + { enum: ["always", "never"] }, + { + oneOf: [ + SCHEMA_BODY, + { + type: "object", + properties: { + line: SCHEMA_BODY, + block: SCHEMA_BODY, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + unexpectedLowercaseComment: + "Comments should not begin with a lowercase character.", + unexpectedUppercaseComment: + "Comments should not begin with an uppercase character.", + }, + }, + + create(context) { + const capitalize = context.options[0] || "always", + normalizedOptions = getAllNormalizedOptions(context.options[1]), + sourceCode = context.sourceCode; + + createRegExpForIgnorePatterns(normalizedOptions); + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks whether a comment is an inline comment. + * + * For the purpose of this rule, a comment is inline if: + * 1. The comment is preceded by a token on the same line; and + * 2. The command is followed by a token on the same line. + * + * Note that the comment itself need not be single-line! + * + * Also, it follows from this definition that only block comments can + * be considered as possibly inline. This is because line comments + * would consume any following tokens on the same line as the comment. + * @param {ASTNode} comment The comment node to check. + * @returns {boolean} True if the comment is an inline comment, false + * otherwise. + */ + function isInlineComment(comment) { + const previousToken = sourceCode.getTokenBefore(comment, { + includeComments: true, + }), + nextToken = sourceCode.getTokenAfter(comment, { + includeComments: true, + }); + + return Boolean( + previousToken && + nextToken && + comment.loc.start.line === previousToken.loc.end.line && + comment.loc.end.line === nextToken.loc.start.line, + ); + } + + /** + * Determine if a comment follows another comment. + * @param {ASTNode} comment The comment to check. + * @returns {boolean} True if the comment follows a valid comment. + */ + function isConsecutiveComment(comment) { + const previousTokenOrComment = sourceCode.getTokenBefore(comment, { + includeComments: true, + }); + + return Boolean( + previousTokenOrComment && + ["Block", "Line"].includes(previousTokenOrComment.type), + ); + } + + /** + * Check a comment to determine if it is valid for this rule. + * @param {ASTNode} comment The comment node to process. + * @param {Object} options The options for checking this comment. + * @returns {boolean} True if the comment is valid, false otherwise. + */ + function isCommentValid(comment, options) { + // 1. Check for default ignore pattern. + if (DEFAULT_IGNORE_PATTERN.test(comment.value)) { + return true; + } + + // 2. Check for custom ignore pattern. + const commentWithoutAsterisks = comment.value.replace(/\*/gu, ""); + + if ( + options.ignorePatternRegExp && + options.ignorePatternRegExp.test(commentWithoutAsterisks) + ) { + return true; + } + + // 3. Check for inline comments. + if (options.ignoreInlineComments && isInlineComment(comment)) { + return true; + } + + // 4. Is this a consecutive comment (and are we tolerating those)? + if ( + options.ignoreConsecutiveComments && + isConsecutiveComment(comment) + ) { + return true; + } + + // 5. Does the comment start with a possible URL? + if (MAYBE_URL.test(commentWithoutAsterisks)) { + return true; + } + + // 6. Is the initial word character a letter? + const commentWordCharsOnly = commentWithoutAsterisks.replace( + WHITESPACE, + "", + ); + + if (commentWordCharsOnly.length === 0) { + return true; + } + + // Get the first Unicode character (1 or 2 code units). + const [firstWordChar] = commentWordCharsOnly; + + if (!LETTER_PATTERN.test(firstWordChar)) { + return true; + } + + // 7. Check the case of the initial word character. + const isUppercase = + firstWordChar !== firstWordChar.toLocaleLowerCase(), + isLowercase = + firstWordChar !== firstWordChar.toLocaleUpperCase(); + + if (capitalize === "always" && isLowercase) { + return false; + } + if (capitalize === "never" && isUppercase) { + return false; + } + + return true; + } + + /** + * Process a comment to determine if it needs to be reported. + * @param {ASTNode} comment The comment node to process. + * @returns {void} + */ + function processComment(comment) { + const options = normalizedOptions[comment.type], + commentValid = isCommentValid(comment, options); + + if (!commentValid) { + const messageId = + capitalize === "always" + ? "unexpectedLowercaseComment" + : "unexpectedUppercaseComment"; + + context.report({ + node: null, // Intentionally using loc instead + loc: comment.loc, + messageId, + fix(fixer) { + const match = comment.value.match(LETTER_PATTERN); + const char = match[0]; + + // Offset match.index by 2 to account for the first 2 characters that start the comment (// or /*) + const charIndex = comment.range[0] + match.index + 2; + + return fixer.replaceTextRange( + [charIndex, charIndex + char.length], + capitalize === "always" + ? char.toLocaleUpperCase() + : char.toLocaleLowerCase(), + ); + }, + }); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments + .filter(token => token.type !== "Shebang") + .forEach(processComment); + }, + }; + }, }; diff --git a/lib/rules/class-methods-use-this.js b/lib/rules/class-methods-use-this.js index 110546303280..f32dddf293fe 100644 --- a/lib/rules/class-methods-use-this.js +++ b/lib/rules/class-methods-use-this.js @@ -17,175 +17,183 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - enforceForClassFields: true, - exceptMethods: [] - }], - - docs: { - description: "Enforce that class methods utilize `this`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/class-methods-use-this" - }, - - schema: [{ - type: "object", - properties: { - exceptMethods: { - type: "array", - items: { - type: "string" - } - }, - enforceForClassFields: { - type: "boolean" - } - }, - additionalProperties: false - }], - - messages: { - missingThis: "Expected 'this' to be used by class {{name}}." - } - }, - create(context) { - const [options] = context.options; - const { enforceForClassFields } = options; - const exceptMethods = new Set(options.exceptMethods); - - const stack = []; - - /** - * Push `this` used flag initialized with `false` onto the stack. - * @returns {void} - */ - function pushContext() { - stack.push(false); - } - - /** - * Pop `this` used flag from the stack. - * @returns {boolean | undefined} `this` used flag - */ - function popContext() { - return stack.pop(); - } - - /** - * Initializes the current context to false and pushes it onto the stack. - * These booleans represent whether 'this' has been used in the context. - * @returns {void} - * @private - */ - function enterFunction() { - pushContext(); - } - - /** - * Check if the node is an instance method - * @param {ASTNode} node node to check - * @returns {boolean} True if its an instance method - * @private - */ - function isInstanceMethod(node) { - switch (node.type) { - case "MethodDefinition": - return !node.static && node.kind !== "constructor"; - case "PropertyDefinition": - return !node.static && enforceForClassFields; - default: - return false; - } - } - - /** - * Check if the node is an instance method not excluded by config - * @param {ASTNode} node node to check - * @returns {boolean} True if it is an instance method, and not excluded by config - * @private - */ - function isIncludedInstanceMethod(node) { - if (isInstanceMethod(node)) { - if (node.computed) { - return true; - } - - const hashIfNeeded = node.key.type === "PrivateIdentifier" ? "#" : ""; - const name = node.key.type === "Literal" - ? astUtils.getStaticStringValue(node.key) - : (node.key.name || ""); - - return !exceptMethods.has(hashIfNeeded + name); - } - return false; - } - - /** - * Checks if we are leaving a function that is a method, and reports if 'this' has not been used. - * Static methods and the constructor are exempt. - * Then pops the context off the stack. - * @param {ASTNode} node A function node that was entered. - * @returns {void} - * @private - */ - function exitFunction(node) { - const methodUsesThis = popContext(); - - if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { - context.report({ - node, - loc: astUtils.getFunctionHeadLoc(node, context.sourceCode), - messageId: "missingThis", - data: { - name: astUtils.getFunctionNameWithKind(node) - } - }); - } - } - - /** - * Mark the current context as having used 'this'. - * @returns {void} - * @private - */ - function markThisUsed() { - if (stack.length) { - stack[stack.length - 1] = true; - } - } - - return { - FunctionDeclaration: enterFunction, - "FunctionDeclaration:exit": exitFunction, - FunctionExpression: enterFunction, - "FunctionExpression:exit": exitFunction, - - /* - * Class field value are implicit functions. - */ - "PropertyDefinition > *.key:exit": pushContext, - "PropertyDefinition:exit": popContext, - - /* - * Class static blocks are implicit functions. They aren't required to use `this`, - * but we have to push context so that it captures any use of `this` in the static block - * separately from enclosing contexts, because static blocks have their own `this` and it - * shouldn't count as used `this` in enclosing contexts. - */ - StaticBlock: pushContext, - "StaticBlock:exit": popContext, - - ThisExpression: markThisUsed, - Super: markThisUsed, - ...( - enforceForClassFields && { - "PropertyDefinition > ArrowFunctionExpression.value": enterFunction, - "PropertyDefinition > ArrowFunctionExpression.value:exit": exitFunction - } - ) - }; - } + meta: { + dialects: ["javascript", "typescript"], + language: "javascript", + type: "suggestion", + + defaultOptions: [ + { + enforceForClassFields: true, + exceptMethods: [], + }, + ], + + docs: { + description: "Enforce that class methods utilize `this`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/class-methods-use-this", + }, + + schema: [ + { + type: "object", + properties: { + exceptMethods: { + type: "array", + items: { + type: "string", + }, + }, + enforceForClassFields: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missingThis: "Expected 'this' to be used by class {{name}}.", + }, + }, + create(context) { + const [options] = context.options; + const { enforceForClassFields } = options; + const exceptMethods = new Set(options.exceptMethods); + + const stack = []; + + /** + * Push `this` used flag initialized with `false` onto the stack. + * @returns {void} + */ + function pushContext() { + stack.push(false); + } + + /** + * Pop `this` used flag from the stack. + * @returns {boolean | undefined} `this` used flag + */ + function popContext() { + return stack.pop(); + } + + /** + * Initializes the current context to false and pushes it onto the stack. + * These booleans represent whether 'this' has been used in the context. + * @returns {void} + * @private + */ + function enterFunction() { + pushContext(); + } + + /** + * Check if the node is an instance method + * @param {ASTNode} node node to check + * @returns {boolean} True if its an instance method + * @private + */ + function isInstanceMethod(node) { + switch (node.type) { + case "MethodDefinition": + return !node.static && node.kind !== "constructor"; + case "PropertyDefinition": + return !node.static && enforceForClassFields; + default: + return false; + } + } + + /** + * Check if the node is an instance method not excluded by config + * @param {ASTNode} node node to check + * @returns {boolean} True if it is an instance method, and not excluded by config + * @private + */ + function isIncludedInstanceMethod(node) { + if (isInstanceMethod(node)) { + if (node.computed) { + return true; + } + + const hashIfNeeded = + node.key.type === "PrivateIdentifier" ? "#" : ""; + const name = + node.key.type === "Literal" + ? astUtils.getStaticStringValue(node.key) + : node.key.name || ""; + + return !exceptMethods.has(hashIfNeeded + name); + } + return false; + } + + /** + * Checks if we are leaving a function that is a method, and reports if 'this' has not been used. + * Static methods and the constructor are exempt. + * Then pops the context off the stack. + * @param {ASTNode} node A function node that was entered. + * @returns {void} + * @private + */ + function exitFunction(node) { + const methodUsesThis = popContext(); + + if (isIncludedInstanceMethod(node.parent) && !methodUsesThis) { + context.report({ + node, + loc: astUtils.getFunctionHeadLoc(node, context.sourceCode), + messageId: "missingThis", + data: { + name: astUtils.getFunctionNameWithKind(node), + }, + }); + } + } + + /** + * Mark the current context as having used 'this'. + * @returns {void} + * @private + */ + function markThisUsed() { + if (stack.length) { + stack[stack.length - 1] = true; + } + } + + return { + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + + /* + * Class field value are implicit functions. + */ + "PropertyDefinition > *.key:exit": pushContext, + "PropertyDefinition:exit": popContext, + + /* + * Class static blocks are implicit functions. They aren't required to use `this`, + * but we have to push context so that it captures any use of `this` in the static block + * separately from enclosing contexts, because static blocks have their own `this` and it + * shouldn't count as used `this` in enclosing contexts. + */ + StaticBlock: pushContext, + "StaticBlock:exit": popContext, + + ThisExpression: markThisUsed, + Super: markThisUsed, + ...(enforceForClassFields && { + "PropertyDefinition > ArrowFunctionExpression.value": + enterFunction, + "PropertyDefinition > ArrowFunctionExpression.value:exit": + exitFunction, + }), + }; + }, }; diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js index 9af73792aa03..5f8f015b4208 100644 --- a/lib/rules/comma-dangle.js +++ b/lib/rules/comma-dangle.js @@ -17,11 +17,11 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const DEFAULT_OPTIONS = Object.freeze({ - arrays: "never", - objects: "never", - imports: "never", - exports: "never", - functions: "never" + arrays: "never", + objects: "never", + imports: "never", + exports: "never", + functions: "never", }); /** @@ -31,11 +31,11 @@ const DEFAULT_OPTIONS = Object.freeze({ * @returns {boolean} `true` if a trailing comma is allowed. */ function isTrailingCommaAllowed(lastItem) { - return !( - lastItem.type === "RestElement" || - lastItem.type === "RestProperty" || - lastItem.type === "ExperimentalRestProperty" - ); + return !( + lastItem.type === "RestElement" || + lastItem.type === "RestProperty" || + lastItem.type === "ExperimentalRestProperty" + ); } /** @@ -45,26 +45,26 @@ function isTrailingCommaAllowed(lastItem) { * @returns {Object} The normalized option value. */ function normalizeOptions(optionValue, ecmaVersion) { - if (typeof optionValue === "string") { - return { - arrays: optionValue, - objects: optionValue, - imports: optionValue, - exports: optionValue, - functions: ecmaVersion < 2017 ? "ignore" : optionValue - }; - } - if (typeof optionValue === "object" && optionValue !== null) { - return { - arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, - objects: optionValue.objects || DEFAULT_OPTIONS.objects, - imports: optionValue.imports || DEFAULT_OPTIONS.imports, - exports: optionValue.exports || DEFAULT_OPTIONS.exports, - functions: optionValue.functions || DEFAULT_OPTIONS.functions - }; - } - - return DEFAULT_OPTIONS; + if (typeof optionValue === "string") { + return { + arrays: optionValue, + objects: optionValue, + imports: optionValue, + exports: optionValue, + functions: ecmaVersion < 2017 ? "ignore" : optionValue, + }; + } + if (typeof optionValue === "object" && optionValue !== null) { + return { + arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays, + objects: optionValue.objects || DEFAULT_OPTIONS.objects, + imports: optionValue.imports || DEFAULT_OPTIONS.imports, + exports: optionValue.exports || DEFAULT_OPTIONS.exports, + functions: optionValue.functions || DEFAULT_OPTIONS.functions, + }; + } + + return DEFAULT_OPTIONS; } //------------------------------------------------------------------------------ @@ -73,319 +73,352 @@ function normalizeOptions(optionValue, ecmaVersion) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "comma-dangle", - url: "https://eslint.style/rules/js/comma-dangle" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow trailing commas", - recommended: false, - url: "https://eslint.org/docs/latest/rules/comma-dangle" - }, - - fixable: "code", - - schema: { - definitions: { - value: { - enum: [ - "always-multiline", - "always", - "never", - "only-multiline" - ] - }, - valueWithIgnore: { - enum: [ - "always-multiline", - "always", - "ignore", - "never", - "only-multiline" - ] - } - }, - type: "array", - items: [ - { - oneOf: [ - { - $ref: "#/definitions/value" - }, - { - type: "object", - properties: { - arrays: { $ref: "#/definitions/valueWithIgnore" }, - objects: { $ref: "#/definitions/valueWithIgnore" }, - imports: { $ref: "#/definitions/valueWithIgnore" }, - exports: { $ref: "#/definitions/valueWithIgnore" }, - functions: { $ref: "#/definitions/valueWithIgnore" } - }, - additionalProperties: false - } - ] - } - ], - additionalItems: false - }, - - messages: { - unexpected: "Unexpected trailing comma.", - missing: "Missing trailing comma." - } - }, - - create(context) { - const options = normalizeOptions(context.options[0], context.languageOptions.ecmaVersion); - - const sourceCode = context.sourceCode; - - /** - * Gets the last item of the given node. - * @param {ASTNode} node The node to get. - * @returns {ASTNode|null} The last node or null. - */ - function getLastItem(node) { - - /** - * Returns the last element of an array - * @param {any[]} array The input array - * @returns {any} The last element - */ - function last(array) { - return array.at(-1); - } - - switch (node.type) { - case "ObjectExpression": - case "ObjectPattern": - return last(node.properties); - case "ArrayExpression": - case "ArrayPattern": - return last(node.elements); - case "ImportDeclaration": - case "ExportNamedDeclaration": - return last(node.specifiers); - case "FunctionDeclaration": - case "FunctionExpression": - case "ArrowFunctionExpression": - return last(node.params); - case "CallExpression": - case "NewExpression": - return last(node.arguments); - default: - return null; - } - } - - /** - * Gets the trailing comma token of the given node. - * If the trailing comma does not exist, this returns the token which is - * the insertion point of the trailing comma token. - * @param {ASTNode} node The node to get. - * @param {ASTNode} lastItem The last item of the node. - * @returns {Token} The trailing comma token or the insertion point. - */ - function getTrailingToken(node, lastItem) { - switch (node.type) { - case "ObjectExpression": - case "ArrayExpression": - case "CallExpression": - case "NewExpression": - return sourceCode.getLastToken(node, 1); - default: { - const nextToken = sourceCode.getTokenAfter(lastItem); - - if (astUtils.isCommaToken(nextToken)) { - return nextToken; - } - return sourceCode.getLastToken(lastItem); - } - } - } - - /** - * Checks whether or not a given node is multiline. - * This rule handles a given node as multiline when the closing parenthesis - * and the last element are not on the same line. - * @param {ASTNode} node A node to check. - * @returns {boolean} `true` if the node is multiline. - */ - function isMultiline(node) { - const lastItem = getLastItem(node); - - if (!lastItem) { - return false; - } - - const penultimateToken = getTrailingToken(node, lastItem); - const lastToken = sourceCode.getTokenAfter(penultimateToken); - - return lastToken.loc.end.line !== penultimateToken.loc.end.line; - } - - /** - * Reports a trailing comma if it exists. - * @param {ASTNode} node A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forbidTrailingComma(node) { - const lastItem = getLastItem(node); - - if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { - return; - } - - const trailingToken = getTrailingToken(node, lastItem); - - if (astUtils.isCommaToken(trailingToken)) { - context.report({ - node: lastItem, - loc: trailingToken.loc, - messageId: "unexpected", - *fix(fixer) { - yield fixer.remove(trailingToken); - - /* - * Extend the range of the fix to include surrounding tokens to ensure - * that the element after which the comma is removed stays _last_. - * This intentionally makes conflicts in fix ranges with rules that may be - * adding or removing elements in the same autofix pass. - * https://github.com/eslint/eslint/issues/15660 - */ - yield fixer.insertTextBefore(sourceCode.getTokenBefore(trailingToken), ""); - yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); - } - }); - } - } - - /** - * Reports the last element of a given node if it does not have a trailing - * comma. - * - * If a given node is `ArrayPattern` which has `RestElement`, the trailing - * comma is disallowed, so report if it exists. - * @param {ASTNode} node A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forceTrailingComma(node) { - const lastItem = getLastItem(node); - - if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) { - return; - } - if (!isTrailingCommaAllowed(lastItem)) { - forbidTrailingComma(node); - return; - } - - const trailingToken = getTrailingToken(node, lastItem); - - if (trailingToken.value !== ",") { - context.report({ - node: lastItem, - loc: { - start: trailingToken.loc.end, - end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end) - }, - messageId: "missing", - *fix(fixer) { - yield fixer.insertTextAfter(trailingToken, ","); - - /* - * Extend the range of the fix to include surrounding tokens to ensure - * that the element after which the comma is inserted stays _last_. - * This intentionally makes conflicts in fix ranges with rules that may be - * adding or removing elements in the same autofix pass. - * https://github.com/eslint/eslint/issues/15660 - */ - yield fixer.insertTextBefore(trailingToken, ""); - yield fixer.insertTextAfter(sourceCode.getTokenAfter(trailingToken), ""); - } - }); - } - } - - /** - * If a given node is multiline, reports the last element of a given node - * when it does not have a trailing comma. - * Otherwise, reports a trailing comma if it exists. - * @param {ASTNode} node A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function forceTrailingCommaIfMultiline(node) { - if (isMultiline(node)) { - forceTrailingComma(node); - } else { - forbidTrailingComma(node); - } - } - - /** - * Only if a given node is not multiline, reports the last element of a given node - * when it does not have a trailing comma. - * Otherwise, reports a trailing comma if it exists. - * @param {ASTNode} node A node to check. Its type is one of - * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, - * ImportDeclaration, and ExportNamedDeclaration. - * @returns {void} - */ - function allowTrailingCommaIfMultiline(node) { - if (!isMultiline(node)) { - forbidTrailingComma(node); - } - } - - const predicate = { - always: forceTrailingComma, - "always-multiline": forceTrailingCommaIfMultiline, - "only-multiline": allowTrailingCommaIfMultiline, - never: forbidTrailingComma, - ignore() {} - }; - - return { - ObjectExpression: predicate[options.objects], - ObjectPattern: predicate[options.objects], - - ArrayExpression: predicate[options.arrays], - ArrayPattern: predicate[options.arrays], - - ImportDeclaration: predicate[options.imports], - - ExportNamedDeclaration: predicate[options.exports], - - FunctionDeclaration: predicate[options.functions], - FunctionExpression: predicate[options.functions], - ArrowFunctionExpression: predicate[options.functions], - CallExpression: predicate[options.functions], - NewExpression: predicate[options.functions] - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "comma-dangle", + url: "https://eslint.style/rules/js/comma-dangle", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require or disallow trailing commas", + recommended: false, + url: "https://eslint.org/docs/latest/rules/comma-dangle", + }, + + fixable: "code", + + schema: { + definitions: { + value: { + enum: [ + "always-multiline", + "always", + "never", + "only-multiline", + ], + }, + valueWithIgnore: { + enum: [ + "always-multiline", + "always", + "ignore", + "never", + "only-multiline", + ], + }, + }, + type: "array", + items: [ + { + oneOf: [ + { + $ref: "#/definitions/value", + }, + { + type: "object", + properties: { + arrays: { + $ref: "#/definitions/valueWithIgnore", + }, + objects: { + $ref: "#/definitions/valueWithIgnore", + }, + imports: { + $ref: "#/definitions/valueWithIgnore", + }, + exports: { + $ref: "#/definitions/valueWithIgnore", + }, + functions: { + $ref: "#/definitions/valueWithIgnore", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + additionalItems: false, + }, + + messages: { + unexpected: "Unexpected trailing comma.", + missing: "Missing trailing comma.", + }, + }, + + create(context) { + const options = normalizeOptions( + context.options[0], + context.languageOptions.ecmaVersion, + ); + + const sourceCode = context.sourceCode; + + /** + * Gets the last item of the given node. + * @param {ASTNode} node The node to get. + * @returns {ASTNode|null} The last node or null. + */ + function getLastItem(node) { + /** + * Returns the last element of an array + * @param {any[]} array The input array + * @returns {any} The last element + */ + function last(array) { + return array.at(-1); + } + + switch (node.type) { + case "ObjectExpression": + case "ObjectPattern": + return last(node.properties); + case "ArrayExpression": + case "ArrayPattern": + return last(node.elements); + case "ImportDeclaration": + case "ExportNamedDeclaration": + return last(node.specifiers); + case "FunctionDeclaration": + case "FunctionExpression": + case "ArrowFunctionExpression": + return last(node.params); + case "CallExpression": + case "NewExpression": + return last(node.arguments); + default: + return null; + } + } + + /** + * Gets the trailing comma token of the given node. + * If the trailing comma does not exist, this returns the token which is + * the insertion point of the trailing comma token. + * @param {ASTNode} node The node to get. + * @param {ASTNode} lastItem The last item of the node. + * @returns {Token} The trailing comma token or the insertion point. + */ + function getTrailingToken(node, lastItem) { + switch (node.type) { + case "ObjectExpression": + case "ArrayExpression": + case "CallExpression": + case "NewExpression": + return sourceCode.getLastToken(node, 1); + default: { + const nextToken = sourceCode.getTokenAfter(lastItem); + + if (astUtils.isCommaToken(nextToken)) { + return nextToken; + } + return sourceCode.getLastToken(lastItem); + } + } + } + + /** + * Checks whether or not a given node is multiline. + * This rule handles a given node as multiline when the closing parenthesis + * and the last element are not on the same line. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is multiline. + */ + function isMultiline(node) { + const lastItem = getLastItem(node); + + if (!lastItem) { + return false; + } + + const penultimateToken = getTrailingToken(node, lastItem); + const lastToken = sourceCode.getTokenAfter(penultimateToken); + + return lastToken.loc.end.line !== penultimateToken.loc.end.line; + } + + /** + * Reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forbidTrailingComma(node) { + const lastItem = getLastItem(node); + + if ( + !lastItem || + (node.type === "ImportDeclaration" && + lastItem.type !== "ImportSpecifier") + ) { + return; + } + + const trailingToken = getTrailingToken(node, lastItem); + + if (astUtils.isCommaToken(trailingToken)) { + context.report({ + node: lastItem, + loc: trailingToken.loc, + messageId: "unexpected", + *fix(fixer) { + yield fixer.remove(trailingToken); + + /* + * Extend the range of the fix to include surrounding tokens to ensure + * that the element after which the comma is removed stays _last_. + * This intentionally makes conflicts in fix ranges with rules that may be + * adding or removing elements in the same autofix pass. + * https://github.com/eslint/eslint/issues/15660 + */ + yield fixer.insertTextBefore( + sourceCode.getTokenBefore(trailingToken), + "", + ); + yield fixer.insertTextAfter( + sourceCode.getTokenAfter(trailingToken), + "", + ); + }, + }); + } + } + + /** + * Reports the last element of a given node if it does not have a trailing + * comma. + * + * If a given node is `ArrayPattern` which has `RestElement`, the trailing + * comma is disallowed, so report if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingComma(node) { + const lastItem = getLastItem(node); + + if ( + !lastItem || + (node.type === "ImportDeclaration" && + lastItem.type !== "ImportSpecifier") + ) { + return; + } + if (!isTrailingCommaAllowed(lastItem)) { + forbidTrailingComma(node); + return; + } + + const trailingToken = getTrailingToken(node, lastItem); + + if (trailingToken.value !== ",") { + context.report({ + node: lastItem, + loc: { + start: trailingToken.loc.end, + end: astUtils.getNextLocation( + sourceCode, + trailingToken.loc.end, + ), + }, + messageId: "missing", + *fix(fixer) { + yield fixer.insertTextAfter(trailingToken, ","); + + /* + * Extend the range of the fix to include surrounding tokens to ensure + * that the element after which the comma is inserted stays _last_. + * This intentionally makes conflicts in fix ranges with rules that may be + * adding or removing elements in the same autofix pass. + * https://github.com/eslint/eslint/issues/15660 + */ + yield fixer.insertTextBefore(trailingToken, ""); + yield fixer.insertTextAfter( + sourceCode.getTokenAfter(trailingToken), + "", + ); + }, + }); + } + } + + /** + * If a given node is multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function forceTrailingCommaIfMultiline(node) { + if (isMultiline(node)) { + forceTrailingComma(node); + } else { + forbidTrailingComma(node); + } + } + + /** + * Only if a given node is not multiline, reports the last element of a given node + * when it does not have a trailing comma. + * Otherwise, reports a trailing comma if it exists. + * @param {ASTNode} node A node to check. Its type is one of + * ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern, + * ImportDeclaration, and ExportNamedDeclaration. + * @returns {void} + */ + function allowTrailingCommaIfMultiline(node) { + if (!isMultiline(node)) { + forbidTrailingComma(node); + } + } + + const predicate = { + always: forceTrailingComma, + "always-multiline": forceTrailingCommaIfMultiline, + "only-multiline": allowTrailingCommaIfMultiline, + never: forbidTrailingComma, + ignore() {}, + }; + + return { + ObjectExpression: predicate[options.objects], + ObjectPattern: predicate[options.objects], + + ArrayExpression: predicate[options.arrays], + ArrayPattern: predicate[options.arrays], + + ImportDeclaration: predicate[options.imports], + + ExportNamedDeclaration: predicate[options.exports], + + FunctionDeclaration: predicate[options.functions], + FunctionExpression: predicate[options.functions], + ArrowFunctionExpression: predicate[options.functions], + CallExpression: predicate[options.functions], + NewExpression: predicate[options.functions], + }; + }, }; diff --git a/lib/rules/comma-spacing.js b/lib/rules/comma-spacing.js index 85f2566d2d53..e5ea14802acd 100644 --- a/lib/rules/comma-spacing.js +++ b/lib/rules/comma-spacing.js @@ -13,198 +13,196 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "comma-spacing", - url: "https://eslint.style/rules/js/comma-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before and after commas", - recommended: false, - url: "https://eslint.org/docs/latest/rules/comma-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - before: { - type: "boolean", - default: false - }, - after: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - messages: { - missing: "A space is required {{loc}} ','.", - unexpected: "There should be no space {{loc}} ','." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - const tokensAndComments = sourceCode.tokensAndComments; - - const options = { - before: context.options[0] ? context.options[0].before : false, - after: context.options[0] ? context.options[0].after : true - }; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // list of comma tokens to ignore for the check of leading whitespace - const commaTokensToIgnore = []; - - /** - * Reports a spacing error with an appropriate message. - * @param {ASTNode} node The binary expression node to report. - * @param {string} loc Is the error "before" or "after" the comma? - * @param {ASTNode} otherNode The node at the left or right of `node` - * @returns {void} - * @private - */ - function report(node, loc, otherNode) { - context.report({ - node, - fix(fixer) { - if (options[loc]) { - if (loc === "before") { - return fixer.insertTextBefore(node, " "); - } - return fixer.insertTextAfter(node, " "); - - } - let start, end; - const newText = ""; - - if (loc === "before") { - start = otherNode.range[1]; - end = node.range[0]; - } else { - start = node.range[1]; - end = otherNode.range[0]; - } - - return fixer.replaceTextRange([start, end], newText); - - }, - messageId: options[loc] ? "missing" : "unexpected", - data: { - loc - } - }); - } - - /** - * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. - * @param {ASTNode} node An ArrayExpression or ArrayPattern node. - * @returns {void} - */ - function addNullElementsToIgnoreList(node) { - let previousToken = sourceCode.getFirstToken(node); - - node.elements.forEach(element => { - let token; - - if (element === null) { - token = sourceCode.getTokenAfter(previousToken); - - if (astUtils.isCommaToken(token)) { - commaTokensToIgnore.push(token); - } - } else { - token = sourceCode.getTokenAfter(element); - } - - previousToken = token; - }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program:exit"() { - tokensAndComments.forEach((token, i) => { - - if (!astUtils.isCommaToken(token)) { - return; - } - - const previousToken = tokensAndComments[i - 1]; - const nextToken = tokensAndComments[i + 1]; - - if ( - previousToken && - !astUtils.isCommaToken(previousToken) && // ignore spacing between two commas - - /* - * `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions). - * In addition to spacing between two commas, this can also ignore: - * - * - Spacing after `[` (controlled by array-bracket-spacing) - * Example: [ , ] - * ^ - * - Spacing after a comment (for backwards compatibility, this was possibly unintentional) - * Example: [a, /* * / ,] - * ^ - */ - !commaTokensToIgnore.includes(token) && - - astUtils.isTokenOnSameLine(previousToken, token) && - options.before !== sourceCode.isSpaceBetweenTokens(previousToken, token) - ) { - report(token, "before", previousToken); - } - - if ( - nextToken && - !astUtils.isCommaToken(nextToken) && // ignore spacing between two commas - !astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens - !astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing - !astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing - !(!options.after && nextToken.type === "Line") && // special case, allow space before line comment - astUtils.isTokenOnSameLine(token, nextToken) && - options.after !== sourceCode.isSpaceBetweenTokens(token, nextToken) - ) { - report(token, "after", nextToken); - } - }); - }, - ArrayExpression: addNullElementsToIgnoreList, - ArrayPattern: addNullElementsToIgnoreList - - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "comma-spacing", + url: "https://eslint.style/rules/js/comma-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent spacing before and after commas", + recommended: false, + url: "https://eslint.org/docs/latest/rules/comma-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: false, + }, + after: { + type: "boolean", + default: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missing: "A space is required {{loc}} ','.", + unexpected: "There should be no space {{loc}} ','.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const tokensAndComments = sourceCode.tokensAndComments; + + const options = { + before: context.options[0] ? context.options[0].before : false, + after: context.options[0] ? context.options[0].after : true, + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // list of comma tokens to ignore for the check of leading whitespace + const commaTokensToIgnore = []; + + /** + * Reports a spacing error with an appropriate message. + * @param {ASTNode} node The binary expression node to report. + * @param {string} loc Is the error "before" or "after" the comma? + * @param {ASTNode} otherNode The node at the left or right of `node` + * @returns {void} + * @private + */ + function report(node, loc, otherNode) { + context.report({ + node, + fix(fixer) { + if (options[loc]) { + if (loc === "before") { + return fixer.insertTextBefore(node, " "); + } + return fixer.insertTextAfter(node, " "); + } + let start, end; + const newText = ""; + + if (loc === "before") { + start = otherNode.range[1]; + end = node.range[0]; + } else { + start = node.range[1]; + end = otherNode.range[0]; + } + + return fixer.replaceTextRange([start, end], newText); + }, + messageId: options[loc] ? "missing" : "unexpected", + data: { + loc, + }, + }); + } + + /** + * Adds null elements of the given ArrayExpression or ArrayPattern node to the ignore list. + * @param {ASTNode} node An ArrayExpression or ArrayPattern node. + * @returns {void} + */ + function addNullElementsToIgnoreList(node) { + let previousToken = sourceCode.getFirstToken(node); + + node.elements.forEach(element => { + let token; + + if (element === null) { + token = sourceCode.getTokenAfter(previousToken); + + if (astUtils.isCommaToken(token)) { + commaTokensToIgnore.push(token); + } + } else { + token = sourceCode.getTokenAfter(element); + } + + previousToken = token; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program:exit"() { + tokensAndComments.forEach((token, i) => { + if (!astUtils.isCommaToken(token)) { + return; + } + + const previousToken = tokensAndComments[i - 1]; + const nextToken = tokensAndComments[i + 1]; + + if ( + previousToken && + !astUtils.isCommaToken(previousToken) && // ignore spacing between two commas + /* + * `commaTokensToIgnore` are ending commas of `null` elements (array holes/elisions). + * In addition to spacing between two commas, this can also ignore: + * + * - Spacing after `[` (controlled by array-bracket-spacing) + * Example: [ , ] + * ^ + * - Spacing after a comment (for backwards compatibility, this was possibly unintentional) + * Example: [a, /* * / ,] + * ^ + */ + !commaTokensToIgnore.includes(token) && + astUtils.isTokenOnSameLine(previousToken, token) && + options.before !== + sourceCode.isSpaceBetweenTokens( + previousToken, + token, + ) + ) { + report(token, "before", previousToken); + } + + if ( + nextToken && + !astUtils.isCommaToken(nextToken) && // ignore spacing between two commas + !astUtils.isClosingParenToken(nextToken) && // controlled by space-in-parens + !astUtils.isClosingBracketToken(nextToken) && // controlled by array-bracket-spacing + !astUtils.isClosingBraceToken(nextToken) && // controlled by object-curly-spacing + !(!options.after && nextToken.type === "Line") && // special case, allow space before line comment + astUtils.isTokenOnSameLine(token, nextToken) && + options.after !== + sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + report(token, "after", nextToken); + } + }); + }, + ArrayExpression: addNullElementsToIgnoreList, + ArrayPattern: addNullElementsToIgnoreList, + }; + }, }; diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js index 372385c5b353..9c1958ad29d2 100644 --- a/lib/rules/comma-style.js +++ b/lib/rules/comma-style.js @@ -14,319 +14,378 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "comma-style", - url: "https://eslint.style/rules/js/comma-style" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent comma style", - recommended: false, - url: "https://eslint.org/docs/latest/rules/comma-style" - }, - - fixable: "code", - - schema: [ - { - enum: ["first", "last"] - }, - { - type: "object", - properties: { - exceptions: { - type: "object", - additionalProperties: { - type: "boolean" - } - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.", - expectedCommaFirst: "',' should be placed first.", - expectedCommaLast: "',' should be placed last." - } - }, - - create(context) { - const style = context.options[0] || "last", - sourceCode = context.sourceCode; - const exceptions = { - ArrayPattern: true, - ArrowFunctionExpression: true, - CallExpression: true, - FunctionDeclaration: true, - FunctionExpression: true, - ImportDeclaration: true, - ObjectPattern: true, - NewExpression: true - }; - - if (context.options.length === 2 && Object.hasOwn(context.options[1], "exceptions")) { - const keys = Object.keys(context.options[1].exceptions); - - for (let i = 0; i < keys.length; i++) { - exceptions[keys[i]] = context.options[1].exceptions[keys[i]]; - } - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Modified text based on the style - * @param {string} styleType Style type - * @param {string} text Source code text - * @returns {string} modified text - * @private - */ - function getReplacedText(styleType, text) { - switch (styleType) { - case "between": - return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`; - - case "first": - return `${text},`; - - case "last": - return `,${text}`; - - default: - return ""; - } - } - - /** - * Determines the fixer function for a given style. - * @param {string} styleType comma style - * @param {ASTNode} previousItemToken The token to check. - * @param {ASTNode} commaToken The token to check. - * @param {ASTNode} currentItemToken The token to check. - * @returns {Function} Fixer function - * @private - */ - function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) { - const text = - sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) + - sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]); - const range = [previousItemToken.range[1], currentItemToken.range[0]]; - - return function(fixer) { - return fixer.replaceTextRange(range, getReplacedText(styleType, text)); - }; - } - - /** - * Validates the spacing around single items in lists. - * @param {Token} previousItemToken The last token from the previous item. - * @param {Token} commaToken The token representing the comma. - * @param {Token} currentItemToken The first token of the current item. - * @param {Token} reportItem The item to use when reporting an error. - * @returns {void} - * @private - */ - function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) { - - // if single line - if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) && - astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { - - // do nothing. - - } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) && - !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) { - - const comment = sourceCode.getCommentsAfter(commaToken)[0]; - const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment) - ? style - : "between"; - - // lone comma - context.report({ - node: reportItem, - loc: commaToken.loc, - messageId: "unexpectedLineBeforeAndAfterComma", - fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) - }); - - } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { - - context.report({ - node: reportItem, - loc: commaToken.loc, - messageId: "expectedCommaFirst", - fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) - }); - - } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) { - - context.report({ - node: reportItem, - loc: commaToken.loc, - messageId: "expectedCommaLast", - fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken) - }); - } - } - - /** - * Checks the comma placement with regards to a declaration/property/element - * @param {ASTNode} node The binary expression node to check - * @param {string} property The property of the node containing child nodes. - * @private - * @returns {void} - */ - function validateComma(node, property) { - const items = node[property], - arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern"); - - if (items.length > 1 || arrayLiteral) { - - // seed as opening [ - let previousItemToken = sourceCode.getFirstToken(node); - - items.forEach(item => { - const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken, - currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken), - reportItem = item || currentItemToken; - - /* - * This works by comparing three token locations: - * - previousItemToken is the last token of the previous item - * - commaToken is the location of the comma before the current item - * - currentItemToken is the first token of the current item - * - * These values get switched around if item is undefined. - * previousItemToken will refer to the last token not belonging - * to the current item, which could be a comma or an opening - * square bracket. currentItemToken could be a comma. - * - * All comparisons are done based on these tokens directly, so - * they are always valid regardless of an undefined item. - */ - if (astUtils.isCommaToken(commaToken)) { - validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem); - } - - if (item) { - const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken); - - previousItemToken = tokenAfterItem - ? sourceCode.getTokenBefore(tokenAfterItem) - : sourceCode.ast.tokens.at(-1); - } else { - previousItemToken = currentItemToken; - } - }); - - /* - * Special case for array literals that have empty last items, such - * as [ 1, 2, ]. These arrays only have two items show up in the - * AST, so we need to look at the token to verify that there's no - * dangling comma. - */ - if (arrayLiteral) { - - const lastToken = sourceCode.getLastToken(node), - nextToLastToken = sourceCode.getTokenBefore(lastToken); - - if (astUtils.isCommaToken(nextToLastToken)) { - validateCommaItemSpacing( - sourceCode.getTokenBefore(nextToLastToken), - nextToLastToken, - lastToken, - lastToken - ); - } - } - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - const nodes = {}; - - if (!exceptions.VariableDeclaration) { - nodes.VariableDeclaration = function(node) { - validateComma(node, "declarations"); - }; - } - if (!exceptions.ObjectExpression) { - nodes.ObjectExpression = function(node) { - validateComma(node, "properties"); - }; - } - if (!exceptions.ObjectPattern) { - nodes.ObjectPattern = function(node) { - validateComma(node, "properties"); - }; - } - if (!exceptions.ArrayExpression) { - nodes.ArrayExpression = function(node) { - validateComma(node, "elements"); - }; - } - if (!exceptions.ArrayPattern) { - nodes.ArrayPattern = function(node) { - validateComma(node, "elements"); - }; - } - if (!exceptions.FunctionDeclaration) { - nodes.FunctionDeclaration = function(node) { - validateComma(node, "params"); - }; - } - if (!exceptions.FunctionExpression) { - nodes.FunctionExpression = function(node) { - validateComma(node, "params"); - }; - } - if (!exceptions.ArrowFunctionExpression) { - nodes.ArrowFunctionExpression = function(node) { - validateComma(node, "params"); - }; - } - if (!exceptions.CallExpression) { - nodes.CallExpression = function(node) { - validateComma(node, "arguments"); - }; - } - if (!exceptions.ImportDeclaration) { - nodes.ImportDeclaration = function(node) { - validateComma(node, "specifiers"); - }; - } - if (!exceptions.NewExpression) { - nodes.NewExpression = function(node) { - validateComma(node, "arguments"); - }; - } - - return nodes; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "comma-style", + url: "https://eslint.style/rules/js/comma-style", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent comma style", + recommended: false, + url: "https://eslint.org/docs/latest/rules/comma-style", + }, + + fixable: "code", + + schema: [ + { + enum: ["first", "last"], + }, + { + type: "object", + properties: { + exceptions: { + type: "object", + additionalProperties: { + type: "boolean", + }, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedLineBeforeAndAfterComma: + "Bad line breaking before and after ','.", + expectedCommaFirst: "',' should be placed first.", + expectedCommaLast: "',' should be placed last.", + }, + }, + + create(context) { + const style = context.options[0] || "last", + sourceCode = context.sourceCode; + const exceptions = { + ArrayPattern: true, + ArrowFunctionExpression: true, + CallExpression: true, + FunctionDeclaration: true, + FunctionExpression: true, + ImportDeclaration: true, + ObjectPattern: true, + NewExpression: true, + }; + + if ( + context.options.length === 2 && + Object.hasOwn(context.options[1], "exceptions") + ) { + const keys = Object.keys(context.options[1].exceptions); + + for (let i = 0; i < keys.length; i++) { + exceptions[keys[i]] = context.options[1].exceptions[keys[i]]; + } + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Modified text based on the style + * @param {string} styleType Style type + * @param {string} text Source code text + * @returns {string} modified text + * @private + */ + function getReplacedText(styleType, text) { + switch (styleType) { + case "between": + return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`; + + case "first": + return `${text},`; + + case "last": + return `,${text}`; + + default: + return ""; + } + } + + /** + * Determines the fixer function for a given style. + * @param {string} styleType comma style + * @param {ASTNode} previousItemToken The token to check. + * @param {ASTNode} commaToken The token to check. + * @param {ASTNode} currentItemToken The token to check. + * @returns {Function} Fixer function + * @private + */ + function getFixerFunction( + styleType, + previousItemToken, + commaToken, + currentItemToken, + ) { + const text = + sourceCode.text.slice( + previousItemToken.range[1], + commaToken.range[0], + ) + + sourceCode.text.slice( + commaToken.range[1], + currentItemToken.range[0], + ); + const range = [ + previousItemToken.range[1], + currentItemToken.range[0], + ]; + + return function (fixer) { + return fixer.replaceTextRange( + range, + getReplacedText(styleType, text), + ); + }; + } + + /** + * Validates the spacing around single items in lists. + * @param {Token} previousItemToken The last token from the previous item. + * @param {Token} commaToken The token representing the comma. + * @param {Token} currentItemToken The first token of the current item. + * @param {Token} reportItem The item to use when reporting an error. + * @returns {void} + * @private + */ + function validateCommaItemSpacing( + previousItemToken, + commaToken, + currentItemToken, + reportItem, + ) { + // if single line + if ( + astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + astUtils.isTokenOnSameLine(previousItemToken, commaToken) + ) { + // do nothing. + } else if ( + !astUtils.isTokenOnSameLine(commaToken, currentItemToken) && + !astUtils.isTokenOnSameLine(previousItemToken, commaToken) + ) { + const comment = sourceCode.getCommentsAfter(commaToken)[0]; + const styleType = + comment && + comment.type === "Block" && + astUtils.isTokenOnSameLine(commaToken, comment) + ? style + : "between"; + + // lone comma + context.report({ + node: reportItem, + loc: commaToken.loc, + messageId: "unexpectedLineBeforeAndAfterComma", + fix: getFixerFunction( + styleType, + previousItemToken, + commaToken, + currentItemToken, + ), + }); + } else if ( + style === "first" && + !astUtils.isTokenOnSameLine(commaToken, currentItemToken) + ) { + context.report({ + node: reportItem, + loc: commaToken.loc, + messageId: "expectedCommaFirst", + fix: getFixerFunction( + style, + previousItemToken, + commaToken, + currentItemToken, + ), + }); + } else if ( + style === "last" && + astUtils.isTokenOnSameLine(commaToken, currentItemToken) + ) { + context.report({ + node: reportItem, + loc: commaToken.loc, + messageId: "expectedCommaLast", + fix: getFixerFunction( + style, + previousItemToken, + commaToken, + currentItemToken, + ), + }); + } + } + + /** + * Checks the comma placement with regards to a declaration/property/element + * @param {ASTNode} node The binary expression node to check + * @param {string} property The property of the node containing child nodes. + * @private + * @returns {void} + */ + function validateComma(node, property) { + const items = node[property], + arrayLiteral = + node.type === "ArrayExpression" || + node.type === "ArrayPattern"; + + if (items.length > 1 || arrayLiteral) { + // seed as opening [ + let previousItemToken = sourceCode.getFirstToken(node); + + items.forEach(item => { + const commaToken = item + ? sourceCode.getTokenBefore(item) + : previousItemToken, + currentItemToken = item + ? sourceCode.getFirstToken(item) + : sourceCode.getTokenAfter(commaToken), + reportItem = item || currentItemToken; + + /* + * This works by comparing three token locations: + * - previousItemToken is the last token of the previous item + * - commaToken is the location of the comma before the current item + * - currentItemToken is the first token of the current item + * + * These values get switched around if item is undefined. + * previousItemToken will refer to the last token not belonging + * to the current item, which could be a comma or an opening + * square bracket. currentItemToken could be a comma. + * + * All comparisons are done based on these tokens directly, so + * they are always valid regardless of an undefined item. + */ + if (astUtils.isCommaToken(commaToken)) { + validateCommaItemSpacing( + previousItemToken, + commaToken, + currentItemToken, + reportItem, + ); + } + + if (item) { + const tokenAfterItem = sourceCode.getTokenAfter( + item, + astUtils.isNotClosingParenToken, + ); + + previousItemToken = tokenAfterItem + ? sourceCode.getTokenBefore(tokenAfterItem) + : sourceCode.ast.tokens.at(-1); + } else { + previousItemToken = currentItemToken; + } + }); + + /* + * Special case for array literals that have empty last items, such + * as [ 1, 2, ]. These arrays only have two items show up in the + * AST, so we need to look at the token to verify that there's no + * dangling comma. + */ + if (arrayLiteral) { + const lastToken = sourceCode.getLastToken(node), + nextToLastToken = sourceCode.getTokenBefore(lastToken); + + if (astUtils.isCommaToken(nextToLastToken)) { + validateCommaItemSpacing( + sourceCode.getTokenBefore(nextToLastToken), + nextToLastToken, + lastToken, + lastToken, + ); + } + } + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const nodes = {}; + + if (!exceptions.VariableDeclaration) { + nodes.VariableDeclaration = function (node) { + validateComma(node, "declarations"); + }; + } + if (!exceptions.ObjectExpression) { + nodes.ObjectExpression = function (node) { + validateComma(node, "properties"); + }; + } + if (!exceptions.ObjectPattern) { + nodes.ObjectPattern = function (node) { + validateComma(node, "properties"); + }; + } + if (!exceptions.ArrayExpression) { + nodes.ArrayExpression = function (node) { + validateComma(node, "elements"); + }; + } + if (!exceptions.ArrayPattern) { + nodes.ArrayPattern = function (node) { + validateComma(node, "elements"); + }; + } + if (!exceptions.FunctionDeclaration) { + nodes.FunctionDeclaration = function (node) { + validateComma(node, "params"); + }; + } + if (!exceptions.FunctionExpression) { + nodes.FunctionExpression = function (node) { + validateComma(node, "params"); + }; + } + if (!exceptions.ArrowFunctionExpression) { + nodes.ArrowFunctionExpression = function (node) { + validateComma(node, "params"); + }; + } + if (!exceptions.CallExpression) { + nodes.CallExpression = function (node) { + validateComma(node, "arguments"); + }; + } + if (!exceptions.ImportDeclaration) { + nodes.ImportDeclaration = function (node) { + validateComma(node, "specifiers"); + }; + } + if (!exceptions.NewExpression) { + nodes.NewExpression = function (node) { + validateComma(node, "arguments"); + }; + } + + return nodes; + }, }; diff --git a/lib/rules/complexity.js b/lib/rules/complexity.js index 7358fe19a493..b2b59b403b92 100644 --- a/lib/rules/complexity.js +++ b/lib/rules/complexity.js @@ -21,172 +21,176 @@ const THRESHOLD_DEFAULT = 20; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [THRESHOLD_DEFAULT], - - docs: { - description: "Enforce a maximum cyclomatic complexity allowed in a program", - recommended: false, - url: "https://eslint.org/docs/latest/rules/complexity" - }, - - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - maximum: { - type: "integer", - minimum: 0 - }, - max: { - type: "integer", - minimum: 0 - }, - variant: { - enum: ["classic", "modified"] - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - complex: "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}." - } - }, - - create(context) { - const option = context.options[0]; - let threshold = THRESHOLD_DEFAULT; - let VARIANT = "classic"; - - if (typeof option === "object") { - if (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) { - threshold = option.maximum || option.max; - } - - if (Object.hasOwn(option, "variant")) { - VARIANT = option.variant; - } - } else if (typeof option === "number") { - threshold = option; - } - - const IS_MODIFIED_COMPLEXITY = VARIANT === "modified"; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // Using a stack to store complexity per code path - const complexities = []; - - /** - * Increase the complexity of the code path in context - * @returns {void} - * @private - */ - function increaseComplexity() { - complexities[complexities.length - 1]++; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - onCodePathStart() { - - // The initial complexity is 1, representing one execution path in the CodePath - complexities.push(1); - }, - - // Each branching in the code adds 1 to the complexity - CatchClause: increaseComplexity, - ConditionalExpression: increaseComplexity, - LogicalExpression: increaseComplexity, - ForStatement: increaseComplexity, - ForInStatement: increaseComplexity, - ForOfStatement: increaseComplexity, - IfStatement: increaseComplexity, - WhileStatement: increaseComplexity, - DoWhileStatement: increaseComplexity, - AssignmentPattern: increaseComplexity, - - // Avoid `default` - "SwitchCase[test]": () => IS_MODIFIED_COMPLEXITY || increaseComplexity(), - SwitchStatement: () => IS_MODIFIED_COMPLEXITY && increaseComplexity(), - - // Logical assignment operators have short-circuiting behavior - AssignmentExpression(node) { - if (astUtils.isLogicalAssignmentOperator(node.operator)) { - increaseComplexity(); - } - }, - - MemberExpression(node) { - if (node.optional === true) { - increaseComplexity(); - } - }, - - CallExpression(node) { - if (node.optional === true) { - increaseComplexity(); - } - }, - - onCodePathEnd(codePath, node) { - const complexity = complexities.pop(); - - /* - * This rule only evaluates complexity of functions, so "program" is excluded. - * Class field initializers and class static blocks are implicit functions. Therefore, - * they shouldn't contribute to the enclosing function's complexity, but their - * own complexity should be evaluated. - */ - if ( - codePath.origin !== "function" && - codePath.origin !== "class-field-initializer" && - codePath.origin !== "class-static-block" - ) { - return; - } - - if (complexity > threshold) { - let name; - - if (codePath.origin === "class-field-initializer") { - name = "class field initializer"; - } else if (codePath.origin === "class-static-block") { - name = "class static block"; - } else { - name = astUtils.getFunctionNameWithKind(node); - } - - context.report({ - node, - messageId: "complex", - data: { - name: upperCaseFirst(name), - complexity, - max: threshold - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [THRESHOLD_DEFAULT], + + docs: { + description: + "Enforce a maximum cyclomatic complexity allowed in a program", + recommended: false, + url: "https://eslint.org/docs/latest/rules/complexity", + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0, + }, + max: { + type: "integer", + minimum: 0, + }, + variant: { + enum: ["classic", "modified"], + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + complex: + "{{name}} has a complexity of {{complexity}}. Maximum allowed is {{max}}.", + }, + }, + + create(context) { + const option = context.options[0]; + let threshold = THRESHOLD_DEFAULT; + let VARIANT = "classic"; + + if (typeof option === "object") { + if ( + Object.hasOwn(option, "maximum") || + Object.hasOwn(option, "max") + ) { + threshold = option.maximum || option.max; + } + + if (Object.hasOwn(option, "variant")) { + VARIANT = option.variant; + } + } else if (typeof option === "number") { + threshold = option; + } + + const IS_MODIFIED_COMPLEXITY = VARIANT === "modified"; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // Using a stack to store complexity per code path + const complexities = []; + + /** + * Increase the complexity of the code path in context + * @returns {void} + * @private + */ + function increaseComplexity() { + complexities[complexities.length - 1]++; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + onCodePathStart() { + // The initial complexity is 1, representing one execution path in the CodePath + complexities.push(1); + }, + + // Each branching in the code adds 1 to the complexity + CatchClause: increaseComplexity, + ConditionalExpression: increaseComplexity, + LogicalExpression: increaseComplexity, + ForStatement: increaseComplexity, + ForInStatement: increaseComplexity, + ForOfStatement: increaseComplexity, + IfStatement: increaseComplexity, + WhileStatement: increaseComplexity, + DoWhileStatement: increaseComplexity, + AssignmentPattern: increaseComplexity, + + // Avoid `default` + "SwitchCase[test]": () => + IS_MODIFIED_COMPLEXITY || increaseComplexity(), + SwitchStatement: () => + IS_MODIFIED_COMPLEXITY && increaseComplexity(), + + // Logical assignment operators have short-circuiting behavior + AssignmentExpression(node) { + if (astUtils.isLogicalAssignmentOperator(node.operator)) { + increaseComplexity(); + } + }, + + MemberExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + + CallExpression(node) { + if (node.optional === true) { + increaseComplexity(); + } + }, + + onCodePathEnd(codePath, node) { + const complexity = complexities.pop(); + + /* + * This rule only evaluates complexity of functions, so "program" is excluded. + * Class field initializers and class static blocks are implicit functions. Therefore, + * they shouldn't contribute to the enclosing function's complexity, but their + * own complexity should be evaluated. + */ + if ( + codePath.origin !== "function" && + codePath.origin !== "class-field-initializer" && + codePath.origin !== "class-static-block" + ) { + return; + } + + if (complexity > threshold) { + let name; + + if (codePath.origin === "class-field-initializer") { + name = "class field initializer"; + } else if (codePath.origin === "class-static-block") { + name = "class static block"; + } else { + name = astUtils.getFunctionNameWithKind(node); + } + + context.report({ + node, + messageId: "complex", + data: { + name: upperCaseFirst(name), + complexity, + max: threshold, + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/computed-property-spacing.js b/lib/rules/computed-property-spacing.js index 3e5455efa78b..b226567337cc 100644 --- a/lib/rules/computed-property-spacing.js +++ b/lib/rules/computed-property-spacing.js @@ -13,214 +13,239 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "computed-property-spacing", - url: "https://eslint.style/rules/js/computed-property-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing inside computed property brackets", - recommended: false, - url: "https://eslint.org/docs/latest/rules/computed-property-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - enforceForClassMembers: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedSpaceBefore: "There should be no space before '{{tokenValue}}'.", - unexpectedSpaceAfter: "There should be no space after '{{tokenValue}}'.", - - missingSpaceBefore: "A space is required before '{{tokenValue}}'.", - missingSpaceAfter: "A space is required after '{{tokenValue}}'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" - const enforceForClassMembers = !context.options[1] || context.options[1].enforceForClassMembers; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports that there shouldn't be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @param {Token} tokenAfter The token after `token`. - * @returns {void} - */ - function reportNoBeginningSpace(node, token, tokenAfter) { - context.report({ - node, - loc: { start: token.loc.end, end: tokenAfter.loc.start }, - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.removeRange([token.range[1], tokenAfter.range[0]]); - } - }); - } - - /** - * Reports that there shouldn't be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @param {Token} tokenBefore The token before `token`. - * @returns {void} - */ - function reportNoEndingSpace(node, token, tokenBefore) { - context.report({ - node, - loc: { start: tokenBefore.loc.end, end: token.loc.start }, - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.removeRange([tokenBefore.range[1], token.range[0]]); - } - }); - } - - /** - * Reports that there should be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredBeginningSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingSpaceAfter", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } - - /** - * Reports that there should be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "missingSpaceBefore", - data: { - tokenValue: token.value - }, - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - - /** - * Returns a function that checks the spacing of a node on the property name - * that was passed in. - * @param {string} propertyName The property on the node to check for spacing - * @returns {Function} A function that will check spacing on a node - */ - function checkSpacing(propertyName) { - return function(node) { - if (!node.computed) { - return; - } - - const property = node[propertyName]; - - const before = sourceCode.getTokenBefore(property, astUtils.isOpeningBracketToken), - first = sourceCode.getTokenAfter(before, { includeComments: true }), - after = sourceCode.getTokenAfter(property, astUtils.isClosingBracketToken), - last = sourceCode.getTokenBefore(after, { includeComments: true }); - - if (astUtils.isTokenOnSameLine(before, first)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(before, first) && astUtils.isTokenOnSameLine(before, first)) { - reportRequiredBeginningSpace(node, before); - } - } else { - if (sourceCode.isSpaceBetweenTokens(before, first)) { - reportNoBeginningSpace(node, before, first); - } - } - } - - if (astUtils.isTokenOnSameLine(last, after)) { - if (propertyNameMustBeSpaced) { - if (!sourceCode.isSpaceBetweenTokens(last, after) && astUtils.isTokenOnSameLine(last, after)) { - reportRequiredEndingSpace(node, after); - } - } else { - if (sourceCode.isSpaceBetweenTokens(last, after)) { - reportNoEndingSpace(node, after, last); - } - } - } - }; - } - - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - const listeners = { - Property: checkSpacing("key"), - MemberExpression: checkSpacing("property") - }; - - if (enforceForClassMembers) { - listeners.MethodDefinition = - listeners.PropertyDefinition = listeners.Property; - } - - return listeners; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "computed-property-spacing", + url: "https://eslint.style/rules/js/computed-property-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing inside computed property brackets", + recommended: false, + url: "https://eslint.org/docs/latest/rules/computed-property-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + enforceForClassMembers: { + type: "boolean", + default: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedSpaceBefore: + "There should be no space before '{{tokenValue}}'.", + unexpectedSpaceAfter: + "There should be no space after '{{tokenValue}}'.", + + missingSpaceBefore: "A space is required before '{{tokenValue}}'.", + missingSpaceAfter: "A space is required after '{{tokenValue}}'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never" + const enforceForClassMembers = + !context.options[1] || context.options[1].enforceForClassMembers; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @param {Token} tokenAfter The token after `token`. + * @returns {void} + */ + function reportNoBeginningSpace(node, token, tokenAfter) { + context.report({ + node, + loc: { start: token.loc.end, end: tokenAfter.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + token.range[1], + tokenAfter.range[0], + ]); + }, + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @param {Token} tokenBefore The token before `token`. + * @returns {void} + */ + function reportNoEndingSpace(node, token, tokenBefore) { + context.report({ + node, + loc: { start: tokenBefore.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + tokenBefore.range[1], + token.range[0], + ]); + }, + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceAfter", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "missingSpaceBefore", + data: { + tokenValue: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } + + /** + * Returns a function that checks the spacing of a node on the property name + * that was passed in. + * @param {string} propertyName The property on the node to check for spacing + * @returns {Function} A function that will check spacing on a node + */ + function checkSpacing(propertyName) { + return function (node) { + if (!node.computed) { + return; + } + + const property = node[propertyName]; + + const before = sourceCode.getTokenBefore( + property, + astUtils.isOpeningBracketToken, + ), + first = sourceCode.getTokenAfter(before, { + includeComments: true, + }), + after = sourceCode.getTokenAfter( + property, + astUtils.isClosingBracketToken, + ), + last = sourceCode.getTokenBefore(after, { + includeComments: true, + }); + + if (astUtils.isTokenOnSameLine(before, first)) { + if (propertyNameMustBeSpaced) { + if ( + !sourceCode.isSpaceBetweenTokens(before, first) && + astUtils.isTokenOnSameLine(before, first) + ) { + reportRequiredBeginningSpace(node, before); + } + } else { + if (sourceCode.isSpaceBetweenTokens(before, first)) { + reportNoBeginningSpace(node, before, first); + } + } + } + + if (astUtils.isTokenOnSameLine(last, after)) { + if (propertyNameMustBeSpaced) { + if ( + !sourceCode.isSpaceBetweenTokens(last, after) && + astUtils.isTokenOnSameLine(last, after) + ) { + reportRequiredEndingSpace(node, after); + } + } else { + if (sourceCode.isSpaceBetweenTokens(last, after)) { + reportNoEndingSpace(node, after, last); + } + } + } + }; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + const listeners = { + Property: checkSpacing("key"), + MemberExpression: checkSpacing("property"), + }; + + if (enforceForClassMembers) { + listeners.MethodDefinition = listeners.PropertyDefinition = + listeners.Property; + } + + return listeners; + }, }; diff --git a/lib/rules/consistent-return.js b/lib/rules/consistent-return.js index f31c2e10025a..09007b1a48d2 100644 --- a/lib/rules/consistent-return.js +++ b/lib/rules/consistent-return.js @@ -21,14 +21,13 @@ const { upperCaseFirst } = require("../shared/string-utils"); * @returns {boolean} True if all segments are unreachable; false otherwise. */ function areAllSegmentsUnreachable(segments) { + for (const segment of segments) { + if (segment.reachable) { + return false; + } + } - for (const segment of segments) { - if (segment.reachable) { - return false; - } - } - - return true; + return true; } /** @@ -37,10 +36,12 @@ function areAllSegmentsUnreachable(segments) { * @returns {boolean} `true` if the node is a `constructor` method */ function isClassConstructor(node) { - return node.type === "FunctionExpression" && - node.parent && - node.parent.type === "MethodDefinition" && - node.parent.kind === "constructor"; + return ( + node.type === "FunctionExpression" && + node.parent && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor" + ); } //------------------------------------------------------------------------------ @@ -49,162 +50,172 @@ function isClassConstructor(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require `return` statements to either always or never specify values", - recommended: false, - url: "https://eslint.org/docs/latest/rules/consistent-return" - }, - - schema: [{ - type: "object", - properties: { - treatUndefinedAsUnspecified: { - type: "boolean" - } - }, - additionalProperties: false - }], - - defaultOptions: [{ treatUndefinedAsUnspecified: false }], - - messages: { - missingReturn: "Expected to return a value at the end of {{name}}.", - missingReturnValue: "{{name}} expected a return value.", - unexpectedReturnValue: "{{name}} expected no return value." - } - }, - - create(context) { - const [{ treatUndefinedAsUnspecified }] = context.options; - let funcInfo = null; - - /** - * Checks whether of not the implicit returning is consistent if the last - * code path segment is reachable. - * @param {ASTNode} node A program/function node to check. - * @returns {void} - */ - function checkLastSegment(node) { - let loc, name; - - /* - * Skip if it expected no return value or unreachable. - * When unreachable, all paths are returned or thrown. - */ - if (!funcInfo.hasReturnValue || - areAllSegmentsUnreachable(funcInfo.currentSegments) || - astUtils.isES5Constructor(node) || - isClassConstructor(node) - ) { - return; - } - - // Adjust a location and a message. - if (node.type === "Program") { - - // The head of program. - loc = { line: 1, column: 0 }; - name = "program"; - } else if (node.type === "ArrowFunctionExpression") { - - // `=>` token - loc = context.sourceCode.getTokenBefore(node.body, astUtils.isArrowToken).loc; - } else if ( - node.parent.type === "MethodDefinition" || - (node.parent.type === "Property" && node.parent.method) - ) { - - // Method name. - loc = node.parent.key.loc; - } else { - - // Function name or `function` keyword. - loc = (node.id || context.sourceCode.getFirstToken(node)).loc; - } - - if (!name) { - name = astUtils.getFunctionNameWithKind(node); - } - - // Reports. - context.report({ - node, - loc, - messageId: "missingReturn", - data: { name } - }); - } - - return { - - // Initializes/Disposes state of each code path. - onCodePathStart(codePath, node) { - funcInfo = { - upper: funcInfo, - codePath, - hasReturn: false, - hasReturnValue: false, - messageId: "", - node, - currentSegments: new Set() - }; - }, - onCodePathEnd() { - funcInfo = funcInfo.upper; - }, - - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - - // Reports a given return statement if it's inconsistent. - ReturnStatement(node) { - const argument = node.argument; - let hasReturnValue = Boolean(argument); - - if (treatUndefinedAsUnspecified && hasReturnValue) { - hasReturnValue = !astUtils.isSpecificId(argument, "undefined") && argument.operator !== "void"; - } - - if (!funcInfo.hasReturn) { - funcInfo.hasReturn = true; - funcInfo.hasReturnValue = hasReturnValue; - funcInfo.messageId = hasReturnValue ? "missingReturnValue" : "unexpectedReturnValue"; - funcInfo.data = { - name: funcInfo.node.type === "Program" - ? "Program" - : upperCaseFirst(astUtils.getFunctionNameWithKind(funcInfo.node)) - }; - } else if (funcInfo.hasReturnValue !== hasReturnValue) { - context.report({ - node, - messageId: funcInfo.messageId, - data: funcInfo.data - }); - } - }, - - // Reports a given program/function if the implicit returning is not consistent. - "Program:exit": checkLastSegment, - "FunctionDeclaration:exit": checkLastSegment, - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Require `return` statements to either always or never specify values", + recommended: false, + url: "https://eslint.org/docs/latest/rules/consistent-return", + }, + + schema: [ + { + type: "object", + properties: { + treatUndefinedAsUnspecified: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [{ treatUndefinedAsUnspecified: false }], + + messages: { + missingReturn: "Expected to return a value at the end of {{name}}.", + missingReturnValue: "{{name}} expected a return value.", + unexpectedReturnValue: "{{name}} expected no return value.", + }, + }, + + create(context) { + const [{ treatUndefinedAsUnspecified }] = context.options; + let funcInfo = null; + + /** + * Checks whether of not the implicit returning is consistent if the last + * code path segment is reachable. + * @param {ASTNode} node A program/function node to check. + * @returns {void} + */ + function checkLastSegment(node) { + let loc, name; + + /* + * Skip if it expected no return value or unreachable. + * When unreachable, all paths are returned or thrown. + */ + if ( + !funcInfo.hasReturnValue || + areAllSegmentsUnreachable(funcInfo.currentSegments) || + astUtils.isES5Constructor(node) || + isClassConstructor(node) + ) { + return; + } + + // Adjust a location and a message. + if (node.type === "Program") { + // The head of program. + loc = { line: 1, column: 0 }; + name = "program"; + } else if (node.type === "ArrowFunctionExpression") { + // `=>` token + loc = context.sourceCode.getTokenBefore( + node.body, + astUtils.isArrowToken, + ).loc; + } else if ( + node.parent.type === "MethodDefinition" || + (node.parent.type === "Property" && node.parent.method) + ) { + // Method name. + loc = node.parent.key.loc; + } else { + // Function name or `function` keyword. + loc = (node.id || context.sourceCode.getFirstToken(node)).loc; + } + + if (!name) { + name = astUtils.getFunctionNameWithKind(node); + } + + // Reports. + context.report({ + node, + loc, + messageId: "missingReturn", + data: { name }, + }); + } + + return { + // Initializes/Disposes state of each code path. + onCodePathStart(codePath, node) { + funcInfo = { + upper: funcInfo, + codePath, + hasReturn: false, + hasReturnValue: false, + messageId: "", + node, + currentSegments: new Set(), + }; + }, + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Reports a given return statement if it's inconsistent. + ReturnStatement(node) { + const argument = node.argument; + let hasReturnValue = Boolean(argument); + + if (treatUndefinedAsUnspecified && hasReturnValue) { + hasReturnValue = + !astUtils.isSpecificId(argument, "undefined") && + argument.operator !== "void"; + } + + if (!funcInfo.hasReturn) { + funcInfo.hasReturn = true; + funcInfo.hasReturnValue = hasReturnValue; + funcInfo.messageId = hasReturnValue + ? "missingReturnValue" + : "unexpectedReturnValue"; + funcInfo.data = { + name: + funcInfo.node.type === "Program" + ? "Program" + : upperCaseFirst( + astUtils.getFunctionNameWithKind( + funcInfo.node, + ), + ), + }; + } else if (funcInfo.hasReturnValue !== hasReturnValue) { + context.report({ + node, + messageId: funcInfo.messageId, + data: funcInfo.data, + }); + } + }, + + // Reports a given program/function if the implicit returning is not consistent. + "Program:exit": checkLastSegment, + "FunctionDeclaration:exit": checkLastSegment, + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + }; + }, }; diff --git a/lib/rules/consistent-this.js b/lib/rules/consistent-this.js index 732de7b4018e..ebdc4d6865c8 100644 --- a/lib/rules/consistent-this.js +++ b/lib/rules/consistent-this.js @@ -10,150 +10,170 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: { - type: "array", - items: { - type: "string", - minLength: 1 - }, - uniqueItems: true - }, - - defaultOptions: ["that"], - - messages: { - aliasNotAssignedToThis: "Designated alias '{{name}}' is not assigned to 'this'.", - unexpectedAlias: "Unexpected alias '{{name}}' for 'this'." - } - }, - - create(context) { - const aliases = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports that a variable declarator or assignment expression is assigning - * a non-'this' value to the specified alias. - * @param {ASTNode} node The assigning node. - * @param {string} name the name of the alias that was incorrectly used. - * @returns {void} - */ - function reportBadAssignment(node, name) { - context.report({ node, messageId: "aliasNotAssignedToThis", data: { name } }); - } - - /** - * Checks that an assignment to an identifier only assigns 'this' to the - * appropriate alias, and the alias is only assigned to 'this'. - * @param {ASTNode} node The assigning node. - * @param {Identifier} name The name of the variable assigned to. - * @param {Expression} value The value of the assignment. - * @returns {void} - */ - function checkAssignment(node, name, value) { - const isThis = value.type === "ThisExpression"; - - if (aliases.includes(name)) { - if (!isThis || node.operator && node.operator !== "=") { - reportBadAssignment(node, name); - } - } else if (isThis) { - context.report({ node, messageId: "unexpectedAlias", data: { name } }); - } - } - - /** - * Ensures that a variable declaration of the alias in a program or function - * is assigned to the correct value. - * @param {string} alias alias the check the assignment of. - * @param {Object} scope scope of the current code we are checking. - * @private - * @returns {void} - */ - function checkWasAssigned(alias, scope) { - const variable = scope.set.get(alias); - - if (!variable) { - return; - } - - if (variable.defs.some(def => def.node.type === "VariableDeclarator" && - def.node.init !== null)) { - return; - } - - /* - * The alias has been declared and not assigned: check it was - * assigned later in the same scope. - */ - if (!variable.references.some(reference => { - const write = reference.writeExpr; - - return ( - reference.from === scope && - write && write.type === "ThisExpression" && - write.parent.operator === "=" - ); - })) { - variable.defs.map(def => def.node).forEach(node => { - reportBadAssignment(node, alias); - }); - } - } - - /** - * Check each alias to ensure that is was assigned to the correct value. - * @param {ASTNode} node The node that represents the scope to check. - * @returns {void} - */ - function ensureWasAssigned(node) { - const scope = sourceCode.getScope(node); - - // if this is program scope we also need to check module scope - const extraScope = node.type === "Program" && node.sourceType === "module" - ? scope.childScopes[0] - : null; - - aliases.forEach(alias => { - checkWasAssigned(alias, scope); - - if (extraScope) { - checkWasAssigned(alias, extraScope); - } - }); - } - - return { - "Program:exit": ensureWasAssigned, - "FunctionExpression:exit": ensureWasAssigned, - "FunctionDeclaration:exit": ensureWasAssigned, - - VariableDeclarator(node) { - const id = node.id; - const isDestructuring = - id.type === "ArrayPattern" || id.type === "ObjectPattern"; - - if (node.init !== null && !isDestructuring) { - checkAssignment(node, id.name, node.init); - } - }, - - AssignmentExpression(node) { - if (node.left.type === "Identifier") { - checkAssignment(node, node.left.name, node.right); - } - } - }; - - } + meta: { + 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", + }, + + schema: { + type: "array", + items: { + type: "string", + minLength: 1, + }, + uniqueItems: true, + }, + + defaultOptions: ["that"], + + messages: { + aliasNotAssignedToThis: + "Designated alias '{{name}}' is not assigned to 'this'.", + unexpectedAlias: "Unexpected alias '{{name}}' for 'this'.", + }, + }, + + create(context) { + const aliases = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports that a variable declarator or assignment expression is assigning + * a non-'this' value to the specified alias. + * @param {ASTNode} node The assigning node. + * @param {string} name the name of the alias that was incorrectly used. + * @returns {void} + */ + function reportBadAssignment(node, name) { + context.report({ + node, + messageId: "aliasNotAssignedToThis", + data: { name }, + }); + } + + /** + * Checks that an assignment to an identifier only assigns 'this' to the + * appropriate alias, and the alias is only assigned to 'this'. + * @param {ASTNode} node The assigning node. + * @param {Identifier} name The name of the variable assigned to. + * @param {Expression} value The value of the assignment. + * @returns {void} + */ + function checkAssignment(node, name, value) { + const isThis = value.type === "ThisExpression"; + + if (aliases.includes(name)) { + if (!isThis || (node.operator && node.operator !== "=")) { + reportBadAssignment(node, name); + } + } else if (isThis) { + context.report({ + node, + messageId: "unexpectedAlias", + data: { name }, + }); + } + } + + /** + * Ensures that a variable declaration of the alias in a program or function + * is assigned to the correct value. + * @param {string} alias alias the check the assignment of. + * @param {Object} scope scope of the current code we are checking. + * @private + * @returns {void} + */ + function checkWasAssigned(alias, scope) { + const variable = scope.set.get(alias); + + if (!variable) { + return; + } + + if ( + variable.defs.some( + def => + def.node.type === "VariableDeclarator" && + def.node.init !== null, + ) + ) { + return; + } + + /* + * The alias has been declared and not assigned: check it was + * assigned later in the same scope. + */ + if ( + !variable.references.some(reference => { + const write = reference.writeExpr; + + return ( + reference.from === scope && + write && + write.type === "ThisExpression" && + write.parent.operator === "=" + ); + }) + ) { + variable.defs + .map(def => def.node) + .forEach(node => { + reportBadAssignment(node, alias); + }); + } + } + + /** + * Check each alias to ensure that is was assigned to the correct value. + * @param {ASTNode} node The node that represents the scope to check. + * @returns {void} + */ + function ensureWasAssigned(node) { + const scope = sourceCode.getScope(node); + + // if this is program scope we also need to check module scope + const extraScope = + node.type === "Program" && node.sourceType === "module" + ? scope.childScopes[0] + : null; + + aliases.forEach(alias => { + checkWasAssigned(alias, scope); + + if (extraScope) { + checkWasAssigned(alias, extraScope); + } + }); + } + + return { + "Program:exit": ensureWasAssigned, + "FunctionExpression:exit": ensureWasAssigned, + "FunctionDeclaration:exit": ensureWasAssigned, + + VariableDeclarator(node) { + const id = node.id; + const isDestructuring = + id.type === "ArrayPattern" || id.type === "ObjectPattern"; + + if (node.init !== null && !isDestructuring) { + checkAssignment(node, id.name, node.init); + } + }, + + AssignmentExpression(node) { + if (node.left.type === "Identifier") { + checkAssignment(node, node.left.name, node.right); + } + }, + }; + }, }; diff --git a/lib/rules/constructor-super.js b/lib/rules/constructor-super.js index 6f46278f781c..368e45a91a72 100644 --- a/lib/rules/constructor-super.js +++ b/lib/rules/constructor-super.js @@ -17,11 +17,11 @@ * @returns {boolean} `true` if the node is a constructor. */ function isConstructorFunction(node) { - return ( - node.type === "FunctionExpression" && - node.parent.type === "MethodDefinition" && - node.parent.kind === "constructor" - ); + return ( + node.type === "FunctionExpression" && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor" + ); } /** @@ -30,101 +30,99 @@ function isConstructorFunction(node) { * @returns {boolean} `true` if the node can be a constructor. */ function isPossibleConstructor(node) { - if (!node) { - return false; - } - - switch (node.type) { - case "ClassExpression": - case "FunctionExpression": - case "ThisExpression": - case "MemberExpression": - case "CallExpression": - case "NewExpression": - case "ChainExpression": - case "YieldExpression": - case "TaggedTemplateExpression": - case "MetaProperty": - return true; - - case "Identifier": - return node.name !== "undefined"; - - case "AssignmentExpression": - if (["=", "&&="].includes(node.operator)) { - return isPossibleConstructor(node.right); - } - - if (["||=", "??="].includes(node.operator)) { - return ( - isPossibleConstructor(node.left) || - isPossibleConstructor(node.right) - ); - } - - /** - * All other assignment operators are mathematical assignment operators (arithmetic or bitwise). - * An assignment expression with a mathematical operator can either evaluate to a primitive value, - * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function. - */ - return false; - - case "LogicalExpression": - - /* - * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if - * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a - * possible constructor. A future improvement could verify that the left side could be truthy by - * excluding falsy literals. - */ - if (node.operator === "&&") { - return isPossibleConstructor(node.right); - } - - return ( - isPossibleConstructor(node.left) || - isPossibleConstructor(node.right) - ); - - case "ConditionalExpression": - return ( - isPossibleConstructor(node.alternate) || - isPossibleConstructor(node.consequent) - ); - - case "SequenceExpression": { - const lastExpression = node.expressions.at(-1); - - return isPossibleConstructor(lastExpression); - } - - default: - return false; - } + if (!node) { + return false; + } + + switch (node.type) { + case "ClassExpression": + case "FunctionExpression": + case "ThisExpression": + case "MemberExpression": + case "CallExpression": + case "NewExpression": + case "ChainExpression": + case "YieldExpression": + case "TaggedTemplateExpression": + case "MetaProperty": + return true; + + case "Identifier": + return node.name !== "undefined"; + + case "AssignmentExpression": + if (["=", "&&="].includes(node.operator)) { + return isPossibleConstructor(node.right); + } + + if (["||=", "??="].includes(node.operator)) { + return ( + isPossibleConstructor(node.left) || + isPossibleConstructor(node.right) + ); + } + + /** + * All other assignment operators are mathematical assignment operators (arithmetic or bitwise). + * An assignment expression with a mathematical operator can either evaluate to a primitive value, + * or throw, depending on the operands. Thus, it cannot evaluate to a constructor function. + */ + return false; + + case "LogicalExpression": + /* + * If the && operator short-circuits, the left side was falsy and therefore not a constructor, and if + * it doesn't short-circuit, it takes the value from the right side, so the right side must always be a + * possible constructor. A future improvement could verify that the left side could be truthy by + * excluding falsy literals. + */ + if (node.operator === "&&") { + return isPossibleConstructor(node.right); + } + + return ( + isPossibleConstructor(node.left) || + isPossibleConstructor(node.right) + ); + + case "ConditionalExpression": + return ( + isPossibleConstructor(node.alternate) || + isPossibleConstructor(node.consequent) + ); + + case "SequenceExpression": { + const lastExpression = node.expressions.at(-1); + + return isPossibleConstructor(lastExpression); + } + + default: + return false; + } } /** * A class to store information about a code path segment. */ class SegmentInfo { - - /** - * Indicates if super() is called in all code paths. - * @type {boolean} - */ - calledInEveryPaths = false; - - /** - * Indicates if super() is called in any code paths. - * @type {boolean} - */ - calledInSomePaths = false; - - /** - * The nodes which have been validated and don't need to be reconsidered. - * @type {ASTNode[]} - */ - validNodes = []; + /** + * Indicates if super() is called in all code paths. + * @type {boolean} + */ + calledInEveryPaths = false; + + /** + * Indicates if super() is called in any code paths. + * @type {boolean} + */ + calledInSomePaths = false; + + /** + * The nodes which have been validated and don't need to be reconsidered. + * @type {ASTNode[]} + */ + validNodes = []; } //------------------------------------------------------------------------------ @@ -133,313 +131,323 @@ class SegmentInfo { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Require `super()` calls in constructors", - recommended: true, - url: "https://eslint.org/docs/latest/rules/constructor-super" - }, - - schema: [], - - messages: { - missingSome: "Lacked a call of 'super()' in some code paths.", - missingAll: "Expected to call 'super()'.", - - duplicate: "Unexpected duplicate 'super()'.", - badSuper: "Unexpected 'super()' because 'super' is not a constructor." - } - }, - - create(context) { - - /* - * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} - * Information for each constructor. - * - upper: Information of the upper constructor. - * - hasExtends: A flag which shows whether own class has a valid `extends` - * part. - * - scope: The scope of own class. - * - codePath: The code path object of the constructor. - */ - let funcInfo = null; - - /** - * @type {Record} - */ - const segInfoMap = Object.create(null); - - /** - * Gets the flag which shows `super()` is called in some paths. - * @param {CodePathSegment} segment A code path segment to get. - * @returns {boolean} The flag which shows `super()` is called in some paths - */ - function isCalledInSomePath(segment) { - return segment.reachable && segInfoMap[segment.id].calledInSomePaths; - } - - /** - * Determines if a segment has been seen in the traversal. - * @param {CodePathSegment} segment A code path segment to check. - * @returns {boolean} `true` if the segment has been seen. - */ - function hasSegmentBeenSeen(segment) { - return !!segInfoMap[segment.id]; - } - - /** - * Gets the flag which shows `super()` is called in all paths. - * @param {CodePathSegment} segment A code path segment to get. - * @returns {boolean} The flag which shows `super()` is called in all paths. - */ - function isCalledInEveryPath(segment) { - return segment.reachable && segInfoMap[segment.id].calledInEveryPaths; - } - - return { - - /** - * Stacks a constructor information. - * @param {CodePath} codePath A code path which was started. - * @param {ASTNode} node The current node. - * @returns {void} - */ - onCodePathStart(codePath, node) { - if (isConstructorFunction(node)) { - - // Class > ClassBody > MethodDefinition > FunctionExpression - const classNode = node.parent.parent.parent; - const superClass = classNode.superClass; - - funcInfo = { - upper: funcInfo, - isConstructor: true, - hasExtends: Boolean(superClass), - superIsConstructor: isPossibleConstructor(superClass), - codePath, - currentSegments: new Set() - }; - } else { - funcInfo = { - upper: funcInfo, - isConstructor: false, - hasExtends: false, - superIsConstructor: false, - codePath, - currentSegments: new Set() - }; - } - }, - - /** - * Pops a constructor information. - * And reports if `super()` lacked. - * @param {CodePath} codePath A code path which was ended. - * @param {ASTNode} node The current node. - * @returns {void} - */ - onCodePathEnd(codePath, node) { - const hasExtends = funcInfo.hasExtends; - - // Pop. - funcInfo = funcInfo.upper; - - if (!hasExtends) { - return; - } - - // Reports if `super()` lacked. - const returnedSegments = codePath.returnedSegments; - const calledInEveryPaths = returnedSegments.every(isCalledInEveryPath); - const calledInSomePaths = returnedSegments.some(isCalledInSomePath); - - if (!calledInEveryPaths) { - context.report({ - messageId: calledInSomePaths - ? "missingSome" - : "missingAll", - node: node.parent - }); - } - }, - - /** - * Initialize information of a given code path segment. - * @param {CodePathSegment} segment A code path segment to initialize. - * @param {CodePathSegment} node Node that starts the segment. - * @returns {void} - */ - onCodePathSegmentStart(segment, node) { - - funcInfo.currentSegments.add(segment); - - if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { - return; - } - - // Initialize info. - const info = segInfoMap[segment.id] = new SegmentInfo(); - - const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); - - // When there are previous segments, aggregates these. - if (seenPrevSegments.length > 0) { - info.calledInSomePaths = seenPrevSegments.some(isCalledInSomePath); - info.calledInEveryPaths = seenPrevSegments.every(isCalledInEveryPath); - } - - /* - * ForStatement > *.update segments are a special case as they are created in advance, - * without seen previous segments. Since they logically don't affect `calledInEveryPaths` - * calculations, and they can never be a lone previous segment of another one, we'll set - * their `calledInEveryPaths` to `true` to effectively ignore them in those calculations. - * . - */ - if (node.parent && node.parent.type === "ForStatement" && node.parent.update === node) { - info.calledInEveryPaths = true; - } - }, - - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - - /** - * Update information of the code path segment when a code path was - * looped. - * @param {CodePathSegment} fromSegment The code path segment of the - * end of a loop. - * @param {CodePathSegment} toSegment A code path segment of the head - * of a loop. - * @returns {void} - */ - onCodePathSegmentLoop(fromSegment, toSegment) { - if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { - return; - } - - funcInfo.codePath.traverseSegments( - { first: toSegment, last: fromSegment }, - (segment, controller) => { - const info = segInfoMap[segment.id]; - - // skip segments after the loop - if (!info) { - controller.skip(); - return; - } - - const seenPrevSegments = segment.prevSegments.filter(hasSegmentBeenSeen); - const calledInSomePreviousPaths = seenPrevSegments.some(isCalledInSomePath); - const calledInEveryPreviousPaths = seenPrevSegments.every(isCalledInEveryPath); - - info.calledInSomePaths ||= calledInSomePreviousPaths; - info.calledInEveryPaths ||= calledInEveryPreviousPaths; - - // If flags become true anew, reports the valid nodes. - if (calledInSomePreviousPaths) { - const nodes = info.validNodes; - - info.validNodes = []; - - for (let i = 0; i < nodes.length; ++i) { - const node = nodes[i]; - - context.report({ - messageId: "duplicate", - node - }); - } - } - } - ); - }, - - /** - * Checks for a call of `super()`. - * @param {ASTNode} node A CallExpression node to check. - * @returns {void} - */ - "CallExpression:exit"(node) { - if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { - return; - } - - // Skips except `super()`. - if (node.callee.type !== "Super") { - return; - } - - // Reports if needed. - const segments = funcInfo.currentSegments; - let duplicate = false; - let info = null; - - for (const segment of segments) { - - if (segment.reachable) { - info = segInfoMap[segment.id]; - - duplicate = duplicate || info.calledInSomePaths; - info.calledInSomePaths = info.calledInEveryPaths = true; - } - } - - if (info) { - if (duplicate) { - context.report({ - messageId: "duplicate", - node - }); - } else if (!funcInfo.superIsConstructor) { - context.report({ - messageId: "badSuper", - node - }); - } else { - info.validNodes.push(node); - } - } - }, - - /** - * Set the mark to the returned path as `super()` was called. - * @param {ASTNode} node A ReturnStatement node to check. - * @returns {void} - */ - ReturnStatement(node) { - if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { - return; - } - - // Skips if no argument. - if (!node.argument) { - return; - } - - // Returning argument is a substitute of 'super()'. - const segments = funcInfo.currentSegments; - - for (const segment of segments) { - - if (segment.reachable) { - const info = segInfoMap[segment.id]; - - info.calledInSomePaths = info.calledInEveryPaths = true; - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Require `super()` calls in constructors", + recommended: true, + url: "https://eslint.org/docs/latest/rules/constructor-super", + }, + + schema: [], + + messages: { + missingSome: "Lacked a call of 'super()' in some code paths.", + missingAll: "Expected to call 'super()'.", + + duplicate: "Unexpected duplicate 'super()'.", + badSuper: + "Unexpected 'super()' because 'super' is not a constructor.", + }, + }, + + create(context) { + /* + * {{hasExtends: boolean, scope: Scope, codePath: CodePath}[]} + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether own class has a valid `extends` + * part. + * - scope: The scope of own class. + * - codePath: The code path object of the constructor. + */ + let funcInfo = null; + + /** + * @type {Record} + */ + const segInfoMap = Object.create(null); + + /** + * Gets the flag which shows `super()` is called in some paths. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in some paths + */ + function isCalledInSomePath(segment) { + return ( + segment.reachable && segInfoMap[segment.id].calledInSomePaths + ); + } + + /** + * Determines if a segment has been seen in the traversal. + * @param {CodePathSegment} segment A code path segment to check. + * @returns {boolean} `true` if the segment has been seen. + */ + function hasSegmentBeenSeen(segment) { + return !!segInfoMap[segment.id]; + } + + /** + * Gets the flag which shows `super()` is called in all paths. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} The flag which shows `super()` is called in all paths. + */ + function isCalledInEveryPath(segment) { + return ( + segment.reachable && segInfoMap[segment.id].calledInEveryPaths + ); + } + + return { + /** + * Stacks a constructor information. + * @param {CodePath} codePath A code path which was started. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + if (isConstructorFunction(node)) { + // Class > ClassBody > MethodDefinition > FunctionExpression + const classNode = node.parent.parent.parent; + const superClass = classNode.superClass; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean(superClass), + superIsConstructor: isPossibleConstructor(superClass), + codePath, + currentSegments: new Set(), + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + superIsConstructor: false, + codePath, + currentSegments: new Set(), + }; + } + }, + + /** + * Pops a constructor information. + * And reports if `super()` lacked. + * @param {CodePath} codePath A code path which was ended. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathEnd(codePath, node) { + const hasExtends = funcInfo.hasExtends; + + // Pop. + funcInfo = funcInfo.upper; + + if (!hasExtends) { + return; + } + + // Reports if `super()` lacked. + const returnedSegments = codePath.returnedSegments; + const calledInEveryPaths = + returnedSegments.every(isCalledInEveryPath); + const calledInSomePaths = + returnedSegments.some(isCalledInSomePath); + + if (!calledInEveryPaths) { + context.report({ + messageId: calledInSomePaths + ? "missingSome" + : "missingAll", + node: node.parent, + }); + } + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment A code path segment to initialize. + * @param {CodePathSegment} node Node that starts the segment. + * @returns {void} + */ + onCodePathSegmentStart(segment, node) { + funcInfo.currentSegments.add(segment); + + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Initialize info. + const info = (segInfoMap[segment.id] = new SegmentInfo()); + + const seenPrevSegments = + segment.prevSegments.filter(hasSegmentBeenSeen); + + // When there are previous segments, aggregates these. + if (seenPrevSegments.length > 0) { + info.calledInSomePaths = + seenPrevSegments.some(isCalledInSomePath); + info.calledInEveryPaths = + seenPrevSegments.every(isCalledInEveryPath); + } + + /* + * ForStatement > *.update segments are a special case as they are created in advance, + * without seen previous segments. Since they logically don't affect `calledInEveryPaths` + * calculations, and they can never be a lone previous segment of another one, we'll set + * their `calledInEveryPaths` to `true` to effectively ignore them in those calculations. + * . + */ + if ( + node.parent && + node.parent.type === "ForStatement" && + node.parent.update === node + ) { + info.calledInEveryPaths = true; + } + }, + + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + funcInfo.codePath.traverseSegments( + { first: toSegment, last: fromSegment }, + (segment, controller) => { + const info = segInfoMap[segment.id]; + + // skip segments after the loop + if (!info) { + controller.skip(); + return; + } + + const seenPrevSegments = + segment.prevSegments.filter(hasSegmentBeenSeen); + const calledInSomePreviousPaths = + seenPrevSegments.some(isCalledInSomePath); + const calledInEveryPreviousPaths = + seenPrevSegments.every(isCalledInEveryPath); + + info.calledInSomePaths ||= calledInSomePreviousPaths; + info.calledInEveryPaths ||= calledInEveryPreviousPaths; + + // If flags become true anew, reports the valid nodes. + if (calledInSomePreviousPaths) { + const nodes = info.validNodes; + + info.validNodes = []; + + for (let i = 0; i < nodes.length; ++i) { + const node = nodes[i]; + + context.report({ + messageId: "duplicate", + node, + }); + } + } + }, + ); + }, + + /** + * Checks for a call of `super()`. + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + "CallExpression:exit"(node) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Skips except `super()`. + if (node.callee.type !== "Super") { + return; + } + + // Reports if needed. + const segments = funcInfo.currentSegments; + let duplicate = false; + let info = null; + + for (const segment of segments) { + if (segment.reachable) { + info = segInfoMap[segment.id]; + + duplicate = duplicate || info.calledInSomePaths; + info.calledInSomePaths = info.calledInEveryPaths = true; + } + } + + if (info) { + if (duplicate) { + context.report({ + messageId: "duplicate", + node, + }); + } else if (!funcInfo.superIsConstructor) { + context.report({ + messageId: "badSuper", + node, + }); + } else { + info.validNodes.push(node); + } + } + }, + + /** + * Set the mark to the returned path as `super()` was called. + * @param {ASTNode} node A ReturnStatement node to check. + * @returns {void} + */ + ReturnStatement(node) { + if (!(funcInfo.isConstructor && funcInfo.hasExtends)) { + return; + } + + // Skips if no argument. + if (!node.argument) { + return; + } + + // Returning argument is a substitute of 'super()'. + const segments = funcInfo.currentSegments; + + for (const segment of segments) { + if (segment.reachable) { + const info = segInfoMap[segment.id]; + + info.calledInSomePaths = info.calledInEveryPaths = true; + } + } + }, + }; + }, }; diff --git a/lib/rules/curly.js b/lib/rules/curly.js index c08b7e39988d..61baae9023eb 100644 --- a/lib/rules/curly.js +++ b/lib/rules/curly.js @@ -16,335 +16,410 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce consistent brace style for all control statements", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/curly" - }, - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["all"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["multi", "multi-line", "multi-or-nest"] - }, - { - enum: ["consistent"] - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - defaultOptions: ["all"], - - fixable: "code", - - messages: { - missingCurlyAfter: "Expected { after '{{name}}'.", - missingCurlyAfterCondition: "Expected { after '{{name}}' condition.", - unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.", - unexpectedCurlyAfterCondition: "Unnecessary { after '{{name}}' condition." - } - }, - - create(context) { - const multiOnly = (context.options[0] === "multi"); - const multiLine = (context.options[0] === "multi-line"); - const multiOrNest = (context.options[0] === "multi-or-nest"); - const consistent = (context.options[1] === "consistent"); - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Determines if a given node is a one-liner that's on the same line as it's preceding code. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code. - * @private - */ - function isCollapsedOneLiner(node) { - const before = sourceCode.getTokenBefore(node); - const last = sourceCode.getLastToken(node); - const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last; - - return before.loc.start.line === lastExcludingSemicolon.loc.end.line; - } - - /** - * Determines if a given node is a one-liner. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a one-liner. - * @private - */ - function isOneLiner(node) { - if (node.type === "EmptyStatement") { - return true; - } - - const first = sourceCode.getFirstToken(node); - const last = sourceCode.getLastToken(node); - const lastExcludingSemicolon = astUtils.isSemicolonToken(last) ? sourceCode.getTokenBefore(last) : last; - - return first.loc.start.line === lastExcludingSemicolon.loc.end.line; - } - - /** - * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError. - * @param {Token} closingBracket The } token - * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block. - */ - function needsSemicolon(closingBracket) { - const tokenBefore = sourceCode.getTokenBefore(closingBracket); - const tokenAfter = sourceCode.getTokenAfter(closingBracket); - const lastBlockNode = sourceCode.getNodeByRangeIndex(tokenBefore.range[0]); - - if (astUtils.isSemicolonToken(tokenBefore)) { - - // If the last statement already has a semicolon, don't add another one. - return false; - } - - if (!tokenAfter) { - - // If there are no statements after this block, there is no need to add a semicolon. - return false; - } - - if (lastBlockNode.type === "BlockStatement" && lastBlockNode.parent.type !== "FunctionExpression" && lastBlockNode.parent.type !== "ArrowFunctionExpression") { - - /* - * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression), - * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause - * a SyntaxError if it was followed by `else`. - */ - return false; - } - - if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) { - - // If the next token is on the same line, insert a semicolon. - return true; - } - - if (/^[([/`+-]/u.test(tokenAfter.value)) { - - // If the next token starts with a character that would disrupt ASI, insert a semicolon. - return true; - } - - if (tokenBefore.type === "Punctuator" && (tokenBefore.value === "++" || tokenBefore.value === "--")) { - - // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI. - return true; - } - - // Otherwise, do not insert a semicolon. - return false; - } - - /** - * Prepares to check the body of a node to see if it's a block statement. - * @param {ASTNode} node The node to report if there's a problem. - * @param {ASTNode} body The body node to check for blocks. - * @param {string} name The name to report if there's a problem. - * @param {{ condition: boolean }} opts Options to pass to the report functions - * @returns {Object} a prepared check object, with "actual", "expected", "check" properties. - * "actual" will be `true` or `false` whether the body is already a block statement. - * "expected" will be `true` or `false` if the body should be a block statement or not, or - * `null` if it doesn't matter, depending on the rule options. It can be modified to change - * the final behavior of "check". - * "check" will be a function reporting appropriate problems depending on the other - * properties. - */ - function prepareCheck(node, body, name, opts) { - const hasBlock = (body.type === "BlockStatement"); - let expected = null; - - if (hasBlock && (body.body.length !== 1 || astUtils.areBracesNecessary(body, sourceCode))) { - expected = true; - } else if (multiOnly) { - expected = false; - } else if (multiLine) { - if (!isCollapsedOneLiner(body)) { - expected = true; - } - - // otherwise, the body is allowed to have braces or not to have braces - - } else if (multiOrNest) { - if (hasBlock) { - const statement = body.body[0]; - const leadingCommentsInBlock = sourceCode.getCommentsBefore(statement); - - expected = !isOneLiner(statement) || leadingCommentsInBlock.length > 0; - } else { - expected = !isOneLiner(body); - } - } else { - - // default "all" - expected = true; - } - - return { - actual: hasBlock, - expected, - check() { - if (this.expected !== null && this.expected !== this.actual) { - if (this.expected) { - context.report({ - node, - loc: body.loc, - messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter", - data: { - name - }, - fix: fixer => fixer.replaceText(body, `{${sourceCode.getText(body)}}`) - }); - } else { - context.report({ - node, - loc: body.loc, - messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter", - data: { - name - }, - fix(fixer) { - - /* - * `do while` expressions sometimes need a space to be inserted after `do`. - * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)` - */ - const needsPrecedingSpace = node.type === "DoWhileStatement" && - sourceCode.getTokenBefore(body).range[1] === body.range[0] && - !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(body, { skip: 1 })); - - const openingBracket = sourceCode.getFirstToken(body); - const closingBracket = sourceCode.getLastToken(body); - const lastTokenInBlock = sourceCode.getTokenBefore(closingBracket); - - if (needsSemicolon(closingBracket)) { - - /* - * If removing braces would cause a SyntaxError due to multiple statements on the same line (or - * change the semantics of the code due to ASI), don't perform a fix. - */ - return null; - } - - const resultingBodyText = sourceCode.getText().slice(openingBracket.range[1], lastTokenInBlock.range[0]) + - sourceCode.getText(lastTokenInBlock) + - sourceCode.getText().slice(lastTokenInBlock.range[1], closingBracket.range[0]); - - return fixer.replaceText(body, (needsPrecedingSpace ? " " : "") + resultingBodyText); - } - }); - } - } - } - }; - } - - /** - * Prepares to check the bodies of a "if", "else if" and "else" chain. - * @param {ASTNode} node The first IfStatement node of the chain. - * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more - * information. - */ - function prepareIfChecks(node) { - const preparedChecks = []; - - for (let currentNode = node; currentNode; currentNode = currentNode.alternate) { - preparedChecks.push(prepareCheck(currentNode, currentNode.consequent, "if", { condition: true })); - if (currentNode.alternate && currentNode.alternate.type !== "IfStatement") { - preparedChecks.push(prepareCheck(currentNode, currentNode.alternate, "else")); - break; - } - } - - if (consistent) { - - /* - * If any node should have or already have braces, make sure they - * all have braces. - * If all nodes shouldn't have braces, make sure they don't. - */ - const expected = preparedChecks.some(preparedCheck => { - if (preparedCheck.expected !== null) { - return preparedCheck.expected; - } - return preparedCheck.actual; - }); - - preparedChecks.forEach(preparedCheck => { - preparedCheck.expected = expected; - }); - } - - return preparedChecks; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - IfStatement(node) { - const parent = node.parent; - const isElseIf = parent.type === "IfStatement" && parent.alternate === node; - - if (!isElseIf) { - - // This is a top `if`, check the whole `if-else-if` chain - prepareIfChecks(node).forEach(preparedCheck => { - preparedCheck.check(); - }); - } - - // Skip `else if`, it's already checked (when the top `if` was visited) - }, - - WhileStatement(node) { - prepareCheck(node, node.body, "while", { condition: true }).check(); - }, - - DoWhileStatement(node) { - prepareCheck(node, node.body, "do").check(); - }, - - ForStatement(node) { - prepareCheck(node, node.body, "for", { condition: true }).check(); - }, - - ForInStatement(node) { - prepareCheck(node, node.body, "for-in").check(); - }, - - ForOfStatement(node) { - prepareCheck(node, node.body, "for-of").check(); - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce consistent brace style for all control statements", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/curly", + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["all"], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["multi", "multi-line", "multi-or-nest"], + }, + { + enum: ["consistent"], + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + defaultOptions: ["all"], + + fixable: "code", + + messages: { + missingCurlyAfter: "Expected { after '{{name}}'.", + missingCurlyAfterCondition: + "Expected { after '{{name}}' condition.", + unexpectedCurlyAfter: "Unnecessary { after '{{name}}'.", + unexpectedCurlyAfterCondition: + "Unnecessary { after '{{name}}' condition.", + }, + }, + + create(context) { + const multiOnly = context.options[0] === "multi"; + const multiLine = context.options[0] === "multi-line"; + const multiOrNest = context.options[0] === "multi-or-nest"; + const consistent = context.options[1] === "consistent"; + + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determines if a given node is a one-liner that's on the same line as it's preceding code. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner that's on the same line as it's preceding code. + * @private + */ + function isCollapsedOneLiner(node) { + const before = sourceCode.getTokenBefore(node); + const last = sourceCode.getLastToken(node); + const lastExcludingSemicolon = astUtils.isSemicolonToken(last) + ? sourceCode.getTokenBefore(last) + : last; + + return ( + before.loc.start.line === lastExcludingSemicolon.loc.end.line + ); + } + + /** + * Determines if a given node is a one-liner. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a one-liner. + * @private + */ + function isOneLiner(node) { + if (node.type === "EmptyStatement") { + return true; + } + + const first = sourceCode.getFirstToken(node); + const last = sourceCode.getLastToken(node); + const lastExcludingSemicolon = astUtils.isSemicolonToken(last) + ? sourceCode.getTokenBefore(last) + : last; + + return first.loc.start.line === lastExcludingSemicolon.loc.end.line; + } + + /** + * Determines if a semicolon needs to be inserted after removing a set of curly brackets, in order to avoid a SyntaxError. + * @param {Token} closingBracket The } token + * @returns {boolean} `true` if a semicolon needs to be inserted after the last statement in the block. + */ + function needsSemicolon(closingBracket) { + const tokenBefore = sourceCode.getTokenBefore(closingBracket); + const tokenAfter = sourceCode.getTokenAfter(closingBracket); + const lastBlockNode = sourceCode.getNodeByRangeIndex( + tokenBefore.range[0], + ); + + if (astUtils.isSemicolonToken(tokenBefore)) { + // If the last statement already has a semicolon, don't add another one. + return false; + } + + if (!tokenAfter) { + // If there are no statements after this block, there is no need to add a semicolon. + return false; + } + + if ( + lastBlockNode.type === "BlockStatement" && + lastBlockNode.parent.type !== "FunctionExpression" && + lastBlockNode.parent.type !== "ArrowFunctionExpression" + ) { + /* + * If the last node surrounded by curly brackets is a BlockStatement (other than a FunctionExpression or an ArrowFunctionExpression), + * don't insert a semicolon. Otherwise, the semicolon would be parsed as a separate statement, which would cause + * a SyntaxError if it was followed by `else`. + */ + return false; + } + + if (tokenBefore.loc.end.line === tokenAfter.loc.start.line) { + // If the next token is on the same line, insert a semicolon. + return true; + } + + if (/^[([/`+-]/u.test(tokenAfter.value)) { + // If the next token starts with a character that would disrupt ASI, insert a semicolon. + return true; + } + + if ( + tokenBefore.type === "Punctuator" && + (tokenBefore.value === "++" || tokenBefore.value === "--") + ) { + // If the last token is ++ or --, insert a semicolon to avoid disrupting ASI. + return true; + } + + // Otherwise, do not insert a semicolon. + return false; + } + + /** + * Prepares to check the body of a node to see if it's a block statement. + * @param {ASTNode} node The node to report if there's a problem. + * @param {ASTNode} body The body node to check for blocks. + * @param {string} name The name to report if there's a problem. + * @param {{ condition: boolean }} opts Options to pass to the report functions + * @returns {Object} a prepared check object, with "actual", "expected", "check" properties. + * "actual" will be `true` or `false` whether the body is already a block statement. + * "expected" will be `true` or `false` if the body should be a block statement or not, or + * `null` if it doesn't matter, depending on the rule options. It can be modified to change + * the final behavior of "check". + * "check" will be a function reporting appropriate problems depending on the other + * properties. + */ + function prepareCheck(node, body, name, opts) { + const hasBlock = body.type === "BlockStatement"; + let expected = null; + + if ( + hasBlock && + (body.body.length !== 1 || + astUtils.areBracesNecessary(body, sourceCode)) + ) { + expected = true; + } else if (multiOnly) { + expected = false; + } else if (multiLine) { + if (!isCollapsedOneLiner(body)) { + expected = true; + } + + // otherwise, the body is allowed to have braces or not to have braces + } else if (multiOrNest) { + if (hasBlock) { + const statement = body.body[0]; + const leadingCommentsInBlock = + sourceCode.getCommentsBefore(statement); + + expected = + !isOneLiner(statement) || + leadingCommentsInBlock.length > 0; + } else { + expected = !isOneLiner(body); + } + } else { + // default "all" + expected = true; + } + + return { + actual: hasBlock, + expected, + check() { + if ( + this.expected !== null && + this.expected !== this.actual + ) { + if (this.expected) { + context.report({ + node, + loc: body.loc, + messageId: + opts && opts.condition + ? "missingCurlyAfterCondition" + : "missingCurlyAfter", + data: { + name, + }, + fix: fixer => + fixer.replaceText( + body, + `{${sourceCode.getText(body)}}`, + ), + }); + } else { + context.report({ + node, + loc: body.loc, + messageId: + opts && opts.condition + ? "unexpectedCurlyAfterCondition" + : "unexpectedCurlyAfter", + data: { + name, + }, + fix(fixer) { + /* + * `do while` expressions sometimes need a space to be inserted after `do`. + * e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)` + */ + const needsPrecedingSpace = + node.type === "DoWhileStatement" && + sourceCode.getTokenBefore(body) + .range[1] === body.range[0] && + !astUtils.canTokensBeAdjacent( + "do", + sourceCode.getFirstToken(body, { + skip: 1, + }), + ); + + const openingBracket = + sourceCode.getFirstToken(body); + const closingBracket = + sourceCode.getLastToken(body); + const lastTokenInBlock = + sourceCode.getTokenBefore( + closingBracket, + ); + + if (needsSemicolon(closingBracket)) { + /* + * If removing braces would cause a SyntaxError due to multiple statements on the same line (or + * change the semantics of the code due to ASI), don't perform a fix. + */ + return null; + } + + const resultingBodyText = + sourceCode + .getText() + .slice( + openingBracket.range[1], + lastTokenInBlock.range[0], + ) + + sourceCode.getText(lastTokenInBlock) + + sourceCode + .getText() + .slice( + lastTokenInBlock.range[1], + closingBracket.range[0], + ); + + return fixer.replaceText( + body, + (needsPrecedingSpace ? " " : "") + + resultingBodyText, + ); + }, + }); + } + } + }, + }; + } + + /** + * Prepares to check the bodies of a "if", "else if" and "else" chain. + * @param {ASTNode} node The first IfStatement node of the chain. + * @returns {Object[]} prepared checks for each body of the chain. See `prepareCheck` for more + * information. + */ + function prepareIfChecks(node) { + const preparedChecks = []; + + for ( + let currentNode = node; + currentNode; + currentNode = currentNode.alternate + ) { + preparedChecks.push( + prepareCheck(currentNode, currentNode.consequent, "if", { + condition: true, + }), + ); + if ( + currentNode.alternate && + currentNode.alternate.type !== "IfStatement" + ) { + preparedChecks.push( + prepareCheck( + currentNode, + currentNode.alternate, + "else", + ), + ); + break; + } + } + + if (consistent) { + /* + * If any node should have or already have braces, make sure they + * all have braces. + * If all nodes shouldn't have braces, make sure they don't. + */ + const expected = preparedChecks.some(preparedCheck => { + if (preparedCheck.expected !== null) { + return preparedCheck.expected; + } + return preparedCheck.actual; + }); + + preparedChecks.forEach(preparedCheck => { + preparedCheck.expected = expected; + }); + } + + return preparedChecks; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + IfStatement(node) { + const parent = node.parent; + const isElseIf = + parent.type === "IfStatement" && parent.alternate === node; + + if (!isElseIf) { + // This is a top `if`, check the whole `if-else-if` chain + prepareIfChecks(node).forEach(preparedCheck => { + preparedCheck.check(); + }); + } + + // Skip `else if`, it's already checked (when the top `if` was visited) + }, + + WhileStatement(node) { + prepareCheck(node, node.body, "while", { + condition: true, + }).check(); + }, + + DoWhileStatement(node) { + prepareCheck(node, node.body, "do").check(); + }, + + ForStatement(node) { + prepareCheck(node, node.body, "for", { + condition: true, + }).check(); + }, + + ForInStatement(node) { + prepareCheck(node, node.body, "for-in").check(); + }, + + ForOfStatement(node) { + prepareCheck(node, node.body, "for-of").check(); + }, + }; + }, }; diff --git a/lib/rules/default-case-last.js b/lib/rules/default-case-last.js index 3a47078c8ca2..12f13cdf7448 100644 --- a/lib/rules/default-case-last.js +++ b/lib/rules/default-case-last.js @@ -11,34 +11,41 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce `default` clauses in `switch` statements to be last", - recommended: false, - url: "https://eslint.org/docs/latest/rules/default-case-last" - }, - - schema: [], - - messages: { - notLast: "Default clause should be the last clause." - } - }, - - create(context) { - return { - SwitchStatement(node) { - const cases = node.cases, - indexOfDefault = cases.findIndex(c => c.test === null); - - if (indexOfDefault !== -1 && indexOfDefault !== cases.length - 1) { - const defaultClause = cases[indexOfDefault]; - - context.report({ node: defaultClause, messageId: "notLast" }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce `default` clauses in `switch` statements to be last", + recommended: false, + url: "https://eslint.org/docs/latest/rules/default-case-last", + }, + + schema: [], + + messages: { + notLast: "Default clause should be the last clause.", + }, + }, + + create(context) { + return { + SwitchStatement(node) { + const cases = node.cases, + indexOfDefault = cases.findIndex(c => c.test === null); + + if ( + indexOfDefault !== -1 && + indexOfDefault !== cases.length - 1 + ) { + const defaultClause = cases[indexOfDefault]; + + context.report({ + node: defaultClause, + messageId: "notLast", + }); + } + }, + }; + }, }; diff --git a/lib/rules/default-case.js b/lib/rules/default-case.js index 9fa7acd30480..e8bc56a62f1b 100644 --- a/lib/rules/default-case.js +++ b/lib/rules/default-case.js @@ -12,88 +12,92 @@ const DEFAULT_COMMENT_PATTERN = /^no default$/iu; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{}], - - docs: { - description: "Require `default` cases in `switch` statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/default-case" - }, - - schema: [{ - type: "object", - properties: { - commentPattern: { - type: "string" - } - }, - additionalProperties: false - }], - - messages: { - missingDefaultCase: "Expected a default case." - } - }, - - create(context) { - const [options] = context.options; - const commentPattern = options.commentPattern - ? new RegExp(options.commentPattern, "u") - : DEFAULT_COMMENT_PATTERN; - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Shortcut to get last element of array - * @param {*[]} collection Array - * @returns {any} Last element - */ - function last(collection) { - return collection.at(-1); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - SwitchStatement(node) { - - if (!node.cases.length) { - - /* - * skip check of empty switch because there is no easy way - * to extract comments inside it now - */ - return; - } - - const hasDefault = node.cases.some(v => v.test === null); - - if (!hasDefault) { - - let comment; - - const lastCase = last(node.cases); - const comments = sourceCode.getCommentsAfter(lastCase); - - if (comments.length) { - comment = last(comments); - } - - if (!comment || !commentPattern.test(comment.value.trim())) { - context.report({ node, messageId: "missingDefaultCase" }); - } - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{}], + + docs: { + description: "Require `default` cases in `switch` statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/default-case", + }, + + schema: [ + { + type: "object", + properties: { + commentPattern: { + type: "string", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missingDefaultCase: "Expected a default case.", + }, + }, + + create(context) { + const [options] = context.options; + const commentPattern = options.commentPattern + ? new RegExp(options.commentPattern, "u") + : DEFAULT_COMMENT_PATTERN; + + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Shortcut to get last element of array + * @param {*[]} collection Array + * @returns {any} Last element + */ + function last(collection) { + return collection.at(-1); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + SwitchStatement(node) { + if (!node.cases.length) { + /* + * skip check of empty switch because there is no easy way + * to extract comments inside it now + */ + return; + } + + const hasDefault = node.cases.some(v => v.test === null); + + if (!hasDefault) { + let comment; + + const lastCase = last(node.cases); + const comments = sourceCode.getCommentsAfter(lastCase); + + if (comments.length) { + comment = last(comments); + } + + if ( + !comment || + !commentPattern.test(comment.value.trim()) + ) { + context.report({ + node, + messageId: "missingDefaultCase", + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/default-param-last.js b/lib/rules/default-param-last.js index e1260c17cf7b..b6c8bca8a245 100644 --- a/lib/rules/default-param-last.js +++ b/lib/rules/default-param-last.js @@ -5,59 +5,74 @@ "use strict"; +/** + * Checks if node is required: i.e. does not have a default value or ? optional indicator. + * @param {ASTNode} node the node to be evaluated + * @returns {boolean} true if the node is required, false if not. + */ +function isRequiredParameter(node) { + return !( + node.type === "AssignmentPattern" || + node.type === "RestElement" || + node.optional + ); +} + /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce default parameters to be last", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/default-param-last" - }, - - schema: [], - - messages: { - shouldBeLast: "Default parameters should be last." - } - }, - - create(context) { - - /** - * Handler for function contexts. - * @param {ASTNode} node function node - * @returns {void} - */ - function handleFunction(node) { - let hasSeenPlainParam = false; - - for (let i = node.params.length - 1; i >= 0; i -= 1) { - const param = node.params[i]; - - if ( - param.type !== "AssignmentPattern" && - param.type !== "RestElement" - ) { - hasSeenPlainParam = true; - continue; - } - - if (hasSeenPlainParam && param.type === "AssignmentPattern") { - context.report({ - node: param, - messageId: "shouldBeLast" - }); - } - } - } - - return { - FunctionDeclaration: handleFunction, - FunctionExpression: handleFunction, - ArrowFunctionExpression: handleFunction - }; - } + meta: { + 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", + }, + + schema: [], + + messages: { + shouldBeLast: "Default parameters should be last.", + }, + }, + + create(context) { + /** + * Handler for function contexts. + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { + let hasSeenRequiredParameter = false; + + for (let i = node.params.length - 1; i >= 0; i -= 1) { + const current = node.params[i]; + const param = + current.type === "TSParameterProperty" + ? current.parameter + : current; + + if (isRequiredParameter(param)) { + hasSeenRequiredParameter = true; + continue; + } + + if (hasSeenRequiredParameter) { + context.report({ + node: current, + messageId: "shouldBeLast", + }); + } + } + } + + return { + FunctionDeclaration: handleFunction, + FunctionExpression: handleFunction, + ArrowFunctionExpression: handleFunction, + }; + }, }; diff --git a/lib/rules/dot-location.js b/lib/rules/dot-location.js index 39b8a92b607a..d85d293121c5 100644 --- a/lib/rules/dot-location.js +++ b/lib/rules/dot-location.js @@ -14,113 +14,125 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "dot-location", - url: "https://eslint.style/rules/js/dot-location" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent newlines before and after dots", - recommended: false, - url: "https://eslint.org/docs/latest/rules/dot-location" - }, - - schema: [ - { - enum: ["object", "property"] - } - ], - - fixable: "code", - - messages: { - expectedDotAfterObject: "Expected dot to be on same line as object.", - expectedDotBeforeProperty: "Expected dot to be on same line as property." - } - }, - - create(context) { - - const config = context.options[0]; - - // default to onObject if no preference is passed - const onObject = config === "object" || !config; - - const sourceCode = context.sourceCode; - - /** - * Reports if the dot between object and property is on the correct location. - * @param {ASTNode} node The `MemberExpression` node. - * @returns {void} - */ - function checkDotLocation(node) { - const property = node.property; - const dotToken = sourceCode.getTokenBefore(property); - - if (onObject) { - - // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node. - const tokenBeforeDot = sourceCode.getTokenBefore(dotToken); - - if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) { - context.report({ - node, - loc: dotToken.loc, - messageId: "expectedDotAfterObject", - *fix(fixer) { - if (dotToken.value.startsWith(".") && astUtils.isDecimalIntegerNumericToken(tokenBeforeDot)) { - yield fixer.insertTextAfter(tokenBeforeDot, ` ${dotToken.value}`); - } else { - yield fixer.insertTextAfter(tokenBeforeDot, dotToken.value); - } - yield fixer.remove(dotToken); - } - }); - } - } else if (!astUtils.isTokenOnSameLine(dotToken, property)) { - context.report({ - node, - loc: dotToken.loc, - messageId: "expectedDotBeforeProperty", - *fix(fixer) { - yield fixer.remove(dotToken); - yield fixer.insertTextBefore(property, dotToken.value); - } - }); - } - } - - /** - * Checks the spacing of the dot within a member expression. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNode(node) { - if (!node.computed) { - checkDotLocation(node); - } - } - - return { - MemberExpression: checkNode - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "dot-location", + url: "https://eslint.style/rules/js/dot-location", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent newlines before and after dots", + recommended: false, + url: "https://eslint.org/docs/latest/rules/dot-location", + }, + + schema: [ + { + enum: ["object", "property"], + }, + ], + + fixable: "code", + + messages: { + expectedDotAfterObject: + "Expected dot to be on same line as object.", + expectedDotBeforeProperty: + "Expected dot to be on same line as property.", + }, + }, + + create(context) { + const config = context.options[0]; + + // default to onObject if no preference is passed + const onObject = config === "object" || !config; + + const sourceCode = context.sourceCode; + + /** + * Reports if the dot between object and property is on the correct location. + * @param {ASTNode} node The `MemberExpression` node. + * @returns {void} + */ + function checkDotLocation(node) { + const property = node.property; + const dotToken = sourceCode.getTokenBefore(property); + + if (onObject) { + // `obj` expression can be parenthesized, but those paren tokens are not a part of the `obj` node. + const tokenBeforeDot = sourceCode.getTokenBefore(dotToken); + + if (!astUtils.isTokenOnSameLine(tokenBeforeDot, dotToken)) { + context.report({ + node, + loc: dotToken.loc, + messageId: "expectedDotAfterObject", + *fix(fixer) { + if ( + dotToken.value.startsWith(".") && + astUtils.isDecimalIntegerNumericToken( + tokenBeforeDot, + ) + ) { + yield fixer.insertTextAfter( + tokenBeforeDot, + ` ${dotToken.value}`, + ); + } else { + yield fixer.insertTextAfter( + tokenBeforeDot, + dotToken.value, + ); + } + yield fixer.remove(dotToken); + }, + }); + } + } else if (!astUtils.isTokenOnSameLine(dotToken, property)) { + context.report({ + node, + loc: dotToken.loc, + messageId: "expectedDotBeforeProperty", + *fix(fixer) { + yield fixer.remove(dotToken); + yield fixer.insertTextBefore(property, dotToken.value); + }, + }); + } + } + + /** + * Checks the spacing of the dot within a member expression. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + if (!node.computed) { + checkDotLocation(node); + } + } + + return { + MemberExpression: checkNode, + }; + }, }; diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js index 30537ae72f42..889773292817 100644 --- a/lib/rules/dot-notation.js +++ b/lib/rules/dot-notation.js @@ -22,159 +22,195 @@ const literalTypesToCheck = new Set(["string", "boolean"]); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - allowKeywords: { - type: "boolean" - }, - allowPattern: { - type: "string" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - useDot: "[{{key}}] is better written in dot notation.", - useBrackets: ".{{key}} is a syntax error." - } - }, - - create(context) { - const [options] = context.options; - const allowKeywords = options.allowKeywords; - const sourceCode = context.sourceCode; - - let allowPattern; - - if (options.allowPattern) { - allowPattern = new RegExp(options.allowPattern, "u"); - } - - /** - * Check if the property is valid dot notation - * @param {ASTNode} node The dot notation node - * @param {string} value Value which is to be checked - * @returns {void} - */ - function checkComputedProperty(node, value) { - if ( - validIdentifier.test(value) && - (allowKeywords || !keywords.includes(String(value))) && - !(allowPattern && allowPattern.test(value)) - ) { - const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``; - - context.report({ - node: node.property, - messageId: "useDot", - data: { - key: formattedValue - }, - *fix(fixer) { - const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken); - const rightBracket = sourceCode.getLastToken(node); - const nextToken = sourceCode.getTokenAfter(node); - - // Don't perform any fixes if there are comments inside the brackets. - if (sourceCode.commentsExistBetween(leftBracket, rightBracket)) { - return; - } - - // Replace the brackets by an identifier. - if (!node.optional) { - yield fixer.insertTextBefore( - leftBracket, - astUtils.isDecimalInteger(node.object) ? " ." : "." - ); - } - yield fixer.replaceTextRange( - [leftBracket.range[0], rightBracket.range[1]], - value - ); - - // Insert a space after the property if it will be connected to the next token. - if ( - nextToken && - rightBracket.range[1] === nextToken.range[0] && - !astUtils.canTokensBeAdjacent(String(value), nextToken) - ) { - yield fixer.insertTextAfter(node, " "); - } - } - }); - } - } - - return { - MemberExpression(node) { - if ( - node.computed && - node.property.type === "Literal" && - (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property)) - ) { - checkComputedProperty(node, node.property.value); - } - if ( - node.computed && - astUtils.isStaticTemplateLiteral(node.property) - ) { - checkComputedProperty(node, node.property.quasis[0].value.cooked); - } - if ( - !allowKeywords && - !node.computed && - node.property.type === "Identifier" && - keywords.includes(String(node.property.name)) - ) { - context.report({ - node: node.property, - messageId: "useBrackets", - data: { - key: node.property.name - }, - *fix(fixer) { - const dotToken = sourceCode.getTokenBefore(node.property); - - // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression. - if (node.object.type === "Identifier" && node.object.name === "let" && !node.optional) { - return; - } - - // Don't perform any fixes if there are comments between the dot and the property name. - if (sourceCode.commentsExistBetween(dotToken, node.property)) { - return; - } - - // Replace the identifier to brackets. - if (!node.optional) { - yield fixer.remove(dotToken); - } - yield fixer.replaceText(node.property, `["${node.property.name}"]`); - } - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + allowKeywords: { + type: "boolean", + }, + allowPattern: { + type: "string", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + useDot: "[{{key}}] is better written in dot notation.", + useBrackets: ".{{key}} is a syntax error.", + }, + }, + + create(context) { + const [options] = context.options; + const allowKeywords = options.allowKeywords; + const sourceCode = context.sourceCode; + + let allowPattern; + + if (options.allowPattern) { + allowPattern = new RegExp(options.allowPattern, "u"); + } + + /** + * Check if the property is valid dot notation + * @param {ASTNode} node The dot notation node + * @param {string} value Value which is to be checked + * @returns {void} + */ + function checkComputedProperty(node, value) { + if ( + validIdentifier.test(value) && + (allowKeywords || !keywords.includes(String(value))) && + !(allowPattern && allowPattern.test(value)) + ) { + const formattedValue = + node.property.type === "Literal" + ? JSON.stringify(value) + : `\`${value}\``; + + context.report({ + node: node.property, + messageId: "useDot", + data: { + key: formattedValue, + }, + *fix(fixer) { + const leftBracket = sourceCode.getTokenAfter( + node.object, + astUtils.isOpeningBracketToken, + ); + const rightBracket = sourceCode.getLastToken(node); + const nextToken = sourceCode.getTokenAfter(node); + + // Don't perform any fixes if there are comments inside the brackets. + if ( + sourceCode.commentsExistBetween( + leftBracket, + rightBracket, + ) + ) { + return; + } + + // Replace the brackets by an identifier. + if (!node.optional) { + yield fixer.insertTextBefore( + leftBracket, + astUtils.isDecimalInteger(node.object) + ? " ." + : ".", + ); + } + yield fixer.replaceTextRange( + [leftBracket.range[0], rightBracket.range[1]], + value, + ); + + // Insert a space after the property if it will be connected to the next token. + if ( + nextToken && + rightBracket.range[1] === nextToken.range[0] && + !astUtils.canTokensBeAdjacent( + String(value), + nextToken, + ) + ) { + yield fixer.insertTextAfter(node, " "); + } + }, + }); + } + } + + return { + MemberExpression(node) { + if ( + node.computed && + node.property.type === "Literal" && + (literalTypesToCheck.has(typeof node.property.value) || + astUtils.isNullLiteral(node.property)) + ) { + checkComputedProperty(node, node.property.value); + } + if ( + node.computed && + astUtils.isStaticTemplateLiteral(node.property) + ) { + checkComputedProperty( + node, + node.property.quasis[0].value.cooked, + ); + } + if ( + !allowKeywords && + !node.computed && + node.property.type === "Identifier" && + keywords.includes(String(node.property.name)) + ) { + context.report({ + node: node.property, + messageId: "useBrackets", + data: { + key: node.property.name, + }, + *fix(fixer) { + const dotToken = sourceCode.getTokenBefore( + node.property, + ); + + // A statement that starts with `let[` is parsed as a destructuring variable declaration, not a MemberExpression. + if ( + node.object.type === "Identifier" && + node.object.name === "let" && + !node.optional + ) { + return; + } + + // Don't perform any fixes if there are comments between the dot and the property name. + if ( + sourceCode.commentsExistBetween( + dotToken, + node.property, + ) + ) { + return; + } + + // Replace the identifier to brackets. + if (!node.optional) { + yield fixer.remove(dotToken); + } + yield fixer.replaceText( + node.property, + `["${node.property.name}"]`, + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/eol-last.js b/lib/rules/eol-last.js index 45ff5937c87a..eef0cfc25de7 100644 --- a/lib/rules/eol-last.js +++ b/lib/rules/eol-last.js @@ -11,123 +11,125 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "eol-last", - url: "https://eslint.style/rules/js/eol-last" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow newline at the end of files", - recommended: false, - url: "https://eslint.org/docs/latest/rules/eol-last" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never", "unix", "windows"] - } - ], - - messages: { - missing: "Newline required at end of file but not found.", - unexpected: "Newline not allowed at end of file." - } - }, - create(context) { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program: function checkBadEOF(node) { - const sourceCode = context.sourceCode, - src = sourceCode.getText(), - lastLine = sourceCode.lines.at(-1), - location = { - column: lastLine.length, - line: sourceCode.lines.length - }, - LF = "\n", - CRLF = `\r${LF}`, - endsWithNewline = src.endsWith(LF); - - /* - * Empty source is always valid: No content in file so we don't - * need to lint for a newline on the last line of content. - */ - if (!src.length) { - return; - } - - let mode = context.options[0] || "always", - appendCRLF = false; - - if (mode === "unix") { - - // `"unix"` should behave exactly as `"always"` - mode = "always"; - } - if (mode === "windows") { - - // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility - mode = "always"; - appendCRLF = true; - } - if (mode === "always" && !endsWithNewline) { - - // File is not newline-terminated, but should be - context.report({ - node, - loc: location, - messageId: "missing", - fix(fixer) { - return fixer.insertTextAfterRange([0, src.length], appendCRLF ? CRLF : LF); - } - }); - } else if (mode === "never" && endsWithNewline) { - - const secondLastLine = sourceCode.lines.at(-2); - - // File is newline-terminated, but shouldn't be - context.report({ - node, - loc: { - start: { line: sourceCode.lines.length - 1, column: secondLastLine.length }, - end: { line: sourceCode.lines.length, column: 0 } - }, - messageId: "unexpected", - fix(fixer) { - const finalEOLs = /(?:\r?\n)+$/u, - match = finalEOLs.exec(sourceCode.text), - start = match.index, - end = sourceCode.text.length; - - return fixer.replaceTextRange([start, end], ""); - } - }); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "eol-last", + url: "https://eslint.style/rules/js/eol-last", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require or disallow newline at the end of files", + recommended: false, + url: "https://eslint.org/docs/latest/rules/eol-last", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never", "unix", "windows"], + }, + ], + + messages: { + missing: "Newline required at end of file but not found.", + unexpected: "Newline not allowed at end of file.", + }, + }, + create(context) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkBadEOF(node) { + const sourceCode = context.sourceCode, + src = sourceCode.getText(), + lastLine = sourceCode.lines.at(-1), + location = { + column: lastLine.length, + line: sourceCode.lines.length, + }, + LF = "\n", + CRLF = `\r${LF}`, + endsWithNewline = src.endsWith(LF); + + /* + * Empty source is always valid: No content in file so we don't + * need to lint for a newline on the last line of content. + */ + if (!src.length) { + return; + } + + let mode = context.options[0] || "always", + appendCRLF = false; + + if (mode === "unix") { + // `"unix"` should behave exactly as `"always"` + mode = "always"; + } + if (mode === "windows") { + // `"windows"` should behave exactly as `"always"`, but append CRLF in the fixer for backwards compatibility + mode = "always"; + appendCRLF = true; + } + if (mode === "always" && !endsWithNewline) { + // File is not newline-terminated, but should be + context.report({ + node, + loc: location, + messageId: "missing", + fix(fixer) { + return fixer.insertTextAfterRange( + [0, src.length], + appendCRLF ? CRLF : LF, + ); + }, + }); + } else if (mode === "never" && endsWithNewline) { + const secondLastLine = sourceCode.lines.at(-2); + + // File is newline-terminated, but shouldn't be + context.report({ + node, + loc: { + start: { + line: sourceCode.lines.length - 1, + column: secondLastLine.length, + }, + end: { line: sourceCode.lines.length, column: 0 }, + }, + messageId: "unexpected", + fix(fixer) { + const finalEOLs = /(?:\r?\n)+$/u, + match = finalEOLs.exec(sourceCode.text), + start = match.index, + end = sourceCode.text.length; + + return fixer.replaceTextRange([start, end], ""); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/eqeqeq.js b/lib/rules/eqeqeq.js index 12b1e805ec13..bc6d77a97572 100644 --- a/lib/rules/eqeqeq.js +++ b/lib/rules/eqeqeq.js @@ -17,158 +17,171 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require the use of `===` and `!==`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/eqeqeq" - }, - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always"] - }, - { - type: "object", - properties: { - null: { - enum: ["always", "never", "ignore"] - } - }, - additionalProperties: false - } - ], - additionalItems: false - }, - { - type: "array", - items: [ - { - enum: ["smart", "allow-null"] - } - ], - additionalItems: false - } - ] - }, - - fixable: "code", - - messages: { - unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'." - } - }, - - create(context) { - const config = context.options[0] || "always"; - const options = context.options[1] || {}; - const sourceCode = context.sourceCode; - - const nullOption = (config === "always") - ? options.null || "always" - : "ignore"; - const enforceRuleForNull = (nullOption === "always"); - const enforceInverseRuleForNull = (nullOption === "never"); - - /** - * Checks if an expression is a typeof expression - * @param {ASTNode} node The node to check - * @returns {boolean} if the node is a typeof expression - */ - function isTypeOf(node) { - return node.type === "UnaryExpression" && node.operator === "typeof"; - } - - /** - * Checks if either operand of a binary expression is a typeof operation - * @param {ASTNode} node The node to check - * @returns {boolean} if one of the operands is typeof - * @private - */ - function isTypeOfBinary(node) { - return isTypeOf(node.left) || isTypeOf(node.right); - } - - /** - * Checks if operands are literals of the same type (via typeof) - * @param {ASTNode} node The node to check - * @returns {boolean} if operands are of same type - * @private - */ - function areLiteralsAndSameType(node) { - return node.left.type === "Literal" && node.right.type === "Literal" && - typeof node.left.value === typeof node.right.value; - } - - /** - * Checks if one of the operands is a literal null - * @param {ASTNode} node The node to check - * @returns {boolean} if operands are null - * @private - */ - function isNullCheck(node) { - return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left); - } - - /** - * Reports a message for this rule. - * @param {ASTNode} node The binary expression node that was checked - * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==') - * @returns {void} - * @private - */ - function report(node, expectedOperator) { - const operatorToken = sourceCode.getFirstTokenBetween( - node.left, - node.right, - token => token.value === node.operator - ); - - context.report({ - node, - loc: operatorToken.loc, - messageId: "unexpected", - data: { expectedOperator, actualOperator: node.operator }, - fix(fixer) { - - // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix. - if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) { - return fixer.replaceText(operatorToken, expectedOperator); - } - return null; - } - }); - } - - return { - BinaryExpression(node) { - const isNull = isNullCheck(node); - - if (node.operator !== "==" && node.operator !== "!=") { - if (enforceInverseRuleForNull && isNull) { - report(node, node.operator.slice(0, -1)); - } - return; - } - - if (config === "smart" && (isTypeOfBinary(node) || - areLiteralsAndSameType(node) || isNull)) { - return; - } - - if (!enforceRuleForNull && isNull) { - return; - } - - report(node, `${node.operator}=`); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Require the use of `===` and `!==`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/eqeqeq", + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always"], + }, + { + type: "object", + properties: { + null: { + enum: ["always", "never", "ignore"], + }, + }, + additionalProperties: false, + }, + ], + additionalItems: false, + }, + { + type: "array", + items: [ + { + enum: ["smart", "allow-null"], + }, + ], + additionalItems: false, + }, + ], + }, + + fixable: "code", + + messages: { + unexpected: + "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'.", + }, + }, + + create(context) { + const config = context.options[0] || "always"; + const options = context.options[1] || {}; + const sourceCode = context.sourceCode; + + const nullOption = + config === "always" ? options.null || "always" : "ignore"; + const enforceRuleForNull = nullOption === "always"; + const enforceInverseRuleForNull = nullOption === "never"; + + /** + * Checks if an expression is a typeof expression + * @param {ASTNode} node The node to check + * @returns {boolean} if the node is a typeof expression + */ + function isTypeOf(node) { + return ( + node.type === "UnaryExpression" && node.operator === "typeof" + ); + } + + /** + * Checks if either operand of a binary expression is a typeof operation + * @param {ASTNode} node The node to check + * @returns {boolean} if one of the operands is typeof + * @private + */ + function isTypeOfBinary(node) { + return isTypeOf(node.left) || isTypeOf(node.right); + } + + /** + * Checks if operands are literals of the same type (via typeof) + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are of same type + * @private + */ + function areLiteralsAndSameType(node) { + return ( + node.left.type === "Literal" && + node.right.type === "Literal" && + typeof node.left.value === typeof node.right.value + ); + } + + /** + * Checks if one of the operands is a literal null + * @param {ASTNode} node The node to check + * @returns {boolean} if operands are null + * @private + */ + function isNullCheck(node) { + return ( + astUtils.isNullLiteral(node.right) || + astUtils.isNullLiteral(node.left) + ); + } + + /** + * Reports a message for this rule. + * @param {ASTNode} node The binary expression node that was checked + * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==') + * @returns {void} + * @private + */ + function report(node, expectedOperator) { + const operatorToken = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + + context.report({ + node, + loc: operatorToken.loc, + messageId: "unexpected", + data: { expectedOperator, actualOperator: node.operator }, + fix(fixer) { + // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix. + if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) { + return fixer.replaceText( + operatorToken, + expectedOperator, + ); + } + return null; + }, + }); + } + + return { + BinaryExpression(node) { + const isNull = isNullCheck(node); + + if (node.operator !== "==" && node.operator !== "!=") { + if (enforceInverseRuleForNull && isNull) { + report(node, node.operator.slice(0, -1)); + } + return; + } + + if ( + config === "smart" && + (isTypeOfBinary(node) || + areLiteralsAndSameType(node) || + isNull) + ) { + return; + } + + if (!enforceRuleForNull && isNull) { + return; + } + + report(node, `${node.operator}=`); + }, + }; + }, }; diff --git a/lib/rules/for-direction.js b/lib/rules/for-direction.js index f386d83df9c8..29c5bcbb1d10 100644 --- a/lib/rules/for-direction.js +++ b/lib/rules/for-direction.js @@ -17,124 +17,149 @@ const { getStaticValue } = require("@eslint-community/eslint-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - 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, - schema: [], - - messages: { - incorrectDirection: "The update clause in this loop moves the variable in the wrong direction." - } - }, - - create(context) { - const { sourceCode } = context; - - /** - * report an error. - * @param {ASTNode} node the node to report. - * @returns {void} - */ - function report(node) { - context.report({ - node, - messageId: "incorrectDirection" - }); - } - - /** - * check the right side of the assignment - * @param {ASTNode} update UpdateExpression to check - * @param {int} dir expected direction that could either be turned around or invalidated - * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear - */ - function getRightDirection(update, dir) { - const staticValue = getStaticValue(update.right, sourceCode.getScope(update)); - - if (staticValue && ["bigint", "boolean", "number"].includes(typeof staticValue.value)) { - const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0 - - return dir * sign; - } - return 0; - } - - /** - * check UpdateExpression add/sub the counter - * @param {ASTNode} update UpdateExpression to check - * @param {string} counter variable name to check - * @returns {int} if add return 1, if sub return -1, if nochange, return 0 - */ - function getUpdateDirection(update, counter) { - if (update.argument.type === "Identifier" && update.argument.name === counter) { - if (update.operator === "++") { - return 1; - } - if (update.operator === "--") { - return -1; - } - } - return 0; - } - - /** - * check AssignmentExpression add/sub the counter - * @param {ASTNode} update AssignmentExpression to check - * @param {string} counter variable name to check - * @returns {int} if add return 1, if sub return -1, if nochange, return 0 - */ - function getAssignmentDirection(update, counter) { - if (update.left.name === counter) { - if (update.operator === "+=") { - return getRightDirection(update, 1); - } - if (update.operator === "-=") { - return getRightDirection(update, -1); - } - } - return 0; - } - - return { - ForStatement(node) { - - if (node.test && node.test.type === "BinaryExpression" && node.update) { - for (const counterPosition of ["left", "right"]) { - if (node.test[counterPosition].type !== "Identifier") { - continue; - } - - const counter = node.test[counterPosition].name; - const operator = node.test.operator; - const update = node.update; - - let wrongDirection; - - if (operator === "<" || operator === "<=") { - wrongDirection = counterPosition === "left" ? -1 : 1; - } else if (operator === ">" || operator === ">=") { - wrongDirection = counterPosition === "left" ? 1 : -1; - } else { - return; - } - - if (update.type === "UpdateExpression") { - if (getUpdateDirection(update, counter) === wrongDirection) { - report(node); - } - } else if (update.type === "AssignmentExpression" && getAssignmentDirection(update, counter) === wrongDirection) { - report(node); - } - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + 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, + schema: [], + + messages: { + incorrectDirection: + "The update clause in this loop moves the variable in the wrong direction.", + }, + }, + + create(context) { + const { sourceCode } = context; + + /** + * report an error. + * @param {ASTNode} node the node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "incorrectDirection", + }); + } + + /** + * check the right side of the assignment + * @param {ASTNode} update UpdateExpression to check + * @param {int} dir expected direction that could either be turned around or invalidated + * @returns {int} return dir, the negated dir, or zero if the counter does not change or the direction is not clear + */ + function getRightDirection(update, dir) { + const staticValue = getStaticValue( + update.right, + sourceCode.getScope(update), + ); + + if ( + staticValue && + ["bigint", "boolean", "number"].includes( + typeof staticValue.value, + ) + ) { + const sign = Math.sign(Number(staticValue.value)) || 0; // convert NaN to 0 + + return dir * sign; + } + return 0; + } + + /** + * check UpdateExpression add/sub the counter + * @param {ASTNode} update UpdateExpression to check + * @param {string} counter variable name to check + * @returns {int} if add return 1, if sub return -1, if nochange, return 0 + */ + function getUpdateDirection(update, counter) { + if ( + update.argument.type === "Identifier" && + update.argument.name === counter + ) { + if (update.operator === "++") { + return 1; + } + if (update.operator === "--") { + return -1; + } + } + return 0; + } + + /** + * check AssignmentExpression add/sub the counter + * @param {ASTNode} update AssignmentExpression to check + * @param {string} counter variable name to check + * @returns {int} if add return 1, if sub return -1, if nochange, return 0 + */ + function getAssignmentDirection(update, counter) { + if (update.left.name === counter) { + if (update.operator === "+=") { + return getRightDirection(update, 1); + } + if (update.operator === "-=") { + return getRightDirection(update, -1); + } + } + return 0; + } + + return { + ForStatement(node) { + if ( + node.test && + node.test.type === "BinaryExpression" && + node.update + ) { + for (const counterPosition of ["left", "right"]) { + if (node.test[counterPosition].type !== "Identifier") { + continue; + } + + const counter = node.test[counterPosition].name; + const operator = node.test.operator; + const update = node.update; + + let wrongDirection; + + if (operator === "<" || operator === "<=") { + wrongDirection = + counterPosition === "left" ? -1 : 1; + } else if (operator === ">" || operator === ">=") { + wrongDirection = + counterPosition === "left" ? 1 : -1; + } else { + return; + } + + if (update.type === "UpdateExpression") { + if ( + getUpdateDirection(update, counter) === + wrongDirection + ) { + report(node); + } + } else if ( + update.type === "AssignmentExpression" && + getAssignmentDirection(update, counter) === + wrongDirection + ) { + report(node); + } + } + } + }, + }; + }, }; diff --git a/lib/rules/func-call-spacing.js b/lib/rules/func-call-spacing.js index 4757b7a11cc6..f9c1ae1467dd 100644 --- a/lib/rules/func-call-spacing.js +++ b/lib/rules/func-call-spacing.js @@ -18,234 +18,264 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "function-call-spacing", - url: "https://eslint.style/rules/js/function-call-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow spacing between function identifiers and their invocations", - recommended: false, - url: "https://eslint.org/docs/latest/rules/func-call-spacing" - }, - - fixable: "whitespace", - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["never"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["always"] - }, - { - type: "object", - properties: { - allowNewlines: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - messages: { - unexpectedWhitespace: "Unexpected whitespace between function name and paren.", - unexpectedNewline: "Unexpected newline between function name and paren.", - missing: "Missing space between function name and paren." - } - }, - - create(context) { - - const never = context.options[0] !== "always"; - const allowNewlines = !never && context.options[1] && context.options[1].allowNewlines; - const sourceCode = context.sourceCode; - const text = sourceCode.getText(); - - /** - * Check if open space is present in a function name - * @param {ASTNode} node node to evaluate - * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee. - * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments. - * @returns {void} - * @private - */ - function checkSpacing(node, leftToken, rightToken) { - const textBetweenTokens = text.slice(leftToken.range[1], rightToken.range[0]).replace(/\/\*.*?\*\//gu, ""); - const hasWhitespace = /\s/u.test(textBetweenTokens); - const hasNewline = hasWhitespace && astUtils.LINEBREAK_MATCHER.test(textBetweenTokens); - - /* - * never allowNewlines hasWhitespace hasNewline message - * F F F F Missing space between function name and paren. - * F F F T (Invalid `!hasWhitespace && hasNewline`) - * F F T T Unexpected newline between function name and paren. - * F F T F (OK) - * F T T F (OK) - * F T T T (OK) - * F T F T (Invalid `!hasWhitespace && hasNewline`) - * F T F F Missing space between function name and paren. - * T T F F (Invalid `never && allowNewlines`) - * T T F T (Invalid `!hasWhitespace && hasNewline`) - * T T T T (Invalid `never && allowNewlines`) - * T T T F (Invalid `never && allowNewlines`) - * T F T F Unexpected space between function name and paren. - * T F T T Unexpected space between function name and paren. - * T F F T (Invalid `!hasWhitespace && hasNewline`) - * T F F F (OK) - * - * T T Unexpected space between function name and paren. - * F F Missing space between function name and paren. - * F F T Unexpected newline between function name and paren. - */ - - if (never && hasWhitespace) { - context.report({ - node, - loc: { - start: leftToken.loc.end, - end: { - line: rightToken.loc.start.line, - column: rightToken.loc.start.column - 1 - } - }, - messageId: "unexpectedWhitespace", - fix(fixer) { - - // Don't remove comments. - if (sourceCode.commentsExistBetween(leftToken, rightToken)) { - return null; - } - - // If `?.` exists, it doesn't hide no-unexpected-multiline errors - if (node.optional) { - return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], "?."); - } - - /* - * Only autofix if there is no newline - * https://github.com/eslint/eslint/issues/7787 - */ - if (hasNewline) { - return null; - } - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } else if (!never && !hasWhitespace) { - context.report({ - node, - loc: { - start: { - line: leftToken.loc.end.line, - column: leftToken.loc.end.column - 1 - }, - end: rightToken.loc.start - }, - messageId: "missing", - fix(fixer) { - if (node.optional) { - return null; // Not sure if inserting a space to either before/after `?.` token. - } - return fixer.insertTextBefore(rightToken, " "); - } - }); - } else if (!never && !allowNewlines && hasNewline) { - context.report({ - node, - loc: { - start: leftToken.loc.end, - end: rightToken.loc.start - }, - messageId: "unexpectedNewline", - fix(fixer) { - - /* - * Only autofix if there is no newline - * https://github.com/eslint/eslint/issues/7787 - * But if `?.` exists, it doesn't hide no-unexpected-multiline errors - */ - if (!node.optional) { - return null; - } - - // Don't remove comments. - if (sourceCode.commentsExistBetween(leftToken, rightToken)) { - return null; - } - - const range = [leftToken.range[1], rightToken.range[0]]; - const qdToken = sourceCode.getTokenAfter(leftToken); - - if (qdToken.range[0] === leftToken.range[1]) { - return fixer.replaceTextRange(range, "?. "); - } - if (qdToken.range[1] === rightToken.range[0]) { - return fixer.replaceTextRange(range, " ?."); - } - return fixer.replaceTextRange(range, " ?. "); - } - }); - } - } - - return { - "CallExpression, NewExpression"(node) { - const lastToken = sourceCode.getLastToken(node); - const lastCalleeToken = sourceCode.getLastToken(node.callee); - const parenToken = sourceCode.getFirstTokenBetween(lastCalleeToken, lastToken, astUtils.isOpeningParenToken); - const prevToken = parenToken && sourceCode.getTokenBefore(parenToken, astUtils.isNotQuestionDotToken); - - // Parens in NewExpression are optional - if (!(parenToken && parenToken.range[1] < node.range[1])) { - return; - } - - checkSpacing(node, prevToken, parenToken); - }, - - ImportExpression(node) { - const leftToken = sourceCode.getFirstToken(node); - const rightToken = sourceCode.getTokenAfter(leftToken); - - checkSpacing(node, leftToken, rightToken); - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "function-call-spacing", + url: "https://eslint.style/rules/js/function-call-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Require or disallow spacing between function identifiers and their invocations", + recommended: false, + url: "https://eslint.org/docs/latest/rules/func-call-spacing", + }, + + fixable: "whitespace", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["never"], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["always"], + }, + { + type: "object", + properties: { + allowNewlines: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + messages: { + unexpectedWhitespace: + "Unexpected whitespace between function name and paren.", + unexpectedNewline: + "Unexpected newline between function name and paren.", + missing: "Missing space between function name and paren.", + }, + }, + + create(context) { + const never = context.options[0] !== "always"; + const allowNewlines = + !never && context.options[1] && context.options[1].allowNewlines; + const sourceCode = context.sourceCode; + const text = sourceCode.getText(); + + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @param {Token} leftToken The last token of the callee. This may be the closing parenthesis that encloses the callee. + * @param {Token} rightToken Tha first token of the arguments. this is the opening parenthesis that encloses the arguments. + * @returns {void} + * @private + */ + function checkSpacing(node, leftToken, rightToken) { + const textBetweenTokens = text + .slice(leftToken.range[1], rightToken.range[0]) + .replace(/\/\*.*?\*\//gu, ""); + const hasWhitespace = /\s/u.test(textBetweenTokens); + const hasNewline = + hasWhitespace && + astUtils.LINEBREAK_MATCHER.test(textBetweenTokens); + + /* + * never allowNewlines hasWhitespace hasNewline message + * F F F F Missing space between function name and paren. + * F F F T (Invalid `!hasWhitespace && hasNewline`) + * F F T T Unexpected newline between function name and paren. + * F F T F (OK) + * F T T F (OK) + * F T T T (OK) + * F T F T (Invalid `!hasWhitespace && hasNewline`) + * F T F F Missing space between function name and paren. + * T T F F (Invalid `never && allowNewlines`) + * T T F T (Invalid `!hasWhitespace && hasNewline`) + * T T T T (Invalid `never && allowNewlines`) + * T T T F (Invalid `never && allowNewlines`) + * T F T F Unexpected space between function name and paren. + * T F T T Unexpected space between function name and paren. + * T F F T (Invalid `!hasWhitespace && hasNewline`) + * T F F F (OK) + * + * T T Unexpected space between function name and paren. + * F F Missing space between function name and paren. + * F F T Unexpected newline between function name and paren. + */ + + if (never && hasWhitespace) { + context.report({ + node, + loc: { + start: leftToken.loc.end, + end: { + line: rightToken.loc.start.line, + column: rightToken.loc.start.column - 1, + }, + }, + messageId: "unexpectedWhitespace", + fix(fixer) { + // Don't remove comments. + if ( + sourceCode.commentsExistBetween( + leftToken, + rightToken, + ) + ) { + return null; + } + + // If `?.` exists, it doesn't hide no-unexpected-multiline errors + if (node.optional) { + return fixer.replaceTextRange( + [leftToken.range[1], rightToken.range[0]], + "?.", + ); + } + + /* + * Only autofix if there is no newline + * https://github.com/eslint/eslint/issues/7787 + */ + if (hasNewline) { + return null; + } + return fixer.removeRange([ + leftToken.range[1], + rightToken.range[0], + ]); + }, + }); + } else if (!never && !hasWhitespace) { + context.report({ + node, + loc: { + start: { + line: leftToken.loc.end.line, + column: leftToken.loc.end.column - 1, + }, + end: rightToken.loc.start, + }, + messageId: "missing", + fix(fixer) { + if (node.optional) { + return null; // Not sure if inserting a space to either before/after `?.` token. + } + return fixer.insertTextBefore(rightToken, " "); + }, + }); + } else if (!never && !allowNewlines && hasNewline) { + context.report({ + node, + loc: { + start: leftToken.loc.end, + end: rightToken.loc.start, + }, + messageId: "unexpectedNewline", + fix(fixer) { + /* + * Only autofix if there is no newline + * https://github.com/eslint/eslint/issues/7787 + * But if `?.` exists, it doesn't hide no-unexpected-multiline errors + */ + if (!node.optional) { + return null; + } + + // Don't remove comments. + if ( + sourceCode.commentsExistBetween( + leftToken, + rightToken, + ) + ) { + return null; + } + + const range = [leftToken.range[1], rightToken.range[0]]; + const qdToken = sourceCode.getTokenAfter(leftToken); + + if (qdToken.range[0] === leftToken.range[1]) { + return fixer.replaceTextRange(range, "?. "); + } + if (qdToken.range[1] === rightToken.range[0]) { + return fixer.replaceTextRange(range, " ?."); + } + return fixer.replaceTextRange(range, " ?. "); + }, + }); + } + } + + return { + "CallExpression, NewExpression"(node) { + const lastToken = sourceCode.getLastToken(node); + const lastCalleeToken = sourceCode.getLastToken(node.callee); + const parenToken = sourceCode.getFirstTokenBetween( + lastCalleeToken, + lastToken, + astUtils.isOpeningParenToken, + ); + const prevToken = + parenToken && + sourceCode.getTokenBefore( + parenToken, + astUtils.isNotQuestionDotToken, + ); + + // Parens in NewExpression are optional + if (!(parenToken && parenToken.range[1] < node.range[1])) { + return; + } + + checkSpacing(node, prevToken, parenToken); + }, + + ImportExpression(node) { + const leftToken = sourceCode.getFirstToken(node); + const rightToken = sourceCode.getTokenAfter(leftToken); + + checkSpacing(node, leftToken, rightToken); + }, + }; + }, }; diff --git a/lib/rules/func-name-matching.js b/lib/rules/func-name-matching.js index b71e6e6ac28f..7543e33123c8 100644 --- a/lib/rules/func-name-matching.js +++ b/lib/rules/func-name-matching.js @@ -22,19 +22,28 @@ const esutils = require("esutils"); * @returns {boolean} True if the pattern is `module.exports` or `module["exports"]` */ function isModuleExports(pattern) { - if (pattern.type === "MemberExpression" && pattern.object.type === "Identifier" && pattern.object.name === "module") { - - // module.exports - if (pattern.property.type === "Identifier" && pattern.property.name === "exports") { - return true; - } - - // module["exports"] - if (pattern.property.type === "Literal" && pattern.property.value === "exports") { - return true; - } - } - return false; + if ( + pattern.type === "MemberExpression" && + pattern.object.type === "Identifier" && + pattern.object.name === "module" + ) { + // module.exports + if ( + pattern.property.type === "Identifier" && + pattern.property.name === "exports" + ) { + return true; + } + + // module["exports"] + if ( + pattern.property.type === "Literal" && + pattern.property.value === "exports" + ) { + return true; + } + } + return false; } /** @@ -44,10 +53,10 @@ function isModuleExports(pattern) { * @returns {boolean} True if the string is a valid identifier */ function isIdentifier(name, ecmaVersion) { - if (ecmaVersion >= 2015) { - return esutils.keyword.isIdentifierES6(name); - } - return esutils.keyword.isIdentifierES5(name); + if (ecmaVersion >= 2015) { + return esutils.keyword.isIdentifierES6(name); + } + return esutils.keyword.isIdentifierES5(name); } //------------------------------------------------------------------------------ @@ -56,199 +65,274 @@ function isIdentifier(name, ecmaVersion) { const alwaysOrNever = { enum: ["always", "never"] }; const optionsObject = { - type: "object", - properties: { - considerPropertyDescriptor: { - type: "boolean" - }, - includeCommonJSModuleExports: { - type: "boolean" - } - }, - additionalProperties: false + type: "object", + properties: { + considerPropertyDescriptor: { + type: "boolean", + }, + includeCommonJSModuleExports: { + type: "boolean", + }, + }, + additionalProperties: false, }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: { - anyOf: [{ - type: "array", - additionalItems: false, - items: [alwaysOrNever, optionsObject] - }, { - type: "array", - additionalItems: false, - items: [optionsObject] - }] - }, - - messages: { - matchProperty: "Function name `{{funcName}}` should match property name `{{name}}`.", - matchVariable: "Function name `{{funcName}}` should match variable name `{{name}}`.", - notMatchProperty: "Function name `{{funcName}}` should not match property name `{{name}}`.", - notMatchVariable: "Function name `{{funcName}}` should not match variable name `{{name}}`." - } - }, - - create(context) { - const options = (typeof context.options[0] === "object" ? context.options[0] : context.options[1]) || {}; - const nameMatches = typeof context.options[0] === "string" ? context.options[0] : "always"; - const considerPropertyDescriptor = options.considerPropertyDescriptor; - const includeModuleExports = options.includeCommonJSModuleExports; - const ecmaVersion = context.languageOptions.ecmaVersion; - - /** - * Check whether node is a certain CallExpression. - * @param {string} objName object name - * @param {string} funcName function name - * @param {ASTNode} node The node to check - * @returns {boolean} `true` if node matches CallExpression - */ - function isPropertyCall(objName, funcName, node) { - if (!node) { - return false; - } - return node.type === "CallExpression" && astUtils.isSpecificMemberAccess(node.callee, objName, funcName); - } - - /** - * Compares identifiers based on the nameMatches option - * @param {string} x the first identifier - * @param {string} y the second identifier - * @returns {boolean} whether the two identifiers should warn. - */ - function shouldWarn(x, y) { - return (nameMatches === "always" && x !== y) || (nameMatches === "never" && x === y); - } - - /** - * Reports - * @param {ASTNode} node The node to report - * @param {string} name The variable or property name - * @param {string} funcName The function name - * @param {boolean} isProp True if the reported node is a property assignment - * @returns {void} - */ - function report(node, name, funcName, isProp) { - let messageId; - - if (nameMatches === "always" && isProp) { - messageId = "matchProperty"; - } else if (nameMatches === "always") { - messageId = "matchVariable"; - } else if (isProp) { - messageId = "notMatchProperty"; - } else { - messageId = "notMatchVariable"; - } - context.report({ - node, - messageId, - data: { - name, - funcName - } - }); - } - - /** - * Determines whether a given node is a string literal - * @param {ASTNode} node The node to check - * @returns {boolean} `true` if the node is a string literal - */ - function isStringLiteral(node) { - return node.type === "Literal" && typeof node.value === "string"; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - VariableDeclarator(node) { - if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") { - return; - } - if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) { - report(node, node.id.name, node.init.id.name, false); - } - }, - - AssignmentExpression(node) { - if ( - node.right.type !== "FunctionExpression" || - (node.left.computed && node.left.property.type !== "Literal") || - (!includeModuleExports && isModuleExports(node.left)) || - (node.left.type !== "Identifier" && node.left.type !== "MemberExpression") - ) { - return; - } - - const isProp = node.left.type === "MemberExpression"; - const name = isProp ? astUtils.getStaticPropertyName(node.left) : node.left.name; - - if (node.right.id && name && isIdentifier(name) && shouldWarn(name, node.right.id.name)) { - report(node, name, node.right.id.name, isProp); - } - }, - - "Property, PropertyDefinition[value]"(node) { - if (!(node.value.type === "FunctionExpression" && node.value.id)) { - return; - } - - if (node.key.type === "Identifier" && !node.computed) { - const functionName = node.value.id.name; - let propertyName = node.key.name; - - if ( - considerPropertyDescriptor && - propertyName === "value" && - node.parent.type === "ObjectExpression" - ) { - if (isPropertyCall("Object", "defineProperty", node.parent.parent) || isPropertyCall("Reflect", "defineProperty", node.parent.parent)) { - const property = node.parent.parent.arguments[1]; - - if (isStringLiteral(property) && shouldWarn(property.value, functionName)) { - report(node, property.value, functionName, true); - } - } else if (isPropertyCall("Object", "defineProperties", node.parent.parent.parent.parent)) { - propertyName = node.parent.parent.key.name; - if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) { - report(node, propertyName, functionName, true); - } - } else if (isPropertyCall("Object", "create", node.parent.parent.parent.parent)) { - propertyName = node.parent.parent.key.name; - if (!node.parent.parent.computed && shouldWarn(propertyName, functionName)) { - report(node, propertyName, functionName, true); - } - } else if (shouldWarn(propertyName, functionName)) { - report(node, propertyName, functionName, true); - } - } else if (shouldWarn(propertyName, functionName)) { - report(node, propertyName, functionName, true); - } - return; - } - - if ( - isStringLiteral(node.key) && - isIdentifier(node.key.value, ecmaVersion) && - shouldWarn(node.key.value, node.value.id.name) - ) { - report(node, node.key.value, node.value.id.name, true); - } - } - }; - } + meta: { + type: "suggestion", + + 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", + }, + + schema: { + anyOf: [ + { + type: "array", + additionalItems: false, + items: [alwaysOrNever, optionsObject], + }, + { + type: "array", + additionalItems: false, + items: [optionsObject], + }, + ], + }, + + messages: { + matchProperty: + "Function name `{{funcName}}` should match property name `{{name}}`.", + matchVariable: + "Function name `{{funcName}}` should match variable name `{{name}}`.", + notMatchProperty: + "Function name `{{funcName}}` should not match property name `{{name}}`.", + notMatchVariable: + "Function name `{{funcName}}` should not match variable name `{{name}}`.", + }, + }, + + create(context) { + const options = + (typeof context.options[0] === "object" + ? context.options[0] + : context.options[1]) || {}; + const nameMatches = + typeof context.options[0] === "string" + ? context.options[0] + : "always"; + const considerPropertyDescriptor = options.considerPropertyDescriptor; + const includeModuleExports = options.includeCommonJSModuleExports; + const ecmaVersion = context.languageOptions.ecmaVersion; + + /** + * Check whether node is a certain CallExpression. + * @param {string} objName object name + * @param {string} funcName function name + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if node matches CallExpression + */ + function isPropertyCall(objName, funcName, node) { + if (!node) { + return false; + } + return ( + node.type === "CallExpression" && + astUtils.isSpecificMemberAccess(node.callee, objName, funcName) + ); + } + + /** + * Compares identifiers based on the nameMatches option + * @param {string} x the first identifier + * @param {string} y the second identifier + * @returns {boolean} whether the two identifiers should warn. + */ + function shouldWarn(x, y) { + return ( + (nameMatches === "always" && x !== y) || + (nameMatches === "never" && x === y) + ); + } + + /** + * Reports + * @param {ASTNode} node The node to report + * @param {string} name The variable or property name + * @param {string} funcName The function name + * @param {boolean} isProp True if the reported node is a property assignment + * @returns {void} + */ + function report(node, name, funcName, isProp) { + let messageId; + + if (nameMatches === "always" && isProp) { + messageId = "matchProperty"; + } else if (nameMatches === "always") { + messageId = "matchVariable"; + } else if (isProp) { + messageId = "notMatchProperty"; + } else { + messageId = "notMatchVariable"; + } + context.report({ + node, + messageId, + data: { + name, + funcName, + }, + }); + } + + /** + * Determines whether a given node is a string literal + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the node is a string literal + */ + function isStringLiteral(node) { + return node.type === "Literal" && typeof node.value === "string"; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclarator(node) { + if ( + !node.init || + node.init.type !== "FunctionExpression" || + node.id.type !== "Identifier" + ) { + return; + } + if ( + node.init.id && + shouldWarn(node.id.name, node.init.id.name) + ) { + report(node, node.id.name, node.init.id.name, false); + } + }, + + AssignmentExpression(node) { + if ( + node.right.type !== "FunctionExpression" || + (node.left.computed && + node.left.property.type !== "Literal") || + (!includeModuleExports && isModuleExports(node.left)) || + (node.left.type !== "Identifier" && + node.left.type !== "MemberExpression") + ) { + return; + } + + const isProp = node.left.type === "MemberExpression"; + const name = isProp + ? astUtils.getStaticPropertyName(node.left) + : node.left.name; + + if ( + node.right.id && + name && + isIdentifier(name) && + shouldWarn(name, node.right.id.name) + ) { + report(node, name, node.right.id.name, isProp); + } + }, + + "Property, PropertyDefinition[value]"(node) { + if ( + !(node.value.type === "FunctionExpression" && node.value.id) + ) { + return; + } + + if (node.key.type === "Identifier" && !node.computed) { + const functionName = node.value.id.name; + let propertyName = node.key.name; + + if ( + considerPropertyDescriptor && + propertyName === "value" && + node.parent.type === "ObjectExpression" + ) { + if ( + isPropertyCall( + "Object", + "defineProperty", + node.parent.parent, + ) || + isPropertyCall( + "Reflect", + "defineProperty", + node.parent.parent, + ) + ) { + const property = node.parent.parent.arguments[1]; + + if ( + isStringLiteral(property) && + shouldWarn(property.value, functionName) + ) { + report( + node, + property.value, + functionName, + true, + ); + } + } else if ( + isPropertyCall( + "Object", + "defineProperties", + node.parent.parent.parent.parent, + ) + ) { + propertyName = node.parent.parent.key.name; + if ( + !node.parent.parent.computed && + shouldWarn(propertyName, functionName) + ) { + report(node, propertyName, functionName, true); + } + } else if ( + isPropertyCall( + "Object", + "create", + node.parent.parent.parent.parent, + ) + ) { + propertyName = node.parent.parent.key.name; + if ( + !node.parent.parent.computed && + shouldWarn(propertyName, functionName) + ) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + } else if (shouldWarn(propertyName, functionName)) { + report(node, propertyName, functionName, true); + } + return; + } + + if ( + isStringLiteral(node.key) && + isIdentifier(node.key.value, ecmaVersion) && + shouldWarn(node.key.value, node.value.id.name) + ) { + report(node, node.key.value, node.value.id.name, true); + } + }, + }; + }, }; diff --git a/lib/rules/func-names.js b/lib/rules/func-names.js index 6990f0c7ba54..4f4a322d4dcf 100644 --- a/lib/rules/func-names.js +++ b/lib/rules/func-names.js @@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the variable is a function name. */ function isFunctionName(variable) { - return variable && variable.defs[0].type === "FunctionName"; + return variable && variable.defs[0].type === "FunctionName"; } //------------------------------------------------------------------------------ @@ -26,166 +26,167 @@ function isFunctionName(variable) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: ["always", {}], - - docs: { - description: "Require or disallow named `function` expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/func-names" - }, - - schema: { - definitions: { - value: { - enum: [ - "always", - "as-needed", - "never" - ] - } - }, - items: [ - { - $ref: "#/definitions/value" - }, - { - type: "object", - properties: { - generators: { - $ref: "#/definitions/value" - } - }, - additionalProperties: false - } - ] - }, - - messages: { - unnamed: "Unexpected unnamed {{name}}.", - named: "Unexpected named {{name}}." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Returns the config option for the given node. - * @param {ASTNode} node A node to get the config for. - * @returns {string} The config option. - */ - function getConfigForNode(node) { - if ( - node.generator && - context.options[1].generators - ) { - return context.options[1].generators; - } - - return context.options[0]; - } - - /** - * Determines whether the current FunctionExpression node is a get, set, or - * shorthand method in an object literal or a class. - * @param {ASTNode} node A node to check. - * @returns {boolean} True if the node is a get, set, or shorthand method. - */ - function isObjectOrClassMethod(node) { - const parent = node.parent; - - return (parent.type === "MethodDefinition" || ( - parent.type === "Property" && ( - parent.method || - parent.kind === "get" || - parent.kind === "set" - ) - )); - } - - /** - * Determines whether the current FunctionExpression node has a name that would be - * inferred from context in a conforming ES6 environment. - * @param {ASTNode} node A node to check. - * @returns {boolean} True if the node would have a name assigned automatically. - */ - function hasInferredName(node) { - const parent = node.parent; - - return isObjectOrClassMethod(node) || - (parent.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.init === node) || - (parent.type === "Property" && parent.value === node) || - (parent.type === "PropertyDefinition" && parent.value === node) || - (parent.type === "AssignmentExpression" && parent.left.type === "Identifier" && parent.right === node) || - (parent.type === "AssignmentPattern" && parent.left.type === "Identifier" && parent.right === node); - } - - /** - * Reports that an unnamed function should be named - * @param {ASTNode} node The node to report in the event of an error. - * @returns {void} - */ - function reportUnexpectedUnnamedFunction(node) { - context.report({ - node, - messageId: "unnamed", - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - data: { name: astUtils.getFunctionNameWithKind(node) } - }); - } - - /** - * Reports that a named function should be unnamed - * @param {ASTNode} node The node to report in the event of an error. - * @returns {void} - */ - function reportUnexpectedNamedFunction(node) { - context.report({ - node, - messageId: "named", - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - data: { name: astUtils.getFunctionNameWithKind(node) } - }); - } - - /** - * The listener for function nodes. - * @param {ASTNode} node function node - * @returns {void} - */ - function handleFunction(node) { - - // Skip recursive functions. - const nameVar = sourceCode.getDeclaredVariables(node)[0]; - - if (isFunctionName(nameVar) && nameVar.references.length > 0) { - return; - } - - const hasName = Boolean(node.id && node.id.name); - const config = getConfigForNode(node); - - if (config === "never") { - if (hasName && node.type !== "FunctionDeclaration") { - reportUnexpectedNamedFunction(node); - } - } else if (config === "as-needed") { - if (!hasName && !hasInferredName(node)) { - reportUnexpectedUnnamedFunction(node); - } - } else { - if (!hasName && !isObjectOrClassMethod(node)) { - reportUnexpectedUnnamedFunction(node); - } - } - } - - return { - "FunctionExpression:exit": handleFunction, - "ExportDefaultDeclaration > FunctionDeclaration": handleFunction - }; - } + meta: { + type: "suggestion", + + defaultOptions: ["always", {}], + + docs: { + description: "Require or disallow named `function` expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/func-names", + }, + + schema: { + definitions: { + value: { + enum: ["always", "as-needed", "never"], + }, + }, + items: [ + { + $ref: "#/definitions/value", + }, + { + type: "object", + properties: { + generators: { + $ref: "#/definitions/value", + }, + }, + additionalProperties: false, + }, + ], + }, + + messages: { + unnamed: "Unexpected unnamed {{name}}.", + named: "Unexpected named {{name}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Returns the config option for the given node. + * @param {ASTNode} node A node to get the config for. + * @returns {string} The config option. + */ + function getConfigForNode(node) { + if (node.generator && context.options[1].generators) { + return context.options[1].generators; + } + + return context.options[0]; + } + + /** + * Determines whether the current FunctionExpression node is a get, set, or + * shorthand method in an object literal or a class. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is a get, set, or shorthand method. + */ + function isObjectOrClassMethod(node) { + const parent = node.parent; + + return ( + parent.type === "MethodDefinition" || + (parent.type === "Property" && + (parent.method || + parent.kind === "get" || + parent.kind === "set")) + ); + } + + /** + * Determines whether the current FunctionExpression node has a name that would be + * inferred from context in a conforming ES6 environment. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node would have a name assigned automatically. + */ + function hasInferredName(node) { + const parent = node.parent; + + return ( + isObjectOrClassMethod(node) || + (parent.type === "VariableDeclarator" && + parent.id.type === "Identifier" && + parent.init === node) || + (parent.type === "Property" && parent.value === node) || + (parent.type === "PropertyDefinition" && + parent.value === node) || + (parent.type === "AssignmentExpression" && + parent.left.type === "Identifier" && + parent.right === node) || + (parent.type === "AssignmentPattern" && + parent.left.type === "Identifier" && + parent.right === node) + ); + } + + /** + * Reports that an unnamed function should be named + * @param {ASTNode} node The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedUnnamedFunction(node) { + context.report({ + node, + messageId: "unnamed", + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node) }, + }); + } + + /** + * Reports that a named function should be unnamed + * @param {ASTNode} node The node to report in the event of an error. + * @returns {void} + */ + function reportUnexpectedNamedFunction(node) { + context.report({ + node, + messageId: "named", + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + data: { name: astUtils.getFunctionNameWithKind(node) }, + }); + } + + /** + * The listener for function nodes. + * @param {ASTNode} node function node + * @returns {void} + */ + function handleFunction(node) { + // Skip recursive functions. + const nameVar = sourceCode.getDeclaredVariables(node)[0]; + + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } + + const hasName = Boolean(node.id && node.id.name); + const config = getConfigForNode(node); + + if (config === "never") { + if (hasName && node.type !== "FunctionDeclaration") { + reportUnexpectedNamedFunction(node); + } + } else if (config === "as-needed") { + if (!hasName && !hasInferredName(node)) { + reportUnexpectedUnnamedFunction(node); + } + } else { + if (!hasName && !isObjectOrClassMethod(node)) { + reportUnexpectedUnnamedFunction(node); + } + } + } + + return { + "FunctionExpression:exit": handleFunction, + "ExportDefaultDeclaration > FunctionDeclaration": handleFunction, + }; + }, }; diff --git a/lib/rules/func-style.js b/lib/rules/func-style.js index be2298340d20..3cf20dbfc916 100644 --- a/lib/rules/func-style.js +++ b/lib/rules/func-style.js @@ -10,130 +10,162 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: ["expression", { - allowArrowFunctions: false, - overrides: {} - }], - - docs: { - 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" - }, - - schema: [ - { - enum: ["declaration", "expression"] - }, - { - type: "object", - properties: { - allowArrowFunctions: { - type: "boolean" - }, - overrides: { - type: "object", - properties: { - namedExports: { - enum: ["declaration", "expression", "ignore"] - } - }, - additionalProperties: false - } - }, - additionalProperties: false - } - ], - - messages: { - expression: "Expected a function expression.", - declaration: "Expected a function declaration." - } - }, - - create(context) { - const [style, { allowArrowFunctions, overrides }] = context.options; - const enforceDeclarations = (style === "declaration"); - const { namedExports: exportFunctionStyle } = overrides; - const stack = []; - - const nodesToCheck = { - FunctionDeclaration(node) { - stack.push(false); - - if ( - !enforceDeclarations && - node.parent.type !== "ExportDefaultDeclaration" && - (typeof exportFunctionStyle === "undefined" || node.parent.type !== "ExportNamedDeclaration") - ) { - context.report({ node, messageId: "expression" }); - } - - if (node.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "expression") { - context.report({ node, messageId: "expression" }); - } - }, - "FunctionDeclaration:exit"() { - stack.pop(); - }, - - FunctionExpression(node) { - stack.push(false); - - if ( - enforceDeclarations && - node.parent.type === "VariableDeclarator" && - (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration") - ) { - context.report({ node: node.parent, messageId: "declaration" }); - } - - if ( - node.parent.type === "VariableDeclarator" && node.parent.parent.parent.type === "ExportNamedDeclaration" && - exportFunctionStyle === "declaration" - ) { - context.report({ node: node.parent, messageId: "declaration" }); - } - }, - "FunctionExpression:exit"() { - stack.pop(); - }, - - "ThisExpression, Super"() { - if (stack.length > 0) { - stack[stack.length - 1] = true; - } - } - }; - - if (!allowArrowFunctions) { - nodesToCheck.ArrowFunctionExpression = function() { - stack.push(false); - }; - - nodesToCheck["ArrowFunctionExpression:exit"] = function(node) { - const hasThisOrSuperExpr = stack.pop(); - - if (!hasThisOrSuperExpr && node.parent.type === "VariableDeclarator") { - if ( - enforceDeclarations && - (typeof exportFunctionStyle === "undefined" || node.parent.parent.parent.type !== "ExportNamedDeclaration") - ) { - context.report({ node: node.parent, messageId: "declaration" }); - } - - if (node.parent.parent.parent.type === "ExportNamedDeclaration" && exportFunctionStyle === "declaration") { - context.report({ node: node.parent, messageId: "declaration" }); - } - } - }; - } - - return nodesToCheck; - - } + meta: { + type: "suggestion", + + defaultOptions: [ + "expression", + { + allowArrowFunctions: false, + overrides: {}, + }, + ], + + docs: { + 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", + }, + + schema: [ + { + enum: ["declaration", "expression"], + }, + { + type: "object", + properties: { + allowArrowFunctions: { + type: "boolean", + }, + overrides: { + type: "object", + properties: { + namedExports: { + enum: ["declaration", "expression", "ignore"], + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + expression: "Expected a function expression.", + declaration: "Expected a function declaration.", + }, + }, + + create(context) { + const [style, { allowArrowFunctions, overrides }] = context.options; + const enforceDeclarations = style === "declaration"; + const { namedExports: exportFunctionStyle } = overrides; + const stack = []; + + const nodesToCheck = { + FunctionDeclaration(node) { + stack.push(false); + + if ( + !enforceDeclarations && + node.parent.type !== "ExportDefaultDeclaration" && + (typeof exportFunctionStyle === "undefined" || + node.parent.type !== "ExportNamedDeclaration") + ) { + context.report({ node, messageId: "expression" }); + } + + if ( + node.parent.type === "ExportNamedDeclaration" && + exportFunctionStyle === "expression" + ) { + context.report({ node, messageId: "expression" }); + } + }, + "FunctionDeclaration:exit"() { + stack.pop(); + }, + + FunctionExpression(node) { + stack.push(false); + + if ( + enforceDeclarations && + node.parent.type === "VariableDeclarator" && + (typeof exportFunctionStyle === "undefined" || + node.parent.parent.parent.type !== + "ExportNamedDeclaration") + ) { + context.report({ + node: node.parent, + messageId: "declaration", + }); + } + + if ( + node.parent.type === "VariableDeclarator" && + node.parent.parent.parent.type === + "ExportNamedDeclaration" && + exportFunctionStyle === "declaration" + ) { + context.report({ + node: node.parent, + messageId: "declaration", + }); + } + }, + "FunctionExpression:exit"() { + stack.pop(); + }, + + "ThisExpression, Super"() { + if (stack.length > 0) { + stack[stack.length - 1] = true; + } + }, + }; + + if (!allowArrowFunctions) { + nodesToCheck.ArrowFunctionExpression = function () { + stack.push(false); + }; + + nodesToCheck["ArrowFunctionExpression:exit"] = function (node) { + const hasThisOrSuperExpr = stack.pop(); + + if ( + !hasThisOrSuperExpr && + node.parent.type === "VariableDeclarator" + ) { + if ( + enforceDeclarations && + (typeof exportFunctionStyle === "undefined" || + node.parent.parent.parent.type !== + "ExportNamedDeclaration") + ) { + context.report({ + node: node.parent, + messageId: "declaration", + }); + } + + if ( + node.parent.parent.parent.type === + "ExportNamedDeclaration" && + exportFunctionStyle === "declaration" + ) { + context.report({ + node: node.parent, + messageId: "declaration", + }); + } + } + }; + } + + return nodesToCheck; + }, }; diff --git a/lib/rules/function-call-argument-newline.js b/lib/rules/function-call-argument-newline.js index 2a4f0b90b925..1c97a897f182 100644 --- a/lib/rules/function-call-argument-newline.js +++ b/lib/rules/function-call-argument-newline.js @@ -12,132 +12,155 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "function-call-argument-newline", - url: "https://eslint.style/rules/js/function-call-argument-newline" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce line breaks between arguments of a function call", - recommended: false, - url: "https://eslint.org/docs/latest/rules/function-call-argument-newline" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never", "consistent"] - } - ], - - messages: { - unexpectedLineBreak: "There should be no line break here.", - missingLineBreak: "There should be a line break after this argument." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const checkers = { - unexpected: { - messageId: "unexpectedLineBreak", - check: (prevToken, currentToken) => prevToken.loc.end.line !== currentToken.loc.start.line, - createFix: (token, tokenBefore) => fixer => - fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], " ") - }, - missing: { - messageId: "missingLineBreak", - check: (prevToken, currentToken) => prevToken.loc.end.line === currentToken.loc.start.line, - createFix: (token, tokenBefore) => fixer => - fixer.replaceTextRange([tokenBefore.range[1], token.range[0]], "\n") - } - }; - - /** - * Check all arguments for line breaks in the CallExpression - * @param {CallExpression} node node to evaluate - * @param {{ messageId: string, check: Function }} checker selected checker - * @returns {void} - * @private - */ - function checkArguments(node, checker) { - for (let i = 1; i < node.arguments.length; i++) { - const prevArgToken = sourceCode.getLastToken(node.arguments[i - 1]); - const currentArgToken = sourceCode.getFirstToken(node.arguments[i]); - - if (checker.check(prevArgToken, currentArgToken)) { - const tokenBefore = sourceCode.getTokenBefore( - currentArgToken, - { includeComments: true } - ); - - const hasLineCommentBefore = tokenBefore.type === "Line"; - - context.report({ - node, - loc: { - start: tokenBefore.loc.end, - end: currentArgToken.loc.start - }, - messageId: checker.messageId, - fix: hasLineCommentBefore ? null : checker.createFix(currentArgToken, tokenBefore) - }); - } - } - } - - /** - * Check if open space is present in a function name - * @param {CallExpression} node node to evaluate - * @returns {void} - * @private - */ - function check(node) { - if (node.arguments.length < 2) { - return; - } - - const option = context.options[0] || "always"; - - if (option === "never") { - checkArguments(node, checkers.unexpected); - } else if (option === "always") { - checkArguments(node, checkers.missing); - } else if (option === "consistent") { - const firstArgToken = sourceCode.getLastToken(node.arguments[0]); - const secondArgToken = sourceCode.getFirstToken(node.arguments[1]); - - if (firstArgToken.loc.end.line === secondArgToken.loc.start.line) { - checkArguments(node, checkers.unexpected); - } else { - checkArguments(node, checkers.missing); - } - } - } - - return { - CallExpression: check, - NewExpression: check - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "function-call-argument-newline", + url: "https://eslint.style/rules/js/function-call-argument-newline", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce line breaks between arguments of a function call", + recommended: false, + url: "https://eslint.org/docs/latest/rules/function-call-argument-newline", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never", "consistent"], + }, + ], + + messages: { + unexpectedLineBreak: "There should be no line break here.", + missingLineBreak: + "There should be a line break after this argument.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const checkers = { + unexpected: { + messageId: "unexpectedLineBreak", + check: (prevToken, currentToken) => + prevToken.loc.end.line !== currentToken.loc.start.line, + createFix: (token, tokenBefore) => fixer => + fixer.replaceTextRange( + [tokenBefore.range[1], token.range[0]], + " ", + ), + }, + missing: { + messageId: "missingLineBreak", + check: (prevToken, currentToken) => + prevToken.loc.end.line === currentToken.loc.start.line, + createFix: (token, tokenBefore) => fixer => + fixer.replaceTextRange( + [tokenBefore.range[1], token.range[0]], + "\n", + ), + }, + }; + + /** + * Check all arguments for line breaks in the CallExpression + * @param {CallExpression} node node to evaluate + * @param {{ messageId: string, check: Function }} checker selected checker + * @returns {void} + * @private + */ + function checkArguments(node, checker) { + for (let i = 1; i < node.arguments.length; i++) { + const prevArgToken = sourceCode.getLastToken( + node.arguments[i - 1], + ); + const currentArgToken = sourceCode.getFirstToken( + node.arguments[i], + ); + + if (checker.check(prevArgToken, currentArgToken)) { + const tokenBefore = sourceCode.getTokenBefore( + currentArgToken, + { includeComments: true }, + ); + + const hasLineCommentBefore = tokenBefore.type === "Line"; + + context.report({ + node, + loc: { + start: tokenBefore.loc.end, + end: currentArgToken.loc.start, + }, + messageId: checker.messageId, + fix: hasLineCommentBefore + ? null + : checker.createFix(currentArgToken, tokenBefore), + }); + } + } + } + + /** + * Check if open space is present in a function name + * @param {CallExpression} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if (node.arguments.length < 2) { + return; + } + + const option = context.options[0] || "always"; + + if (option === "never") { + checkArguments(node, checkers.unexpected); + } else if (option === "always") { + checkArguments(node, checkers.missing); + } else if (option === "consistent") { + const firstArgToken = sourceCode.getLastToken( + node.arguments[0], + ); + const secondArgToken = sourceCode.getFirstToken( + node.arguments[1], + ); + + if ( + firstArgToken.loc.end.line === secondArgToken.loc.start.line + ) { + checkArguments(node, checkers.unexpected); + } else { + checkArguments(node, checkers.missing); + } + } + } + + return { + CallExpression: check, + NewExpression: check, + }; + }, }; diff --git a/lib/rules/function-paren-newline.js b/lib/rules/function-paren-newline.js index bf3845ef9c5c..b505e307a85f 100644 --- a/lib/rules/function-paren-newline.js +++ b/lib/rules/function-paren-newline.js @@ -17,294 +17,352 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "function-paren-newline", - url: "https://eslint.style/rules/js/function-paren-newline" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent line breaks inside function parentheses", - recommended: false, - url: "https://eslint.org/docs/latest/rules/function-paren-newline" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["always", "never", "consistent", "multiline", "multiline-arguments"] - }, - { - type: "object", - properties: { - minItems: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - expectedBefore: "Expected newline before ')'.", - expectedAfter: "Expected newline after '('.", - expectedBetween: "Expected newline between arguments/params.", - unexpectedBefore: "Unexpected newline before ')'.", - unexpectedAfter: "Unexpected newline after '('." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const rawOption = context.options[0] || "multiline"; - const multilineOption = rawOption === "multiline"; - const multilineArgumentsOption = rawOption === "multiline-arguments"; - const consistentOption = rawOption === "consistent"; - let minItems; - - if (typeof rawOption === "object") { - minItems = rawOption.minItems; - } else if (rawOption === "always") { - minItems = 0; - } else if (rawOption === "never") { - minItems = Infinity; - } else { - minItems = null; - } - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Determines whether there should be newlines inside function parens - * @param {ASTNode[]} elements The arguments or parameters in the list - * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code. - * @returns {boolean} `true` if there should be newlines inside the function parens - */ - function shouldHaveNewlines(elements, hasLeftNewline) { - if (multilineArgumentsOption && elements.length === 1) { - return hasLeftNewline; - } - if (multilineOption || multilineArgumentsOption) { - return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line); - } - if (consistentOption) { - return hasLeftNewline; - } - return elements.length >= minItems; - } - - /** - * Validates parens - * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token - * @param {ASTNode[]} elements The arguments or parameters in the list - * @returns {void} - */ - function validateParens(parens, elements) { - const leftParen = parens.leftParen; - const rightParen = parens.rightParen; - const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); - const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen); - const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen); - const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen); - const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); - - if (hasLeftNewline && !needsNewlines) { - context.report({ - node: leftParen, - messageId: "unexpectedAfter", - fix(fixer) { - return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim() - - // If there is a comment between the ( and the first element, don't do a fix. - ? null - : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]); - } - }); - } else if (!hasLeftNewline && needsNewlines) { - context.report({ - node: leftParen, - messageId: "expectedAfter", - fix: fixer => fixer.insertTextAfter(leftParen, "\n") - }); - } - - if (hasRightNewline && !needsNewlines) { - context.report({ - node: rightParen, - messageId: "unexpectedBefore", - fix(fixer) { - return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim() - - // If there is a comment between the last element and the ), don't do a fix. - ? null - : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]); - } - }); - } else if (!hasRightNewline && needsNewlines) { - context.report({ - node: rightParen, - messageId: "expectedBefore", - fix: fixer => fixer.insertTextBefore(rightParen, "\n") - }); - } - } - - /** - * Validates a list of arguments or parameters - * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token - * @param {ASTNode[]} elements The arguments or parameters in the list - * @returns {void} - */ - function validateArguments(parens, elements) { - const leftParen = parens.leftParen; - const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); - const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen); - const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); - - for (let i = 0; i <= elements.length - 2; i++) { - const currentElement = elements[i]; - const nextElement = elements[i + 1]; - const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line; - - if (!hasNewLine && needsNewlines) { - context.report({ - node: currentElement, - messageId: "expectedBetween", - fix: fixer => fixer.insertTextBefore(nextElement, "\n") - }); - } - } - } - - /** - * Gets the left paren and right paren tokens of a node. - * @param {ASTNode} node The node with parens - * @throws {TypeError} Unexpected node type. - * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token. - * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression - * with a single parameter) - */ - function getParenTokens(node) { - switch (node.type) { - case "NewExpression": - if (!node.arguments.length && - !( - astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) && - astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && - node.callee.range[1] < node.range[1] - ) - ) { - - // If the NewExpression does not have parens (e.g. `new Foo`), return null. - return null; - } - - // falls through - - case "CallExpression": - return { - leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken), - rightParen: sourceCode.getLastToken(node) - }; - - case "FunctionDeclaration": - case "FunctionExpression": { - const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); - const rightParen = node.params.length - ? sourceCode.getTokenAfter(node.params.at(-1), astUtils.isClosingParenToken) - : sourceCode.getTokenAfter(leftParen); - - return { leftParen, rightParen }; - } - - case "ArrowFunctionExpression": { - const firstToken = sourceCode.getFirstToken(node, { skip: (node.async ? 1 : 0) }); - - if (!astUtils.isOpeningParenToken(firstToken)) { - - // If the ArrowFunctionExpression has a single param without parens, return null. - return null; - } - - const rightParen = node.params.length - ? sourceCode.getTokenAfter(node.params.at(-1), astUtils.isClosingParenToken) - : sourceCode.getTokenAfter(firstToken); - - return { - leftParen: firstToken, - rightParen - }; - } - - case "ImportExpression": { - const leftParen = sourceCode.getFirstToken(node, 1); - const rightParen = sourceCode.getLastToken(node); - - return { leftParen, rightParen }; - } - - default: - throw new TypeError(`unexpected node with type ${node.type}`); - } - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - [[ - "ArrowFunctionExpression", - "CallExpression", - "FunctionDeclaration", - "FunctionExpression", - "ImportExpression", - "NewExpression" - ]](node) { - const parens = getParenTokens(node); - let params; - - if (node.type === "ImportExpression") { - params = [node.source]; - } else if (astUtils.isFunction(node)) { - params = node.params; - } else { - params = node.arguments; - } - - if (parens) { - validateParens(parens, params); - - if (multilineArgumentsOption) { - validateArguments(parens, params); - } - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "function-paren-newline", + url: "https://eslint.style/rules/js/function-paren-newline", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent line breaks inside function parentheses", + recommended: false, + url: "https://eslint.org/docs/latest/rules/function-paren-newline", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: [ + "always", + "never", + "consistent", + "multiline", + "multiline-arguments", + ], + }, + { + type: "object", + properties: { + minItems: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + expectedBefore: "Expected newline before ')'.", + expectedAfter: "Expected newline after '('.", + expectedBetween: "Expected newline between arguments/params.", + unexpectedBefore: "Unexpected newline before ')'.", + unexpectedAfter: "Unexpected newline after '('.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const rawOption = context.options[0] || "multiline"; + const multilineOption = rawOption === "multiline"; + const multilineArgumentsOption = rawOption === "multiline-arguments"; + const consistentOption = rawOption === "consistent"; + let minItems; + + if (typeof rawOption === "object") { + minItems = rawOption.minItems; + } else if (rawOption === "always") { + minItems = 0; + } else if (rawOption === "never") { + minItems = Infinity; + } else { + minItems = null; + } + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Determines whether there should be newlines inside function parens + * @param {ASTNode[]} elements The arguments or parameters in the list + * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code. + * @returns {boolean} `true` if there should be newlines inside the function parens + */ + function shouldHaveNewlines(elements, hasLeftNewline) { + if (multilineArgumentsOption && elements.length === 1) { + return hasLeftNewline; + } + if (multilineOption || multilineArgumentsOption) { + return elements.some( + (element, index) => + index !== elements.length - 1 && + element.loc.end.line !== + elements[index + 1].loc.start.line, + ); + } + if (consistentOption) { + return hasLeftNewline; + } + return elements.length >= minItems; + } + + /** + * Validates parens + * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode[]} elements The arguments or parameters in the list + * @returns {void} + */ + function validateParens(parens, elements) { + const leftParen = parens.leftParen; + const rightParen = parens.rightParen; + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); + const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen); + const hasLeftNewline = !astUtils.isTokenOnSameLine( + leftParen, + tokenAfterLeftParen, + ); + const hasRightNewline = !astUtils.isTokenOnSameLine( + tokenBeforeRightParen, + rightParen, + ); + const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); + + if (hasLeftNewline && !needsNewlines) { + context.report({ + node: leftParen, + messageId: "unexpectedAfter", + fix(fixer) { + return sourceCode + .getText() + .slice( + leftParen.range[1], + tokenAfterLeftParen.range[0], + ) + .trim() + ? // If there is a comment between the ( and the first element, don't do a fix. + null + : fixer.removeRange([ + leftParen.range[1], + tokenAfterLeftParen.range[0], + ]); + }, + }); + } else if (!hasLeftNewline && needsNewlines) { + context.report({ + node: leftParen, + messageId: "expectedAfter", + fix: fixer => fixer.insertTextAfter(leftParen, "\n"), + }); + } + + if (hasRightNewline && !needsNewlines) { + context.report({ + node: rightParen, + messageId: "unexpectedBefore", + fix(fixer) { + return sourceCode + .getText() + .slice( + tokenBeforeRightParen.range[1], + rightParen.range[0], + ) + .trim() + ? // If there is a comment between the last element and the ), don't do a fix. + null + : fixer.removeRange([ + tokenBeforeRightParen.range[1], + rightParen.range[0], + ]); + }, + }); + } else if (!hasRightNewline && needsNewlines) { + context.report({ + node: rightParen, + messageId: "expectedBefore", + fix: fixer => fixer.insertTextBefore(rightParen, "\n"), + }); + } + } + + /** + * Validates a list of arguments or parameters + * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode[]} elements The arguments or parameters in the list + * @returns {void} + */ + function validateArguments(parens, elements) { + const leftParen = parens.leftParen; + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); + const hasLeftNewline = !astUtils.isTokenOnSameLine( + leftParen, + tokenAfterLeftParen, + ); + const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); + + for (let i = 0; i <= elements.length - 2; i++) { + const currentElement = elements[i]; + const nextElement = elements[i + 1]; + const hasNewLine = + currentElement.loc.end.line !== nextElement.loc.start.line; + + if (!hasNewLine && needsNewlines) { + context.report({ + node: currentElement, + messageId: "expectedBetween", + fix: fixer => fixer.insertTextBefore(nextElement, "\n"), + }); + } + } + } + + /** + * Gets the left paren and right paren tokens of a node. + * @param {ASTNode} node The node with parens + * @throws {TypeError} Unexpected node type. + * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token. + * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression + * with a single parameter) + */ + function getParenTokens(node) { + switch (node.type) { + case "NewExpression": + if ( + !node.arguments.length && + !( + astUtils.isOpeningParenToken( + sourceCode.getLastToken(node, { skip: 1 }), + ) && + astUtils.isClosingParenToken( + sourceCode.getLastToken(node), + ) && + node.callee.range[1] < node.range[1] + ) + ) { + // If the NewExpression does not have parens (e.g. `new Foo`), return null. + return null; + } + + // falls through + + case "CallExpression": + return { + leftParen: sourceCode.getTokenAfter( + node.callee, + astUtils.isOpeningParenToken, + ), + rightParen: sourceCode.getLastToken(node), + }; + + case "FunctionDeclaration": + case "FunctionExpression": { + const leftParen = sourceCode.getFirstToken( + node, + astUtils.isOpeningParenToken, + ); + const rightParen = node.params.length + ? sourceCode.getTokenAfter( + node.params.at(-1), + astUtils.isClosingParenToken, + ) + : sourceCode.getTokenAfter(leftParen); + + return { leftParen, rightParen }; + } + + case "ArrowFunctionExpression": { + const firstToken = sourceCode.getFirstToken(node, { + skip: node.async ? 1 : 0, + }); + + if (!astUtils.isOpeningParenToken(firstToken)) { + // If the ArrowFunctionExpression has a single param without parens, return null. + return null; + } + + const rightParen = node.params.length + ? sourceCode.getTokenAfter( + node.params.at(-1), + astUtils.isClosingParenToken, + ) + : sourceCode.getTokenAfter(firstToken); + + return { + leftParen: firstToken, + rightParen, + }; + } + + case "ImportExpression": { + const leftParen = sourceCode.getFirstToken(node, 1); + const rightParen = sourceCode.getLastToken(node); + + return { leftParen, rightParen }; + } + + default: + throw new TypeError( + `unexpected node with type ${node.type}`, + ); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + [[ + "ArrowFunctionExpression", + "CallExpression", + "FunctionDeclaration", + "FunctionExpression", + "ImportExpression", + "NewExpression", + ]](node) { + const parens = getParenTokens(node); + let params; + + if (node.type === "ImportExpression") { + params = [node.source]; + } else if (astUtils.isFunction(node)) { + params = node.params; + } else { + params = node.arguments; + } + + if (parens) { + validateParens(parens, params); + + if (multilineArgumentsOption) { + validateArguments(parens, params); + } + } + }, + }; + }, }; diff --git a/lib/rules/generator-star-spacing.js b/lib/rules/generator-star-spacing.js index 9aeb7c857662..9973f9afe421 100644 --- a/lib/rules/generator-star-spacing.js +++ b/lib/rules/generator-star-spacing.js @@ -11,217 +11,236 @@ //------------------------------------------------------------------------------ const OVERRIDE_SCHEMA = { - oneOf: [ - { - enum: ["before", "after", "both", "neither"] - }, - { - type: "object", - properties: { - before: { type: "boolean" }, - after: { type: "boolean" } - }, - additionalProperties: false - } - ] + oneOf: [ + { + enum: ["before", "after", "both", "neither"], + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "generator-star-spacing", - url: "https://eslint.style/rules/js/generator-star-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing around `*` operators in generator functions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/generator-star-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["before", "after", "both", "neither"] - }, - { - type: "object", - properties: { - before: { type: "boolean" }, - after: { type: "boolean" }, - named: OVERRIDE_SCHEMA, - anonymous: OVERRIDE_SCHEMA, - method: OVERRIDE_SCHEMA - }, - additionalProperties: false - } - ] - } - ], - - messages: { - missingBefore: "Missing space before *.", - missingAfter: "Missing space after *.", - unexpectedBefore: "Unexpected space before *.", - unexpectedAfter: "Unexpected space after *." - } - }, - - create(context) { - - const optionDefinitions = { - before: { before: true, after: false }, - after: { before: false, after: true }, - both: { before: true, after: true }, - neither: { before: false, after: false } - }; - - /** - * Returns resolved option definitions based on an option and defaults - * @param {any} option The option object or string value - * @param {Object} defaults The defaults to use if options are not present - * @returns {Object} the resolved object definition - */ - function optionToDefinition(option, defaults) { - if (!option) { - return defaults; - } - - return typeof option === "string" - ? optionDefinitions[option] - : Object.assign({}, defaults, option); - } - - const modes = (function(option) { - const defaults = optionToDefinition(option, optionDefinitions.before); - - return { - named: optionToDefinition(option.named, defaults), - anonymous: optionToDefinition(option.anonymous, defaults), - method: optionToDefinition(option.method, defaults) - }; - }(context.options[0] || {})); - - const sourceCode = context.sourceCode; - - /** - * Checks if the given token is a star token or not. - * @param {Token} token The token to check. - * @returns {boolean} `true` if the token is a star token. - */ - function isStarToken(token) { - return token.value === "*" && token.type === "Punctuator"; - } - - /** - * Gets the generator star token of the given function node. - * @param {ASTNode} node The function node to get. - * @returns {Token} Found star token. - */ - function getStarToken(node) { - return sourceCode.getFirstToken( - (node.parent.method || node.parent.type === "MethodDefinition") ? node.parent : node, - isStarToken - ); - } - - /** - * capitalize a given string. - * @param {string} str the given string. - * @returns {string} the capitalized string. - */ - function capitalize(str) { - return str[0].toUpperCase() + str.slice(1); - } - - /** - * Checks the spacing between two tokens before or after the star token. - * @param {string} kind Either "named", "anonymous", or "method" - * @param {string} side Either "before" or "after". - * @param {Token} leftToken `function` keyword token if side is "before", or - * star token if side is "after". - * @param {Token} rightToken Star token if side is "before", or identifier - * token if side is "after". - * @returns {void} - */ - function checkSpacing(kind, side, leftToken, rightToken) { - if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) { - const after = leftToken.value === "*"; - const spaceRequired = modes[kind][side]; - const node = after ? leftToken : rightToken; - const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`; - - context.report({ - node, - messageId, - fix(fixer) { - if (spaceRequired) { - if (after) { - return fixer.insertTextAfter(node, " "); - } - return fixer.insertTextBefore(node, " "); - } - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } - } - - /** - * Enforces the spacing around the star if node is a generator function. - * @param {ASTNode} node A function expression or declaration node. - * @returns {void} - */ - function checkFunction(node) { - if (!node.generator) { - return; - } - - const starToken = getStarToken(node); - const prevToken = sourceCode.getTokenBefore(starToken); - const nextToken = sourceCode.getTokenAfter(starToken); - - let kind = "named"; - - if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) { - kind = "method"; - } else if (!node.id) { - kind = "anonymous"; - } - - // Only check before when preceded by `function`|`static` keyword - if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) { - checkSpacing(kind, "before", prevToken, starToken); - } - - checkSpacing(kind, "after", starToken, nextToken); - } - - return { - FunctionDeclaration: checkFunction, - FunctionExpression: checkFunction - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "generator-star-spacing", + url: "https://eslint.style/rules/js/generator-star-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing around `*` operators in generator functions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/generator-star-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"], + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" }, + named: OVERRIDE_SCHEMA, + anonymous: OVERRIDE_SCHEMA, + method: OVERRIDE_SCHEMA, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + missingBefore: "Missing space before *.", + missingAfter: "Missing space after *.", + unexpectedBefore: "Unexpected space before *.", + unexpectedAfter: "Unexpected space after *.", + }, + }, + + create(context) { + const optionDefinitions = { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false }, + }; + + /** + * Returns resolved option definitions based on an option and defaults + * @param {any} option The option object or string value + * @param {Object} defaults The defaults to use if options are not present + * @returns {Object} the resolved object definition + */ + function optionToDefinition(option, defaults) { + if (!option) { + return defaults; + } + + return typeof option === "string" + ? optionDefinitions[option] + : Object.assign({}, defaults, option); + } + + const modes = (function (option) { + const defaults = optionToDefinition( + option, + optionDefinitions.before, + ); + + return { + named: optionToDefinition(option.named, defaults), + anonymous: optionToDefinition(option.anonymous, defaults), + method: optionToDefinition(option.method, defaults), + }; + })(context.options[0] || {}); + + const sourceCode = context.sourceCode; + + /** + * Checks if the given token is a star token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is a star token. + */ + function isStarToken(token) { + return token.value === "*" && token.type === "Punctuator"; + } + + /** + * Gets the generator star token of the given function node. + * @param {ASTNode} node The function node to get. + * @returns {Token} Found star token. + */ + function getStarToken(node) { + return sourceCode.getFirstToken( + node.parent.method || node.parent.type === "MethodDefinition" + ? node.parent + : node, + isStarToken, + ); + } + + /** + * capitalize a given string. + * @param {string} str the given string. + * @returns {string} the capitalized string. + */ + function capitalize(str) { + return str[0].toUpperCase() + str.slice(1); + } + + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} kind Either "named", "anonymous", or "method" + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(kind, side, leftToken, rightToken) { + if ( + !!(rightToken.range[0] - leftToken.range[1]) !== + modes[kind][side] + ) { + const after = leftToken.value === "*"; + const spaceRequired = modes[kind][side]; + const node = after ? leftToken : rightToken; + const messageId = `${spaceRequired ? "missing" : "unexpected"}${capitalize(side)}`; + + context.report({ + node, + messageId, + fix(fixer) { + if (spaceRequired) { + if (after) { + return fixer.insertTextAfter(node, " "); + } + return fixer.insertTextBefore(node, " "); + } + return fixer.removeRange([ + leftToken.range[1], + rightToken.range[0], + ]); + }, + }); + } + } + + /** + * Enforces the spacing around the star if node is a generator function. + * @param {ASTNode} node A function expression or declaration node. + * @returns {void} + */ + function checkFunction(node) { + if (!node.generator) { + return; + } + + const starToken = getStarToken(node); + const prevToken = sourceCode.getTokenBefore(starToken); + const nextToken = sourceCode.getTokenAfter(starToken); + + let kind = "named"; + + if ( + node.parent.type === "MethodDefinition" || + (node.parent.type === "Property" && node.parent.method) + ) { + kind = "method"; + } else if (!node.id) { + kind = "anonymous"; + } + + // Only check before when preceded by `function`|`static` keyword + if ( + !( + kind === "method" && + starToken === sourceCode.getFirstToken(node.parent) + ) + ) { + checkSpacing(kind, "before", prevToken, starToken); + } + + checkSpacing(kind, "after", starToken, nextToken); + } + + return { + FunctionDeclaration: checkFunction, + FunctionExpression: checkFunction, + }; + }, }; diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js index f012876b4e71..243982252a06 100644 --- a/lib/rules/getter-return.js +++ b/lib/rules/getter-return.js @@ -23,14 +23,13 @@ const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/u; * @returns {boolean} True if any segment is reachable; false otherwise. */ function isAnySegmentReachable(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; } //------------------------------------------------------------------------------ @@ -39,168 +38,205 @@ function isAnySegmentReachable(segments) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - allowImplicit: false - }], - - docs: { - description: "Enforce `return` statements in getters", - recommended: true, - url: "https://eslint.org/docs/latest/rules/getter-return" - }, - - fixable: null, - - schema: [ - { - type: "object", - properties: { - allowImplicit: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - expected: "Expected to return a value in {{name}}.", - expectedAlways: "Expected {{name}} to always return a value." - } - }, - - create(context) { - const [{ allowImplicit }] = context.options; - const sourceCode = context.sourceCode; - - let funcInfo = { - upper: null, - codePath: null, - hasReturn: false, - shouldCheck: false, - node: null, - currentSegments: [] - }; - - /** - * Checks whether or not the last code path segment is reachable. - * Then reports this function if the segment is reachable. - * - * If the last code path segment is reachable, there are paths which are not - * returned or thrown. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkLastSegment(node) { - if (funcInfo.shouldCheck && - isAnySegmentReachable(funcInfo.currentSegments) - ) { - context.report({ - node, - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - messageId: funcInfo.hasReturn ? "expectedAlways" : "expected", - data: { - name: astUtils.getFunctionNameWithKind(funcInfo.node) - } - }); - } - } - - /** - * Checks whether a node means a getter function. - * @param {ASTNode} node a node to check. - * @returns {boolean} if node means a getter, return true; else return false. - */ - function isGetter(node) { - const parent = node.parent; - - if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") { - if (parent.kind === "get") { - return true; - } - if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") { - - // Object.defineProperty() or Reflect.defineProperty() - if (parent.parent.parent.type === "CallExpression") { - const callNode = parent.parent.parent.callee; - - if (astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperty") || - astUtils.isSpecificMemberAccess(callNode, "Reflect", "defineProperty")) { - return true; - } - } - - // Object.defineProperties() or Object.create() - if (parent.parent.parent.type === "Property" && - parent.parent.parent.parent.type === "ObjectExpression" && - parent.parent.parent.parent.parent.type === "CallExpression") { - const callNode = parent.parent.parent.parent.parent.callee; - - return astUtils.isSpecificMemberAccess(callNode, "Object", "defineProperties") || - astUtils.isSpecificMemberAccess(callNode, "Object", "create"); - } - } - } - return false; - } - return { - - // Stacks this function's information. - onCodePathStart(codePath, node) { - funcInfo = { - upper: funcInfo, - codePath, - hasReturn: false, - shouldCheck: isGetter(node), - node, - currentSegments: new Set() - }; - }, - - // Pops this function's information. - onCodePathEnd() { - funcInfo = funcInfo.upper; - }, - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - // Checks the return statement is valid. - ReturnStatement(node) { - if (funcInfo.shouldCheck) { - funcInfo.hasReturn = true; - - // if allowImplicit: false, should also check node.argument - if (!allowImplicit && !node.argument) { - context.report({ - node, - messageId: "expected", - data: { - name: astUtils.getFunctionNameWithKind(funcInfo.node) - } - }); - } - } - }, - - // Reports a given function if the last path is reachable. - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + allowImplicit: false, + }, + ], + + docs: { + description: "Enforce `return` statements in getters", + recommended: true, + url: "https://eslint.org/docs/latest/rules/getter-return", + }, + + fixable: null, + + schema: [ + { + type: "object", + properties: { + allowImplicit: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + expected: "Expected to return a value in {{name}}.", + expectedAlways: "Expected {{name}} to always return a value.", + }, + }, + + create(context) { + const [{ allowImplicit }] = context.options; + const sourceCode = context.sourceCode; + + let funcInfo = { + upper: null, + codePath: null, + hasReturn: false, + shouldCheck: false, + node: null, + currentSegments: [], + }; + + /** + * Checks whether or not the last code path segment is reachable. + * Then reports this function if the segment is reachable. + * + * If the last code path segment is reachable, there are paths which are not + * returned or thrown. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkLastSegment(node) { + if ( + funcInfo.shouldCheck && + isAnySegmentReachable(funcInfo.currentSegments) + ) { + context.report({ + node, + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + messageId: funcInfo.hasReturn + ? "expectedAlways" + : "expected", + data: { + name: astUtils.getFunctionNameWithKind(funcInfo.node), + }, + }); + } + } + + /** + * Checks whether a node means a getter function. + * @param {ASTNode} node a node to check. + * @returns {boolean} if node means a getter, return true; else return false. + */ + function isGetter(node) { + const parent = node.parent; + + if ( + TARGET_NODE_TYPE.test(node.type) && + node.body.type === "BlockStatement" + ) { + if (parent.kind === "get") { + return true; + } + if ( + parent.type === "Property" && + astUtils.getStaticPropertyName(parent) === "get" && + parent.parent.type === "ObjectExpression" + ) { + // Object.defineProperty() or Reflect.defineProperty() + if (parent.parent.parent.type === "CallExpression") { + const callNode = parent.parent.parent.callee; + + if ( + astUtils.isSpecificMemberAccess( + callNode, + "Object", + "defineProperty", + ) || + astUtils.isSpecificMemberAccess( + callNode, + "Reflect", + "defineProperty", + ) + ) { + return true; + } + } + + // Object.defineProperties() or Object.create() + if ( + parent.parent.parent.type === "Property" && + parent.parent.parent.parent.type === + "ObjectExpression" && + parent.parent.parent.parent.parent.type === + "CallExpression" + ) { + const callNode = + parent.parent.parent.parent.parent.callee; + + return ( + astUtils.isSpecificMemberAccess( + callNode, + "Object", + "defineProperties", + ) || + astUtils.isSpecificMemberAccess( + callNode, + "Object", + "create", + ) + ); + } + } + } + return false; + } + return { + // Stacks this function's information. + onCodePathStart(codePath, node) { + funcInfo = { + upper: funcInfo, + codePath, + hasReturn: false, + shouldCheck: isGetter(node), + node, + currentSegments: new Set(), + }; + }, + + // Pops this function's information. + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Checks the return statement is valid. + ReturnStatement(node) { + if (funcInfo.shouldCheck) { + funcInfo.hasReturn = true; + + // if allowImplicit: false, should also check node.argument + if (!allowImplicit && !node.argument) { + context.report({ + node, + messageId: "expected", + data: { + name: astUtils.getFunctionNameWithKind( + funcInfo.node, + ), + }, + }); + } + } + }, + + // Reports a given function if the last path is reachable. + "FunctionExpression:exit": checkLastSegment, + "ArrowFunctionExpression:exit": checkLastSegment, + }; + }, }; diff --git a/lib/rules/global-require.js b/lib/rules/global-require.js index b395cad8f4b3..da869e64778e 100644 --- a/lib/rules/global-require.js +++ b/lib/rules/global-require.js @@ -7,15 +7,15 @@ "use strict"; const ACCEPTABLE_PARENTS = new Set([ - "AssignmentExpression", - "VariableDeclarator", - "MemberExpression", - "ExpressionStatement", - "CallExpression", - "ConditionalExpression", - "Program", - "VariableDeclaration", - "ChainExpression" + "AssignmentExpression", + "VariableDeclarator", + "MemberExpression", + "ExpressionStatement", + "CallExpression", + "ConditionalExpression", + "Program", + "VariableDeclaration", + "ChainExpression", ]); /** @@ -25,16 +25,18 @@ const ACCEPTABLE_PARENTS = new Set([ * @returns {Reference|null} Returns the found reference or null if none were found. */ function findReference(scope, node) { - const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] && - reference.identifier.range[1] === node.range[1]); - - if (references.length === 1) { - return references[0]; - } - - /* c8 ignore next */ - return null; - + const references = scope.references.filter( + reference => + reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1], + ); + + if (references.length === 1) { + return references[0]; + } + + /* c8 ignore next */ + return null; } /** @@ -44,63 +46,72 @@ function findReference(scope, node) { * @returns {boolean} Whether or not the name is shadowed. */ function isShadowed(scope, node) { - const reference = findReference(scope, node); + const reference = findReference(scope, node); - return reference && reference.resolved && reference.resolved.defs.length > 0; + return ( + reference && reference.resolved && reference.resolved.defs.length > 0 + ); } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - recommended: false, - url: "https://eslint.org/docs/latest/rules/global-require" - }, - - schema: [], - messages: { - unexpected: "Unexpected require()." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - const currentScope = sourceCode.getScope(node); - - if (node.callee.name === "require" && !isShadowed(currentScope, node.callee)) { - const isGoodRequire = sourceCode.getAncestors(node).every(parent => ACCEPTABLE_PARENTS.has(parent.type)); - - if (!isGoodRequire) { - context.report({ node, messageId: "unexpected" }); - } - } - } - }; - } + meta: { + 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", + recommended: false, + url: "https://eslint.org/docs/latest/rules/global-require", + }, + + schema: [], + messages: { + unexpected: "Unexpected require().", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + CallExpression(node) { + const currentScope = sourceCode.getScope(node); + + if ( + node.callee.name === "require" && + !isShadowed(currentScope, node.callee) + ) { + const isGoodRequire = sourceCode + .getAncestors(node) + .every(parent => ACCEPTABLE_PARENTS.has(parent.type)); + + if (!isGoodRequire) { + context.report({ node, messageId: "unexpected" }); + } + } + }, + }; + }, }; diff --git a/lib/rules/grouped-accessor-pairs.js b/lib/rules/grouped-accessor-pairs.js index cd627b479bad..0f634121d29b 100644 --- a/lib/rules/grouped-accessor-pairs.js +++ b/lib/rules/grouped-accessor-pairs.js @@ -40,20 +40,23 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the lists have same tokens. */ function areEqualTokenLists(left, right) { - if (left.length !== right.length) { - return false; - } - - for (let i = 0; i < left.length; i++) { - const leftToken = left[i], - rightToken = right[i]; - - if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) { - return false; - } - } - - return true; + if (left.length !== right.length) { + return false; + } + + for (let i = 0; i < left.length; i++) { + const leftToken = left[i], + rightToken = right[i]; + + if ( + leftToken.type !== rightToken.type || + leftToken.value !== rightToken.value + ) { + return false; + } + } + + return true; } /** @@ -63,18 +66,16 @@ function areEqualTokenLists(left, right) { * @returns {boolean} `true` if the keys are equal. */ function areEqualKeys(left, right) { - if (typeof left === "string" && typeof right === "string") { - - // Statically computed names. - return left === right; - } - if (Array.isArray(left) && Array.isArray(right)) { - - // Token lists. - return areEqualTokenLists(left, right); - } - - return false; + if (typeof left === "string" && typeof right === "string") { + // Statically computed names. + return left === right; + } + if (Array.isArray(left) && Array.isArray(right)) { + // Token lists. + return areEqualTokenLists(left, right); + } + + return false; } /** @@ -83,7 +84,7 @@ function areEqualKeys(left, right) { * @returns {boolean} `true` if the node is of an accessor kind. */ function isAccessorKind(node) { - return node.kind === "get" || node.kind === "set"; + return node.kind === "get" || node.kind === "set"; } //------------------------------------------------------------------------------ @@ -92,126 +93,145 @@ function isAccessorKind(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: ["anyOrder"], - - docs: { - description: "Require grouped accessor pairs in object literals and classes", - recommended: false, - url: "https://eslint.org/docs/latest/rules/grouped-accessor-pairs" - }, - - schema: [ - { - enum: ["anyOrder", "getBeforeSet", "setBeforeGet"] - } - ], - - messages: { - notGrouped: "Accessor pair {{ formerName }} and {{ latterName }} should be grouped.", - invalidOrder: "Expected {{ latterName }} to be before {{ formerName }}." - } - }, - - create(context) { - const [order] = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports the given accessor pair. - * @param {string} messageId messageId to report. - * @param {ASTNode} formerNode getter/setter node that is defined before `latterNode`. - * @param {ASTNode} latterNode getter/setter node that is defined after `formerNode`. - * @returns {void} - * @private - */ - function report(messageId, formerNode, latterNode) { - context.report({ - node: latterNode, - messageId, - loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode), - data: { - formerName: astUtils.getFunctionNameWithKind(formerNode.value), - latterName: astUtils.getFunctionNameWithKind(latterNode.value) - } - }); - } - - /** - * Checks accessor pairs in the given list of nodes. - * @param {ASTNode[]} nodes The list to check. - * @param {Function} shouldCheck – Predicate that returns `true` if the node should be checked. - * @returns {void} - * @private - */ - function checkList(nodes, shouldCheck) { - const accessors = []; - let found = false; - - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - - if (shouldCheck(node) && isAccessorKind(node)) { - - // Creates a new `AccessorData` object for the given getter or setter node. - const name = astUtils.getStaticPropertyName(node); - const key = (name !== null) ? name : sourceCode.getTokens(node.key); - - // Merges the given `AccessorData` object into the given accessors list. - for (let j = 0; j < accessors.length; j++) { - const accessor = accessors[j]; - - if (areEqualKeys(accessor.key, key)) { - accessor.getters.push(...node.kind === "get" ? [node] : []); - accessor.setters.push(...node.kind === "set" ? [node] : []); - found = true; - break; - } - } - if (!found) { - accessors.push({ - key, - getters: node.kind === "get" ? [node] : [], - setters: node.kind === "set" ? [node] : [] - }); - } - found = false; - } - } - - for (const { getters, setters } of accessors) { - - // Don't report accessor properties that have duplicate getters or setters. - if (getters.length === 1 && setters.length === 1) { - const [getter] = getters, - [setter] = setters, - getterIndex = nodes.indexOf(getter), - setterIndex = nodes.indexOf(setter), - formerNode = getterIndex < setterIndex ? getter : setter, - latterNode = getterIndex < setterIndex ? setter : getter; - - if (Math.abs(getterIndex - setterIndex) > 1) { - report("notGrouped", formerNode, latterNode); - } else if ( - (order === "getBeforeSet" && getterIndex > setterIndex) || - (order === "setBeforeGet" && getterIndex < setterIndex) - ) { - report("invalidOrder", formerNode, latterNode); - } - } - } - } - - return { - ObjectExpression(node) { - checkList(node.properties, n => n.type === "Property"); - }, - ClassBody(node) { - checkList(node.body, n => n.type === "MethodDefinition" && !n.static); - checkList(node.body, n => n.type === "MethodDefinition" && n.static); - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: ["anyOrder"], + + docs: { + description: + "Require grouped accessor pairs in object literals and classes", + recommended: false, + url: "https://eslint.org/docs/latest/rules/grouped-accessor-pairs", + }, + + schema: [ + { + enum: ["anyOrder", "getBeforeSet", "setBeforeGet"], + }, + ], + + messages: { + notGrouped: + "Accessor pair {{ formerName }} and {{ latterName }} should be grouped.", + invalidOrder: + "Expected {{ latterName }} to be before {{ formerName }}.", + }, + }, + + create(context) { + const [order] = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports the given accessor pair. + * @param {string} messageId messageId to report. + * @param {ASTNode} formerNode getter/setter node that is defined before `latterNode`. + * @param {ASTNode} latterNode getter/setter node that is defined after `formerNode`. + * @returns {void} + * @private + */ + function report(messageId, formerNode, latterNode) { + context.report({ + node: latterNode, + messageId, + loc: astUtils.getFunctionHeadLoc(latterNode.value, sourceCode), + data: { + formerName: astUtils.getFunctionNameWithKind( + formerNode.value, + ), + latterName: astUtils.getFunctionNameWithKind( + latterNode.value, + ), + }, + }); + } + + /** + * Checks accessor pairs in the given list of nodes. + * @param {ASTNode[]} nodes The list to check. + * @param {Function} shouldCheck – Predicate that returns `true` if the node should be checked. + * @returns {void} + * @private + */ + function checkList(nodes, shouldCheck) { + const accessors = []; + let found = false; + + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + + if (shouldCheck(node) && isAccessorKind(node)) { + // Creates a new `AccessorData` object for the given getter or setter node. + const name = astUtils.getStaticPropertyName(node); + const key = + name !== null ? name : sourceCode.getTokens(node.key); + + // Merges the given `AccessorData` object into the given accessors list. + for (let j = 0; j < accessors.length; j++) { + const accessor = accessors[j]; + + if (areEqualKeys(accessor.key, key)) { + accessor.getters.push( + ...(node.kind === "get" ? [node] : []), + ); + accessor.setters.push( + ...(node.kind === "set" ? [node] : []), + ); + found = true; + break; + } + } + if (!found) { + accessors.push({ + key, + getters: node.kind === "get" ? [node] : [], + setters: node.kind === "set" ? [node] : [], + }); + } + found = false; + } + } + + for (const { getters, setters } of accessors) { + // Don't report accessor properties that have duplicate getters or setters. + if (getters.length === 1 && setters.length === 1) { + const [getter] = getters, + [setter] = setters, + getterIndex = nodes.indexOf(getter), + setterIndex = nodes.indexOf(setter), + formerNode = + getterIndex < setterIndex ? getter : setter, + latterNode = + getterIndex < setterIndex ? setter : getter; + + if (Math.abs(getterIndex - setterIndex) > 1) { + report("notGrouped", formerNode, latterNode); + } else if ( + (order === "getBeforeSet" && + getterIndex > setterIndex) || + (order === "setBeforeGet" && getterIndex < setterIndex) + ) { + report("invalidOrder", formerNode, latterNode); + } + } + } + } + + return { + ObjectExpression(node) { + checkList(node.properties, n => n.type === "Property"); + }, + ClassBody(node) { + checkList( + node.body, + n => n.type === "MethodDefinition" && !n.static, + ); + checkList( + node.body, + n => n.type === "MethodDefinition" && n.static, + ); + }, + }; + }, }; diff --git a/lib/rules/guard-for-in.js b/lib/rules/guard-for-in.js index d6e70d0d75f7..86789c6de2b2 100644 --- a/lib/rules/guard-for-in.js +++ b/lib/rules/guard-for-in.js @@ -11,66 +11,75 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require `for-in` loops to include an `if` statement", - recommended: false, - url: "https://eslint.org/docs/latest/rules/guard-for-in" - }, - - schema: [], - messages: { - wrap: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype." - } - }, - - create(context) { - - return { - - ForInStatement(node) { - const body = node.body; - - // empty statement - if (body.type === "EmptyStatement") { - return; - } - - // if statement - if (body.type === "IfStatement") { - return; - } - - // empty block - if (body.type === "BlockStatement" && body.body.length === 0) { - return; - } - - // block with just if statement - if (body.type === "BlockStatement" && body.body.length === 1 && body.body[0].type === "IfStatement") { - return; - } - - // block that starts with if statement - if (body.type === "BlockStatement" && body.body.length >= 1 && body.body[0].type === "IfStatement") { - const i = body.body[0]; - - // ... whose consequent is a continue - if (i.consequent.type === "ContinueStatement") { - return; - } - - // ... whose consequent is a block that contains only a continue - if (i.consequent.type === "BlockStatement" && i.consequent.body.length === 1 && i.consequent.body[0].type === "ContinueStatement") { - return; - } - } - - context.report({ node, messageId: "wrap" }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Require `for-in` loops to include an `if` statement", + recommended: false, + url: "https://eslint.org/docs/latest/rules/guard-for-in", + }, + + schema: [], + messages: { + wrap: "The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype.", + }, + }, + + create(context) { + return { + ForInStatement(node) { + const body = node.body; + + // empty statement + if (body.type === "EmptyStatement") { + return; + } + + // if statement + if (body.type === "IfStatement") { + return; + } + + // empty block + if (body.type === "BlockStatement" && body.body.length === 0) { + return; + } + + // block with just if statement + if ( + body.type === "BlockStatement" && + body.body.length === 1 && + body.body[0].type === "IfStatement" + ) { + return; + } + + // block that starts with if statement + if ( + body.type === "BlockStatement" && + body.body.length >= 1 && + body.body[0].type === "IfStatement" + ) { + const i = body.body[0]; + + // ... whose consequent is a continue + if (i.consequent.type === "ContinueStatement") { + return; + } + + // ... whose consequent is a block that contains only a continue + if ( + i.consequent.type === "BlockStatement" && + i.consequent.body.length === 1 && + i.consequent.body[0].type === "ContinueStatement" + ) { + return; + } + } + + context.report({ node, messageId: "wrap" }); + }, + }; + }, }; diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js index 6d7d046dca90..83e1a3d87919 100644 --- a/lib/rules/handle-callback-err.js +++ b/lib/rules/handle-callback-err.js @@ -12,106 +12,111 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - recommended: false, - url: "https://eslint.org/docs/latest/rules/handle-callback-err" - }, - - schema: [ - { - type: "string" - } - ], - messages: { - expected: "Expected error to be handled." - } - }, - - create(context) { - - const errorArgument = context.options[0] || "err"; - const sourceCode = context.sourceCode; - - /** - * Checks if the given argument should be interpreted as a regexp pattern. - * @param {string} stringToCheck The string which should be checked. - * @returns {boolean} Whether or not the string should be interpreted as a pattern. - */ - function isPattern(stringToCheck) { - const firstChar = stringToCheck[0]; - - return firstChar === "^"; - } - - /** - * Checks if the given name matches the configured error argument. - * @param {string} name The name which should be compared. - * @returns {boolean} Whether or not the given name matches the configured error variable name. - */ - function matchesConfiguredErrorName(name) { - if (isPattern(errorArgument)) { - const regexp = new RegExp(errorArgument, "u"); - - return regexp.test(name); - } - return name === errorArgument; - } - - /** - * Get the parameters of a given function scope. - * @param {Object} scope The function scope. - * @returns {Array} All parameters of the given scope. - */ - function getParameters(scope) { - return scope.variables.filter(variable => variable.defs[0] && variable.defs[0].type === "Parameter"); - } - - /** - * Check to see if we're handling the error object properly. - * @param {ASTNode} node The AST node to check. - * @returns {void} - */ - function checkForError(node) { - const scope = sourceCode.getScope(node), - parameters = getParameters(scope), - firstParameter = parameters[0]; - - if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) { - if (firstParameter.references.length === 0) { - context.report({ node, messageId: "expected" }); - } - } - } - - return { - FunctionDeclaration: checkForError, - FunctionExpression: checkForError, - ArrowFunctionExpression: checkForError - }; - - } + meta: { + 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", + recommended: false, + url: "https://eslint.org/docs/latest/rules/handle-callback-err", + }, + + schema: [ + { + type: "string", + }, + ], + messages: { + expected: "Expected error to be handled.", + }, + }, + + create(context) { + const errorArgument = context.options[0] || "err"; + const sourceCode = context.sourceCode; + + /** + * Checks if the given argument should be interpreted as a regexp pattern. + * @param {string} stringToCheck The string which should be checked. + * @returns {boolean} Whether or not the string should be interpreted as a pattern. + */ + function isPattern(stringToCheck) { + const firstChar = stringToCheck[0]; + + return firstChar === "^"; + } + + /** + * Checks if the given name matches the configured error argument. + * @param {string} name The name which should be compared. + * @returns {boolean} Whether or not the given name matches the configured error variable name. + */ + function matchesConfiguredErrorName(name) { + if (isPattern(errorArgument)) { + const regexp = new RegExp(errorArgument, "u"); + + return regexp.test(name); + } + return name === errorArgument; + } + + /** + * Get the parameters of a given function scope. + * @param {Object} scope The function scope. + * @returns {Array} All parameters of the given scope. + */ + function getParameters(scope) { + return scope.variables.filter( + variable => + variable.defs[0] && variable.defs[0].type === "Parameter", + ); + } + + /** + * Check to see if we're handling the error object properly. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function checkForError(node) { + const scope = sourceCode.getScope(node), + parameters = getParameters(scope), + firstParameter = parameters[0]; + + if ( + firstParameter && + matchesConfiguredErrorName(firstParameter.name) + ) { + if (firstParameter.references.length === 0) { + context.report({ node, messageId: "expected" }); + } + } + } + + return { + FunctionDeclaration: checkForError, + FunctionExpression: checkForError, + ArrowFunctionExpression: checkForError, + }; + }, }; diff --git a/lib/rules/id-blacklist.js b/lib/rules/id-blacklist.js index 354e4bb7941b..c37170df80ee 100644 --- a/lib/rules/id-blacklist.js +++ b/lib/rules/id-blacklist.js @@ -17,29 +17,19 @@ * @returns {boolean} `true` if the node is assignment target. */ function isAssignmentTarget(node) { - const parent = node.parent; - - return ( - - // normal assignment - ( - parent.type === "AssignmentExpression" && - parent.left === node - ) || - - // destructuring - parent.type === "ArrayPattern" || - parent.type === "RestElement" || - ( - parent.type === "Property" && - parent.value === node && - parent.parent.type === "ObjectPattern" - ) || - ( - parent.type === "AssignmentPattern" && - parent.left === node - ) - ); + const parent = node.parent; + + return ( + // normal assignment + (parent.type === "AssignmentExpression" && parent.left === node) || + // destructuring + parent.type === "ArrayPattern" || + parent.type === "RestElement" || + (parent.type === "Property" && + parent.value === node && + parent.parent.type === "ObjectPattern") || + (parent.type === "AssignmentPattern" && parent.left === node) + ); } /** @@ -52,21 +42,17 @@ function isAssignmentTarget(node) { * @returns {boolean} `true` if the node is a renamed import. */ function isRenamedImport(node) { - const parent = node.parent; - - return ( - ( - parent.type === "ImportSpecifier" && - parent.imported !== parent.local && - parent.imported === node - ) || - ( - parent.type === "ExportSpecifier" && - parent.parent.source && // re-export - parent.local !== parent.exported && - parent.local === node - ) - ); + const parent = node.parent; + + return ( + (parent.type === "ImportSpecifier" && + parent.imported !== parent.local && + parent.imported === node) || + (parent.type === "ExportSpecifier" && + parent.parent.source && // re-export + parent.local !== parent.exported && + parent.local === node) + ); } /** @@ -78,17 +64,15 @@ function isRenamedImport(node) { * @returns {boolean} `true` if the node is a renamed node in an ObjectPattern destructuring. */ function isRenamedInDestructuring(node) { - const parent = node.parent; - - return ( - ( - !parent.computed && - parent.type === "Property" && - parent.parent.type === "ObjectPattern" && - parent.value !== node && - parent.key === node - ) - ); + const parent = node.parent; + + return ( + !parent.computed && + parent.type === "Property" && + parent.parent.type === "ObjectPattern" && + parent.value !== node && + parent.key === node + ); } /** @@ -97,13 +81,13 @@ function isRenamedInDestructuring(node) { * @returns {boolean} `true` if the node is a shorthand property definition. */ function isShorthandPropertyDefinition(node) { - const parent = node.parent; + const parent = node.parent; - return ( - parent.type === "Property" && - parent.parent.type === "ObjectExpression" && - parent.shorthand - ); + return ( + parent.type === "Property" && + parent.parent.type === "ObjectExpression" && + parent.shorthand + ); } //------------------------------------------------------------------------------ @@ -112,147 +96,146 @@ function isShorthandPropertyDefinition(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - recommended: false, - url: "https://eslint.org/docs/latest/rules/id-blacklist" - }, - - schema: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - }, - messages: { - restricted: "Identifier '{{name}}' is restricted." - } - }, - - create(context) { - - const denyList = new Set(context.options); - const reportedNodes = new Set(); - const sourceCode = context.sourceCode; - - let globalScope; - - /** - * Checks whether the given name is restricted. - * @param {string} name The name to check. - * @returns {boolean} `true` if the name is restricted. - * @private - */ - function isRestricted(name) { - return denyList.has(name); - } - - /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. - */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); - - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); - } - - /** - * Determines whether the given node should be checked. - * @param {ASTNode} node `Identifier` node. - * @returns {boolean} `true` if the node should be checked. - */ - function shouldCheck(node) { - const parent = node.parent; - - /* - * Member access has special rules for checking property names. - * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. - * Write access isn't allowed, because it potentially creates a new property with a restricted name. - */ - if ( - parent.type === "MemberExpression" && - parent.property === node && - !parent.computed - ) { - return isAssignmentTarget(parent); - } - - return ( - parent.type !== "CallExpression" && - parent.type !== "NewExpression" && - !isRenamedImport(node) && - !isRenamedInDestructuring(node) && - !( - isReferenceToGlobalVariable(node) && - !isShorthandPropertyDefinition(node) - ) - ); - } - - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - - /* - * We used the range instead of the node because it's possible - * for the same identifier to be represented by two different - * nodes, with the most clear example being shorthand properties: - * { foo } - * In this case, "foo" is represented by one node for the name - * and one for the value. The only way to know they are the same - * is to look at the range. - */ - if (!reportedNodes.has(node.range.toString())) { - context.report({ - node, - messageId: "restricted", - data: { - name: node.name - } - }); - reportedNodes.add(node.range.toString()); - } - - } - - return { - - Program(node) { - globalScope = sourceCode.getScope(node); - }, - - Identifier(node) { - if (isRestricted(node.name) && shouldCheck(node)) { - report(node); - } - } - }; - } + meta: { + 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", + recommended: false, + url: "https://eslint.org/docs/latest/rules/id-blacklist", + }, + + schema: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + messages: { + restricted: "Identifier '{{name}}' is restricted.", + }, + }, + + create(context) { + const denyList = new Set(context.options); + const reportedNodes = new Set(); + const sourceCode = context.sourceCode; + + let globalScope; + + /** + * Checks whether the given name is restricted. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is restricted. + * @private + */ + function isRestricted(name) { + return denyList.has(name); + } + + /** + * Checks whether the given node represents a reference to a global variable that is not declared in the source code. + * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a reference to a global variable. + */ + function isReferenceToGlobalVariable(node) { + const variable = globalScope.set.get(node.name); + + return ( + variable && + variable.defs.length === 0 && + variable.references.some(ref => ref.identifier === node) + ); + } + + /** + * Determines whether the given node should be checked. + * @param {ASTNode} node `Identifier` node. + * @returns {boolean} `true` if the node should be checked. + */ + function shouldCheck(node) { + const parent = node.parent; + + /* + * Member access has special rules for checking property names. + * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. + * Write access isn't allowed, because it potentially creates a new property with a restricted name. + */ + if ( + parent.type === "MemberExpression" && + parent.property === node && + !parent.computed + ) { + return isAssignmentTarget(parent); + } + + return ( + parent.type !== "CallExpression" && + parent.type !== "NewExpression" && + !isRenamedImport(node) && + !isRenamedInDestructuring(node) && + !( + isReferenceToGlobalVariable(node) && + !isShorthandPropertyDefinition(node) + ) + ); + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + context.report({ + node, + messageId: "restricted", + data: { + name: node.name, + }, + }); + reportedNodes.add(node.range.toString()); + } + } + + return { + Program(node) { + globalScope = sourceCode.getScope(node); + }, + + Identifier(node) { + if (isRestricted(node.name) && shouldCheck(node)) { + report(node); + } + }, + }; + }, }; diff --git a/lib/rules/id-denylist.js b/lib/rules/id-denylist.js index 8441c641dc67..ef56fb19820d 100644 --- a/lib/rules/id-denylist.js +++ b/lib/rules/id-denylist.js @@ -22,29 +22,19 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is assignment target. */ function isAssignmentTarget(node) { - const parent = node.parent; - - return ( - - // normal assignment - ( - parent.type === "AssignmentExpression" && - parent.left === node - ) || - - // destructuring - parent.type === "ArrayPattern" || - parent.type === "RestElement" || - ( - parent.type === "Property" && - parent.value === node && - parent.parent.type === "ObjectPattern" - ) || - ( - parent.type === "AssignmentPattern" && - parent.left === node - ) - ); + const parent = node.parent; + + return ( + // normal assignment + (parent.type === "AssignmentExpression" && parent.left === node) || + // destructuring + parent.type === "ArrayPattern" || + parent.type === "RestElement" || + (parent.type === "Property" && + parent.value === node && + parent.parent.type === "ObjectPattern") || + (parent.type === "AssignmentPattern" && parent.left === node) + ); } /** @@ -57,21 +47,17 @@ function isAssignmentTarget(node) { * @returns {boolean} `true` if the node is a renamed import. */ function isRenamedImport(node) { - const parent = node.parent; - - return ( - ( - parent.type === "ImportSpecifier" && - parent.imported !== parent.local && - parent.imported === node - ) || - ( - parent.type === "ExportSpecifier" && - parent.parent.source && // re-export - parent.local !== parent.exported && - parent.local === node - ) - ); + const parent = node.parent; + + return ( + (parent.type === "ImportSpecifier" && + parent.imported !== parent.local && + parent.imported === node) || + (parent.type === "ExportSpecifier" && + parent.parent.source && // re-export + parent.local !== parent.exported && + parent.local === node) + ); } /** @@ -83,16 +69,14 @@ function isRenamedImport(node) { * @returns {boolean} `true` if the node is in an ObjectPattern destructuring. */ function isPropertyNameInDestructuring(node) { - const parent = node.parent; - - return ( - ( - !parent.computed && - parent.type === "Property" && - parent.parent.type === "ObjectPattern" && - parent.key === node - ) - ); + const parent = node.parent; + + return ( + !parent.computed && + parent.type === "Property" && + parent.parent.type === "ObjectPattern" && + parent.key === node + ); } //------------------------------------------------------------------------------ @@ -101,142 +85,139 @@ function isPropertyNameInDestructuring(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [], - - docs: { - description: "Disallow specified identifiers", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/id-denylist" - }, - - schema: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - }, - messages: { - restricted: "Identifier '{{name}}' is restricted.", - restrictedPrivate: "Identifier '#{{name}}' is restricted." - } - }, - - create(context) { - const denyList = new Set(context.options); - const reportedNodes = new Set(); - const sourceCode = context.sourceCode; - - let globalScope; - - /** - * Checks whether the given name is restricted. - * @param {string} name The name to check. - * @returns {boolean} `true` if the name is restricted. - * @private - */ - function isRestricted(name) { - return denyList.has(name); - } - - /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. - */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); - - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); - } - - /** - * Determines whether the given node should be checked. - * @param {ASTNode} node `Identifier` node. - * @returns {boolean} `true` if the node should be checked. - */ - function shouldCheck(node) { - - // Import attributes are defined by environments, so naming conventions shouldn't apply to them - if (astUtils.isImportAttributeKey(node)) { - return false; - } - - const parent = node.parent; - - /* - * Member access has special rules for checking property names. - * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. - * Write access isn't allowed, because it potentially creates a new property with a restricted name. - */ - if ( - parent.type === "MemberExpression" && - parent.property === node && - !parent.computed - ) { - return isAssignmentTarget(parent); - } - - return ( - parent.type !== "CallExpression" && - parent.type !== "NewExpression" && - !isRenamedImport(node) && - !isPropertyNameInDestructuring(node) && - !isReferenceToGlobalVariable(node) - ); - } - - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - - /* - * We used the range instead of the node because it's possible - * for the same identifier to be represented by two different - * nodes, with the most clear example being shorthand properties: - * { foo } - * In this case, "foo" is represented by one node for the name - * and one for the value. The only way to know they are the same - * is to look at the range. - */ - if (!reportedNodes.has(node.range.toString())) { - const isPrivate = node.type === "PrivateIdentifier"; - - context.report({ - node, - messageId: isPrivate ? "restrictedPrivate" : "restricted", - data: { - name: node.name - } - }); - reportedNodes.add(node.range.toString()); - } - } - - return { - - Program(node) { - globalScope = sourceCode.getScope(node); - }, - - [[ - "Identifier", - "PrivateIdentifier" - ]](node) { - if (isRestricted(node.name) && shouldCheck(node)) { - report(node); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [], + + docs: { + description: "Disallow specified identifiers", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/id-denylist", + }, + + schema: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + messages: { + restricted: "Identifier '{{name}}' is restricted.", + restrictedPrivate: "Identifier '#{{name}}' is restricted.", + }, + }, + + create(context) { + const denyList = new Set(context.options); + const reportedNodes = new Set(); + const sourceCode = context.sourceCode; + + let globalScope; + + /** + * Checks whether the given name is restricted. + * @param {string} name The name to check. + * @returns {boolean} `true` if the name is restricted. + * @private + */ + function isRestricted(name) { + return denyList.has(name); + } + + /** + * Checks whether the given node represents a reference to a global variable that is not declared in the source code. + * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a reference to a global variable. + */ + function isReferenceToGlobalVariable(node) { + const variable = globalScope.set.get(node.name); + + return ( + variable && + variable.defs.length === 0 && + variable.references.some(ref => ref.identifier === node) + ); + } + + /** + * Determines whether the given node should be checked. + * @param {ASTNode} node `Identifier` node. + * @returns {boolean} `true` if the node should be checked. + */ + function shouldCheck(node) { + // Import attributes are defined by environments, so naming conventions shouldn't apply to them + if (astUtils.isImportAttributeKey(node)) { + return false; + } + + const parent = node.parent; + + /* + * Member access has special rules for checking property names. + * Read access to a property with a restricted name is allowed, because it can be on an object that user has no control over. + * Write access isn't allowed, because it potentially creates a new property with a restricted name. + */ + if ( + parent.type === "MemberExpression" && + parent.property === node && + !parent.computed + ) { + return isAssignmentTarget(parent); + } + + return ( + parent.type !== "CallExpression" && + parent.type !== "NewExpression" && + !isRenamedImport(node) && + !isPropertyNameInDestructuring(node) && + !isReferenceToGlobalVariable(node) + ); + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + const isPrivate = node.type === "PrivateIdentifier"; + + context.report({ + node, + messageId: isPrivate ? "restrictedPrivate" : "restricted", + data: { + name: node.name, + }, + }); + reportedNodes.add(node.range.toString()); + } + } + + return { + Program(node) { + globalScope = sourceCode.getScope(node); + }, + + [["Identifier", "PrivateIdentifier"]](node) { + if (isRestricted(node.name) && shouldCheck(node)) { + report(node); + } + }, + }; + }, }; diff --git a/lib/rules/id-length.js b/lib/rules/id-length.js index 700763641602..8a8aeeab0c94 100644 --- a/lib/rules/id-length.js +++ b/lib/rules/id-length.js @@ -11,7 +11,10 @@ //------------------------------------------------------------------------------ const { getGraphemeCount } = require("../shared/string-utils"); -const { getModuleExportName, isImportAttributeKey } = require("./utils/ast-utils"); +const { + getModuleExportName, + isImportAttributeKey, +} = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Rule Definition @@ -19,173 +22,196 @@ const { getModuleExportName, isImportAttributeKey } = require("./utils/ast-utils /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - min: { - type: "integer" - }, - max: { - type: "integer" - }, - exceptions: { - type: "array", - uniqueItems: true, - items: { - type: "string" - } - }, - exceptionPatterns: { - type: "array", - uniqueItems: true, - items: { - type: "string" - } - }, - properties: { - enum: ["always", "never"] - } - }, - additionalProperties: false - } - ], - messages: { - tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", - tooShortPrivate: "Identifier name '#{{name}}' is too short (< {{min}}).", - tooLong: "Identifier name '{{name}}' is too long (> {{max}}).", - tooLongPrivate: "Identifier name #'{{name}}' is too long (> {{max}})." - } - }, - - create(context) { - const [options] = context.options; - const { max: maxLength = Infinity, min: minLength } = options; - const properties = options.properties !== "never"; - const exceptions = new Set(options.exceptions); - const exceptionPatterns = options.exceptionPatterns.map(pattern => new RegExp(pattern, "u")); - const reportedNodes = new Set(); - - /** - * Checks if a string matches the provided exception patterns - * @param {string} name The string to check. - * @returns {boolean} if the string is a match - * @private - */ - function matchesExceptionPattern(name) { - return exceptionPatterns.some(pattern => pattern.test(name)); - } - - const SUPPORTED_EXPRESSIONS = { - MemberExpression: properties && function(parent) { - return !parent.computed && ( - - // regular property assignment - (parent.parent.left === parent && parent.parent.type === "AssignmentExpression" || - - // or the last identifier in an ObjectPattern destructuring - parent.parent.type === "Property" && parent.parent.value === parent && - parent.parent.parent.type === "ObjectPattern" && parent.parent.parent.parent.left === parent.parent.parent) - ); - }, - AssignmentPattern(parent, node) { - return parent.left === node; - }, - VariableDeclarator(parent, node) { - return parent.id === node; - }, - Property(parent, node) { - - if (parent.parent.type === "ObjectPattern") { - const isKeyAndValueSame = parent.value.name === parent.key.name; - - return ( - !isKeyAndValueSame && parent.value === node || - isKeyAndValueSame && parent.key === node && properties - ); - } - return properties && !isImportAttributeKey(node) && !parent.computed && parent.key.name === node.name; - }, - ImportSpecifier(parent, node) { - return ( - parent.local === node && - getModuleExportName(parent.imported) !== getModuleExportName(parent.local) - ); - }, - ImportDefaultSpecifier: true, - ImportNamespaceSpecifier: true, - RestElement: true, - FunctionExpression: true, - ArrowFunctionExpression: true, - ClassDeclaration: true, - FunctionDeclaration: true, - MethodDefinition: true, - PropertyDefinition: true, - CatchClause: true, - ArrayPattern: true - }; - - return { - [[ - "Identifier", - "PrivateIdentifier" - ]](node) { - const name = node.name; - const parent = node.parent; - - const nameLength = getGraphemeCount(name); - - const isShort = nameLength < minLength; - const isLong = nameLength > maxLength; - - if (!(isShort || isLong) || exceptions.has(name) || matchesExceptionPattern(name)) { - return; // Nothing to report - } - - const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; - - /* - * We used the range instead of the node because it's possible - * for the same identifier to be represented by two different - * nodes, with the most clear example being shorthand properties: - * { foo } - * In this case, "foo" is represented by one node for the name - * and one for the value. The only way to know they are the same - * is to look at the range. - */ - if (isValidExpression && !reportedNodes.has(node.range.toString()) && (isValidExpression === true || isValidExpression(parent, node))) { - reportedNodes.add(node.range.toString()); - - let messageId = isShort ? "tooShort" : "tooLong"; - - if (node.type === "PrivateIdentifier") { - messageId += "Private"; - } - - context.report({ - node, - messageId, - data: { name, min: minLength, max: maxLength } - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + min: { + type: "integer", + }, + max: { + type: "integer", + }, + exceptions: { + type: "array", + uniqueItems: true, + items: { + type: "string", + }, + }, + exceptionPatterns: { + type: "array", + uniqueItems: true, + items: { + type: "string", + }, + }, + properties: { + enum: ["always", "never"], + }, + }, + additionalProperties: false, + }, + ], + messages: { + tooShort: "Identifier name '{{name}}' is too short (< {{min}}).", + tooShortPrivate: + "Identifier name '#{{name}}' is too short (< {{min}}).", + tooLong: "Identifier name '{{name}}' is too long (> {{max}}).", + tooLongPrivate: + "Identifier name #'{{name}}' is too long (> {{max}}).", + }, + }, + + create(context) { + const [options] = context.options; + const { max: maxLength = Infinity, min: minLength } = options; + const properties = options.properties !== "never"; + const exceptions = new Set(options.exceptions); + const exceptionPatterns = options.exceptionPatterns.map( + pattern => new RegExp(pattern, "u"), + ); + const reportedNodes = new Set(); + + /** + * Checks if a string matches the provided exception patterns + * @param {string} name The string to check. + * @returns {boolean} if the string is a match + * @private + */ + function matchesExceptionPattern(name) { + return exceptionPatterns.some(pattern => pattern.test(name)); + } + + const SUPPORTED_EXPRESSIONS = { + MemberExpression: + properties && + function (parent) { + return ( + !parent.computed && + // regular property assignment + ((parent.parent.left === parent && + parent.parent.type === "AssignmentExpression") || + // or the last identifier in an ObjectPattern destructuring + (parent.parent.type === "Property" && + parent.parent.value === parent && + parent.parent.parent.type === "ObjectPattern" && + parent.parent.parent.parent.left === + parent.parent.parent)) + ); + }, + AssignmentPattern(parent, node) { + return parent.left === node; + }, + VariableDeclarator(parent, node) { + return parent.id === node; + }, + Property(parent, node) { + if (parent.parent.type === "ObjectPattern") { + const isKeyAndValueSame = + parent.value.name === parent.key.name; + + return ( + (!isKeyAndValueSame && parent.value === node) || + (isKeyAndValueSame && parent.key === node && properties) + ); + } + return ( + properties && + !isImportAttributeKey(node) && + !parent.computed && + parent.key.name === node.name + ); + }, + ImportSpecifier(parent, node) { + return ( + parent.local === node && + getModuleExportName(parent.imported) !== + getModuleExportName(parent.local) + ); + }, + ImportDefaultSpecifier: true, + ImportNamespaceSpecifier: true, + RestElement: true, + FunctionExpression: true, + ArrowFunctionExpression: true, + ClassDeclaration: true, + FunctionDeclaration: true, + MethodDefinition: true, + PropertyDefinition: true, + CatchClause: true, + ArrayPattern: true, + }; + + return { + [["Identifier", "PrivateIdentifier"]](node) { + const name = node.name; + const parent = node.parent; + + const nameLength = getGraphemeCount(name); + + const isShort = nameLength < minLength; + const isLong = nameLength > maxLength; + + if ( + !(isShort || isLong) || + exceptions.has(name) || + matchesExceptionPattern(name) + ) { + return; // Nothing to report + } + + const isValidExpression = SUPPORTED_EXPRESSIONS[parent.type]; + + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if ( + isValidExpression && + !reportedNodes.has(node.range.toString()) && + (isValidExpression === true || + isValidExpression(parent, node)) + ) { + reportedNodes.add(node.range.toString()); + + let messageId = isShort ? "tooShort" : "tooLong"; + + if (node.type === "PrivateIdentifier") { + messageId += "Private"; + } + + context.report({ + node, + messageId, + data: { name, min: minLength, max: maxLength }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index c099deb153ab..09b648e1d78e 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -17,292 +17,347 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "string" - }, - { - type: "object", - properties: { - properties: { - type: "boolean" - }, - classFields: { - type: "boolean" - }, - onlyDeclarations: { - type: "boolean" - }, - ignoreDestructuring: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - messages: { - notMatch: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", - notMatchPrivate: "Identifier '#{{name}}' does not match the pattern '{{pattern}}'." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Options - //-------------------------------------------------------------------------- - const [pattern, { - classFields: checkClassFields, - ignoreDestructuring, - onlyDeclarations, - properties: checkProperties - }] = context.options; - const regexp = new RegExp(pattern, "u"); - - const sourceCode = context.sourceCode; - let globalScope; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - // contains reported nodes to avoid reporting twice on destructuring with shorthand notation - const reportedNodes = new Set(); - const ALLOWED_PARENT_TYPES = new Set(["CallExpression", "NewExpression"]); - const DECLARATION_TYPES = new Set(["FunctionDeclaration", "VariableDeclarator"]); - const IMPORT_TYPES = new Set(["ImportSpecifier", "ImportNamespaceSpecifier", "ImportDefaultSpecifier"]); - - /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. - */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); - - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); - } - - /** - * Checks if a string matches the provided pattern - * @param {string} name The string to check. - * @returns {boolean} if the string is a match - * @private - */ - function isInvalid(name) { - return !regexp.test(name); - } - - /** - * Checks if a parent of a node is an ObjectPattern. - * @param {ASTNode} node The node to check. - * @returns {boolean} if the node is inside an ObjectPattern - * @private - */ - function isInsideObjectPattern(node) { - let { parent } = node; - - while (parent) { - if (parent.type === "ObjectPattern") { - return true; - } - - parent = parent.parent; - } - - return false; - } - - /** - * Verifies if we should report an error or not based on the effective - * parent node and the identifier name. - * @param {ASTNode} effectiveParent The effective parent node of the node to be reported - * @param {string} name The identifier name of the identifier node - * @returns {boolean} whether an error should be reported or not - */ - function shouldReport(effectiveParent, name) { - return (!onlyDeclarations || DECLARATION_TYPES.has(effectiveParent.type)) && - !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && isInvalid(name); - } - - /** - * Reports an AST node as a rule violation. - * @param {ASTNode} node The node to report. - * @returns {void} - * @private - */ - function report(node) { - - /* - * We used the range instead of the node because it's possible - * for the same identifier to be represented by two different - * nodes, with the most clear example being shorthand properties: - * { foo } - * In this case, "foo" is represented by one node for the name - * and one for the value. The only way to know they are the same - * is to look at the range. - */ - if (!reportedNodes.has(node.range.toString())) { - - const messageId = (node.type === "PrivateIdentifier") - ? "notMatchPrivate" : "notMatch"; - - context.report({ - node, - messageId, - data: { - name: node.name, - pattern - } - }); - reportedNodes.add(node.range.toString()); - } - } - - return { - - Program(node) { - globalScope = sourceCode.getScope(node); - }, - - Identifier(node) { - const name = node.name, - parent = node.parent, - effectiveParent = (parent.type === "MemberExpression") ? parent.parent : parent; - - if (isReferenceToGlobalVariable(node) || astUtils.isImportAttributeKey(node)) { - return; - } - - if (parent.type === "MemberExpression") { - - if (!checkProperties) { - return; - } - - // Always check object names - if (parent.object.type === "Identifier" && - parent.object.name === name) { - if (isInvalid(name)) { - report(node); - } - - // Report AssignmentExpressions left side's assigned variable id - } else if (effectiveParent.type === "AssignmentExpression" && - effectiveParent.left.type === "MemberExpression" && - effectiveParent.left.property.name === node.name) { - if (isInvalid(name)) { - report(node); - } - - // Report AssignmentExpressions only if they are the left side of the assignment - } else if (effectiveParent.type === "AssignmentExpression" && effectiveParent.right.type !== "MemberExpression") { - if (isInvalid(name)) { - report(node); - } - } - - // For https://github.com/eslint/eslint/issues/15123 - } else if ( - parent.type === "Property" && - parent.parent.type === "ObjectExpression" && - parent.key === node && - !parent.computed - ) { - if (checkProperties && isInvalid(name)) { - report(node); - } - - /* - * Properties have their own rules, and - * AssignmentPattern nodes can be treated like Properties: - * e.g.: const { no_camelcased = false } = bar; - */ - } else if (parent.type === "Property" || parent.type === "AssignmentPattern") { - - if (parent.parent && parent.parent.type === "ObjectPattern") { - if (!ignoreDestructuring && parent.shorthand && parent.value.left && isInvalid(name)) { - report(node); - } - - const assignmentKeyEqualsValue = parent.key.name === parent.value.name; - - // prevent checking righthand side of destructured object - if (!assignmentKeyEqualsValue && parent.key === node) { - return; - } - - const valueIsInvalid = parent.value.name && isInvalid(name); - - // ignore destructuring if the option is set, unless a new identifier is created - if (valueIsInvalid && !(assignmentKeyEqualsValue && ignoreDestructuring)) { - report(node); - } - } - - // never check properties or always ignore destructuring - if ((!checkProperties && !parent.computed) || (ignoreDestructuring && isInsideObjectPattern(node))) { - return; - } - - // don't check right hand side of AssignmentExpression to prevent duplicate warnings - if (parent.right !== node && shouldReport(effectiveParent, name)) { - report(node); - } - - // Check if it's an import specifier - } else if (IMPORT_TYPES.has(parent.type)) { - - // Report only if the local imported identifier is invalid - if (parent.local && parent.local.name === node.name && isInvalid(name)) { - report(node); - } - - } else if (parent.type === "PropertyDefinition") { - - if (checkClassFields && isInvalid(name)) { - report(node); - } - - // Report anything that is invalid that isn't a CallExpression - } else if (shouldReport(effectiveParent, name)) { - report(node); - } - }, - - "PrivateIdentifier"(node) { - - const isClassField = node.parent.type === "PropertyDefinition"; - - if (isClassField && !checkClassFields) { - return; - } - - if (isInvalid(node.name)) { - report(node); - } - } - - }; - - } + meta: { + 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", + }, + + schema: [ + { + type: "string", + }, + { + type: "object", + properties: { + properties: { + type: "boolean", + }, + classFields: { + type: "boolean", + }, + onlyDeclarations: { + type: "boolean", + }, + ignoreDestructuring: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + notMatch: + "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", + notMatchPrivate: + "Identifier '#{{name}}' does not match the pattern '{{pattern}}'.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + const [ + pattern, + { + classFields: checkClassFields, + ignoreDestructuring, + onlyDeclarations, + properties: checkProperties, + }, + ] = context.options; + const regexp = new RegExp(pattern, "u"); + + const sourceCode = context.sourceCode; + let globalScope; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + // contains reported nodes to avoid reporting twice on destructuring with shorthand notation + const reportedNodes = new Set(); + const ALLOWED_PARENT_TYPES = new Set([ + "CallExpression", + "NewExpression", + ]); + const DECLARATION_TYPES = new Set([ + "FunctionDeclaration", + "VariableDeclarator", + ]); + const IMPORT_TYPES = new Set([ + "ImportSpecifier", + "ImportNamespaceSpecifier", + "ImportDefaultSpecifier", + ]); + + /** + * Checks whether the given node represents a reference to a global variable that is not declared in the source code. + * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a reference to a global variable. + */ + function isReferenceToGlobalVariable(node) { + const variable = globalScope.set.get(node.name); + + return ( + variable && + variable.defs.length === 0 && + variable.references.some(ref => ref.identifier === node) + ); + } + + /** + * Checks if a string matches the provided pattern + * @param {string} name The string to check. + * @returns {boolean} if the string is a match + * @private + */ + function isInvalid(name) { + return !regexp.test(name); + } + + /** + * Checks if a parent of a node is an ObjectPattern. + * @param {ASTNode} node The node to check. + * @returns {boolean} if the node is inside an ObjectPattern + * @private + */ + function isInsideObjectPattern(node) { + let { parent } = node; + + while (parent) { + if (parent.type === "ObjectPattern") { + return true; + } + + parent = parent.parent; + } + + return false; + } + + /** + * Verifies if we should report an error or not based on the effective + * parent node and the identifier name. + * @param {ASTNode} effectiveParent The effective parent node of the node to be reported + * @param {string} name The identifier name of the identifier node + * @returns {boolean} whether an error should be reported or not + */ + function shouldReport(effectiveParent, name) { + return ( + (!onlyDeclarations || + DECLARATION_TYPES.has(effectiveParent.type)) && + !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && + isInvalid(name) + ); + } + + /** + * Reports an AST node as a rule violation. + * @param {ASTNode} node The node to report. + * @returns {void} + * @private + */ + function report(node) { + /* + * We used the range instead of the node because it's possible + * for the same identifier to be represented by two different + * nodes, with the most clear example being shorthand properties: + * { foo } + * In this case, "foo" is represented by one node for the name + * and one for the value. The only way to know they are the same + * is to look at the range. + */ + if (!reportedNodes.has(node.range.toString())) { + const messageId = + node.type === "PrivateIdentifier" + ? "notMatchPrivate" + : "notMatch"; + + context.report({ + node, + messageId, + data: { + name: node.name, + pattern, + }, + }); + reportedNodes.add(node.range.toString()); + } + } + + return { + Program(node) { + globalScope = sourceCode.getScope(node); + }, + + Identifier(node) { + const name = node.name, + parent = node.parent, + effectiveParent = + parent.type === "MemberExpression" + ? parent.parent + : parent; + + if ( + isReferenceToGlobalVariable(node) || + astUtils.isImportAttributeKey(node) + ) { + return; + } + + if (parent.type === "MemberExpression") { + if (!checkProperties) { + return; + } + + // Always check object names + if ( + parent.object.type === "Identifier" && + parent.object.name === name + ) { + if (isInvalid(name)) { + report(node); + } + + // Report AssignmentExpressions left side's assigned variable id + } else if ( + effectiveParent.type === "AssignmentExpression" && + effectiveParent.left.type === "MemberExpression" && + effectiveParent.left.property.name === node.name + ) { + if (isInvalid(name)) { + report(node); + } + + // Report AssignmentExpressions only if they are the left side of the assignment + } else if ( + effectiveParent.type === "AssignmentExpression" && + effectiveParent.right.type !== "MemberExpression" + ) { + if (isInvalid(name)) { + report(node); + } + } + + // For https://github.com/eslint/eslint/issues/15123 + } else if ( + parent.type === "Property" && + parent.parent.type === "ObjectExpression" && + parent.key === node && + !parent.computed + ) { + if (checkProperties && isInvalid(name)) { + report(node); + } + + /* + * Properties have their own rules, and + * AssignmentPattern nodes can be treated like Properties: + * e.g.: const { no_camelcased = false } = bar; + */ + } else if ( + parent.type === "Property" || + parent.type === "AssignmentPattern" + ) { + if ( + parent.parent && + parent.parent.type === "ObjectPattern" + ) { + if ( + !ignoreDestructuring && + parent.shorthand && + parent.value.left && + isInvalid(name) + ) { + report(node); + } + + const assignmentKeyEqualsValue = + parent.key.name === parent.value.name; + + // prevent checking righthand side of destructured object + if (!assignmentKeyEqualsValue && parent.key === node) { + return; + } + + const valueIsInvalid = + parent.value.name && isInvalid(name); + + // ignore destructuring if the option is set, unless a new identifier is created + if ( + valueIsInvalid && + !(assignmentKeyEqualsValue && ignoreDestructuring) + ) { + report(node); + } + } + + // never check properties or always ignore destructuring + if ( + (!checkProperties && !parent.computed) || + (ignoreDestructuring && isInsideObjectPattern(node)) + ) { + return; + } + + // don't check right hand side of AssignmentExpression to prevent duplicate warnings + if ( + parent.right !== node && + shouldReport(effectiveParent, name) + ) { + report(node); + } + + // Check if it's an import specifier + } else if (IMPORT_TYPES.has(parent.type)) { + // Report only if the local imported identifier is invalid + if ( + parent.local && + parent.local.name === node.name && + isInvalid(name) + ) { + report(node); + } + } else if (parent.type === "PropertyDefinition") { + if (checkClassFields && isInvalid(name)) { + report(node); + } + + // Report anything that is invalid that isn't a CallExpression + } else if (shouldReport(effectiveParent, name)) { + report(node); + } + }, + + PrivateIdentifier(node) { + const isClassField = node.parent.type === "PropertyDefinition"; + + if (isClassField && !checkClassFields) { + return; + } + + if (isInvalid(node.name)) { + report(node); + } + }, + }; + }, }; diff --git a/lib/rules/implicit-arrow-linebreak.js b/lib/rules/implicit-arrow-linebreak.js index c24a30c9e5f5..d049eaa14aa1 100644 --- a/lib/rules/implicit-arrow-linebreak.js +++ b/lib/rules/implicit-arrow-linebreak.js @@ -12,91 +12,114 @@ const { isCommentToken, isNotOpeningParenToken } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "implicit-arrow-linebreak", - url: "https://eslint.style/rules/js/implicit-arrow-linebreak" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "implicit-arrow-linebreak", + url: "https://eslint.style/rules/js/implicit-arrow-linebreak", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce the location of arrow function bodies", - recommended: false, - url: "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak" - }, + docs: { + description: "Enforce the location of arrow function bodies", + recommended: false, + url: "https://eslint.org/docs/latest/rules/implicit-arrow-linebreak", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - enum: ["beside", "below"] - } - ], - messages: { - expected: "Expected a linebreak before this expression.", - unexpected: "Expected no linebreak before this expression." - } - }, + schema: [ + { + enum: ["beside", "below"], + }, + ], + messages: { + expected: "Expected a linebreak before this expression.", + unexpected: "Expected no linebreak before this expression.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; - const option = context.options[0] || "beside"; + create(context) { + const sourceCode = context.sourceCode; + const option = context.options[0] || "beside"; - /** - * Validates the location of an arrow function body - * @param {ASTNode} node The arrow function body - * @returns {void} - */ - function validateExpression(node) { - if (node.body.type === "BlockStatement") { - return; - } + /** + * Validates the location of an arrow function body + * @param {ASTNode} node The arrow function body + * @returns {void} + */ + function validateExpression(node) { + if (node.body.type === "BlockStatement") { + return; + } - const arrowToken = sourceCode.getTokenBefore(node.body, isNotOpeningParenToken); - const firstTokenOfBody = sourceCode.getTokenAfter(arrowToken); + const arrowToken = sourceCode.getTokenBefore( + node.body, + isNotOpeningParenToken, + ); + const firstTokenOfBody = sourceCode.getTokenAfter(arrowToken); - if (arrowToken.loc.end.line === firstTokenOfBody.loc.start.line && option === "below") { - context.report({ - node: firstTokenOfBody, - messageId: "expected", - fix: fixer => fixer.insertTextBefore(firstTokenOfBody, "\n") - }); - } else if (arrowToken.loc.end.line !== firstTokenOfBody.loc.start.line && option === "beside") { - context.report({ - node: firstTokenOfBody, - messageId: "unexpected", - fix(fixer) { - if (sourceCode.getFirstTokenBetween(arrowToken, firstTokenOfBody, { includeComments: true, filter: isCommentToken })) { - return null; - } + if ( + arrowToken.loc.end.line === firstTokenOfBody.loc.start.line && + option === "below" + ) { + context.report({ + node: firstTokenOfBody, + messageId: "expected", + fix: fixer => + fixer.insertTextBefore(firstTokenOfBody, "\n"), + }); + } else if ( + arrowToken.loc.end.line !== firstTokenOfBody.loc.start.line && + option === "beside" + ) { + context.report({ + node: firstTokenOfBody, + messageId: "unexpected", + fix(fixer) { + if ( + sourceCode.getFirstTokenBetween( + arrowToken, + firstTokenOfBody, + { + includeComments: true, + filter: isCommentToken, + }, + ) + ) { + return null; + } - return fixer.replaceTextRange([arrowToken.range[1], firstTokenOfBody.range[0]], " "); - } - }); - } - } + return fixer.replaceTextRange( + [arrowToken.range[1], firstTokenOfBody.range[0]], + " ", + ); + }, + }); + } + } - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - return { - ArrowFunctionExpression: node => validateExpression(node) - }; - } + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + return { + ArrowFunctionExpression: node => validateExpression(node), + }; + }, }; diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js index 0d1149fbc4d4..147a01885a1c 100644 --- a/lib/rules/indent-legacy.js +++ b/lib/rules/indent-legacy.js @@ -22,1121 +22,1347 @@ const astUtils = require("./utils/ast-utils"); /* c8 ignore next */ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", - - docs: { - description: "Enforce consistent indentation", - recommended: false, - url: "https://eslint.org/docs/latest/rules/indent-legacy" - }, - - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "indent", - url: "https://eslint.style/rules/js/indent" - } - } - ] - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["tab"] - }, - { - type: "integer", - minimum: 0 - } - ] - }, - { - type: "object", - properties: { - SwitchCase: { - type: "integer", - minimum: 0 - }, - VariableDeclarator: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - var: { - type: "integer", - minimum: 0 - }, - let: { - type: "integer", - minimum: 0 - }, - const: { - type: "integer", - minimum: 0 - } - } - } - ] - }, - outerIIFEBody: { - type: "integer", - minimum: 0 - }, - MemberExpression: { - type: "integer", - minimum: 0 - }, - FunctionDeclaration: { - type: "object", - properties: { - parameters: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first"] - } - ] - }, - body: { - type: "integer", - minimum: 0 - } - } - }, - FunctionExpression: { - type: "object", - properties: { - parameters: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first"] - } - ] - }, - body: { - type: "integer", - minimum: 0 - } - } - }, - CallExpression: { - type: "object", - properties: { - parameters: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first"] - } - ] - } - } - }, - ArrayExpression: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first"] - } - ] - }, - ObjectExpression: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first"] - } - ] - } - }, - additionalProperties: false - } - ], - messages: { - expected: "Expected indentation of {{expected}} but found {{actual}}." - } - }, - - create(context) { - const DEFAULT_VARIABLE_INDENT = 1; - const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config - const DEFAULT_FUNCTION_BODY_INDENT = 1; - - let indentType = "space"; - let indentSize = 4; - const options = { - SwitchCase: 0, - VariableDeclarator: { - var: DEFAULT_VARIABLE_INDENT, - let: DEFAULT_VARIABLE_INDENT, - const: DEFAULT_VARIABLE_INDENT - }, - outerIIFEBody: null, - FunctionDeclaration: { - parameters: DEFAULT_PARAMETER_INDENT, - body: DEFAULT_FUNCTION_BODY_INDENT - }, - FunctionExpression: { - parameters: DEFAULT_PARAMETER_INDENT, - body: DEFAULT_FUNCTION_BODY_INDENT - }, - CallExpression: { - arguments: DEFAULT_PARAMETER_INDENT - }, - ArrayExpression: 1, - ObjectExpression: 1 - }; - - const sourceCode = context.sourceCode; - - if (context.options.length) { - if (context.options[0] === "tab") { - indentSize = 1; - indentType = "tab"; - } else /* c8 ignore start */ if (typeof context.options[0] === "number") { - indentSize = context.options[0]; - indentType = "space"; - }/* c8 ignore stop */ - - if (context.options[1]) { - const opts = context.options[1]; - - options.SwitchCase = opts.SwitchCase || 0; - const variableDeclaratorRules = opts.VariableDeclarator; - - if (typeof variableDeclaratorRules === "number") { - options.VariableDeclarator = { - var: variableDeclaratorRules, - let: variableDeclaratorRules, - const: variableDeclaratorRules - }; - } else if (typeof variableDeclaratorRules === "object") { - Object.assign(options.VariableDeclarator, variableDeclaratorRules); - } - - if (typeof opts.outerIIFEBody === "number") { - options.outerIIFEBody = opts.outerIIFEBody; - } - - if (typeof opts.MemberExpression === "number") { - options.MemberExpression = opts.MemberExpression; - } - - if (typeof opts.FunctionDeclaration === "object") { - Object.assign(options.FunctionDeclaration, opts.FunctionDeclaration); - } - - if (typeof opts.FunctionExpression === "object") { - Object.assign(options.FunctionExpression, opts.FunctionExpression); - } - - if (typeof opts.CallExpression === "object") { - Object.assign(options.CallExpression, opts.CallExpression); - } - - if (typeof opts.ArrayExpression === "number" || typeof opts.ArrayExpression === "string") { - options.ArrayExpression = opts.ArrayExpression; - } - - if (typeof opts.ObjectExpression === "number" || typeof opts.ObjectExpression === "string") { - options.ObjectExpression = opts.ObjectExpression; - } - } - } - - const caseIndentStore = {}; - - /** - * Creates an error message for a line, given the expected/actual indentation. - * @param {int} expectedAmount The expected amount of indentation characters for this line - * @param {int} actualSpaces The actual number of indentation spaces that were found on this line - * @param {int} actualTabs The actual number of indentation tabs that were found on this line - * @returns {string} An error message for this line - */ - function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) { - const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" - const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" - const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" - let foundStatement; - - if (actualSpaces > 0 && actualTabs > 0) { - foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs" - } else if (actualSpaces > 0) { - - /* - * Abbreviate the message if the expected indentation is also spaces. - * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' - */ - foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`; - } else if (actualTabs > 0) { - foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`; - } else { - foundStatement = "0"; - } - return { - expected: expectedStatement, - actual: foundStatement - }; - } - - /** - * Reports a given indent violation - * @param {ASTNode} node Node violating the indent rule - * @param {int} needed Expected indentation character count - * @param {int} gottenSpaces Indentation space count in the actual node/code - * @param {int} gottenTabs Indentation tab count in the actual node/code - * @param {Object} [loc] Error line and column location - * @param {boolean} isLastNodeCheck Is the error for last node check - * @returns {void} - */ - function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) { - if (gottenSpaces && gottenTabs) { - - // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs. - return; - } - - const desiredIndent = (indentType === "space" ? " " : "\t").repeat(needed); - - const textRange = isLastNodeCheck - ? [node.range[1] - node.loc.end.column, node.range[1] - node.loc.end.column + gottenSpaces + gottenTabs] - : [node.range[0] - node.loc.start.column, node.range[0] - node.loc.start.column + gottenSpaces + gottenTabs]; - - context.report({ - node, - loc, - messageId: "expected", - data: createErrorMessageData(needed, gottenSpaces, gottenTabs), - fix: fixer => fixer.replaceTextRange(textRange, desiredIndent) - }); - } - - /** - * Get the actual indent of node - * @param {ASTNode|Token} node Node to examine - * @param {boolean} [byLastLine=false] get indent of node's last line - * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also - * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and - * `badChar` is the amount of the other indentation character. - */ - function getNodeIndent(node, byLastLine) { - const token = byLastLine ? sourceCode.getLastToken(node) : sourceCode.getFirstToken(node); - const srcCharsBeforeNode = sourceCode.getText(token, token.loc.start.column).split(""); - const indentChars = srcCharsBeforeNode.slice(0, srcCharsBeforeNode.findIndex(char => char !== " " && char !== "\t")); - const spaces = indentChars.filter(char => char === " ").length; - const tabs = indentChars.filter(char => char === "\t").length; - - return { - space: spaces, - tab: tabs, - goodChar: indentType === "space" ? spaces : tabs, - badChar: indentType === "space" ? tabs : spaces - }; - } - - /** - * Checks node is the first in its own start line. By default it looks by start line. - * @param {ASTNode} node The node to check - * @param {boolean} [byEndLocation=false] Lookup based on start position or end - * @returns {boolean} true if its the first in the its start line - */ - function isNodeFirstInLine(node, byEndLocation) { - const firstToken = byEndLocation === true ? sourceCode.getLastToken(node, 1) : sourceCode.getTokenBefore(node), - startLine = byEndLocation === true ? node.loc.end.line : node.loc.start.line, - endLine = firstToken ? firstToken.loc.end.line : -1; - - return startLine !== endLine; - } - - /** - * Check indent for node - * @param {ASTNode} node Node to check - * @param {int} neededIndent needed indent - * @returns {void} - */ - function checkNodeIndent(node, neededIndent) { - const actualIndent = getNodeIndent(node, false); - - if ( - node.type !== "ArrayExpression" && - node.type !== "ObjectExpression" && - (actualIndent.goodChar !== neededIndent || actualIndent.badChar !== 0) && - isNodeFirstInLine(node) - ) { - report(node, neededIndent, actualIndent.space, actualIndent.tab); - } - - if (node.type === "IfStatement" && node.alternate) { - const elseToken = sourceCode.getTokenBefore(node.alternate); - - checkNodeIndent(elseToken, neededIndent); - - if (!isNodeFirstInLine(node.alternate)) { - checkNodeIndent(node.alternate, neededIndent); - } - } - - if (node.type === "TryStatement" && node.handler) { - const catchToken = sourceCode.getFirstToken(node.handler); - - checkNodeIndent(catchToken, neededIndent); - } - - if (node.type === "TryStatement" && node.finalizer) { - const finallyToken = sourceCode.getTokenBefore(node.finalizer); - - checkNodeIndent(finallyToken, neededIndent); - } - - if (node.type === "DoWhileStatement") { - const whileToken = sourceCode.getTokenAfter(node.body); - - checkNodeIndent(whileToken, neededIndent); - } - } - - /** - * Check indent for nodes list - * @param {ASTNode[]} nodes list of node objects - * @param {int} indent needed indent - * @returns {void} - */ - function checkNodesIndent(nodes, indent) { - nodes.forEach(node => checkNodeIndent(node, indent)); - } - - /** - * Check last node line indent this detects, that block closed correctly - * @param {ASTNode} node Node to examine - * @param {int} lastLineIndent needed indent - * @returns {void} - */ - function checkLastNodeLineIndent(node, lastLineIndent) { - const lastToken = sourceCode.getLastToken(node); - const endIndent = getNodeIndent(lastToken, true); - - if ((endIndent.goodChar !== lastLineIndent || endIndent.badChar !== 0) && isNodeFirstInLine(node, true)) { - report( - node, - lastLineIndent, - endIndent.space, - endIndent.tab, - { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, - true - ); - } - } - - /** - * Check last node line indent this detects, that block closed correctly - * This function for more complicated return statement case, where closing parenthesis may be followed by ';' - * @param {ASTNode} node Node to examine - * @param {int} firstLineIndent first line needed indent - * @returns {void} - */ - function checkLastReturnStatementLineIndent(node, firstLineIndent) { - - /* - * in case if return statement ends with ');' we have traverse back to ')' - * otherwise we'll measure indent for ';' and replace ')' - */ - const lastToken = sourceCode.getLastToken(node, astUtils.isClosingParenToken); - const textBeforeClosingParenthesis = sourceCode.getText(lastToken, lastToken.loc.start.column).slice(0, -1); - - if (textBeforeClosingParenthesis.trim()) { - - // There are tokens before the closing paren, don't report this case - return; - } - - const endIndent = getNodeIndent(lastToken, true); - - if (endIndent.goodChar !== firstLineIndent) { - report( - node, - firstLineIndent, - endIndent.space, - endIndent.tab, - { line: lastToken.loc.start.line, column: lastToken.loc.start.column }, - true - ); - } - } - - /** - * Check first node line indent is correct - * @param {ASTNode} node Node to examine - * @param {int} firstLineIndent needed indent - * @returns {void} - */ - function checkFirstNodeLineIndent(node, firstLineIndent) { - const startIndent = getNodeIndent(node, false); - - if ((startIndent.goodChar !== firstLineIndent || startIndent.badChar !== 0) && isNodeFirstInLine(node)) { - report( - node, - firstLineIndent, - startIndent.space, - startIndent.tab, - { line: node.loc.start.line, column: node.loc.start.column } - ); - } - } - - /** - * Returns a parent node of given node based on a specified type - * if not present then return null - * @param {ASTNode} node node to examine - * @param {string} type type that is being looked for - * @param {string} stopAtList end points for the evaluating code - * @returns {ASTNode|void} if found then node otherwise null - */ - function getParentNodeByType(node, type, stopAtList) { - let parent = node.parent; - const stopAtSet = new Set(stopAtList || ["Program"]); - - while (parent.type !== type && !stopAtSet.has(parent.type) && parent.type !== "Program") { - parent = parent.parent; - } - - return parent.type === type ? parent : null; - } - - /** - * Returns the VariableDeclarator based on the current node - * if not present then return null - * @param {ASTNode} node node to examine - * @returns {ASTNode|void} if found then node otherwise null - */ - function getVariableDeclaratorNode(node) { - return getParentNodeByType(node, "VariableDeclarator"); - } - - /** - * Check to see if the node is part of the multi-line variable declaration. - * Also if its on the same line as the varNode - * @param {ASTNode} node node to check - * @param {ASTNode} varNode variable declaration node to check against - * @returns {boolean} True if all the above condition satisfy - */ - function isNodeInVarOnTop(node, varNode) { - return varNode && - varNode.parent.loc.start.line === node.loc.start.line && - varNode.parent.declarations.length > 1; - } - - /** - * Check to see if the argument before the callee node is multi-line and - * there should only be 1 argument before the callee node - * @param {ASTNode} node node to check - * @returns {boolean} True if arguments are multi-line - */ - function isArgBeforeCalleeNodeMultiline(node) { - const parent = node.parent; - - if (parent.arguments.length >= 2 && parent.arguments[1] === node) { - return parent.arguments[0].loc.end.line > parent.arguments[0].loc.start.line; - } - - return false; - } - - /** - * Check to see if the node is a file level IIFE - * @param {ASTNode} node The function node to check. - * @returns {boolean} True if the node is the outer IIFE - */ - function isOuterIIFE(node) { - const parent = node.parent; - let stmt = parent.parent; - - /* - * Verify that the node is an IIEF - */ - if ( - parent.type !== "CallExpression" || - parent.callee !== node) { - - return false; - } - - /* - * Navigate legal ancestors to determine whether this IIEF is outer - */ - while ( - stmt.type === "UnaryExpression" && ( - stmt.operator === "!" || - stmt.operator === "~" || - stmt.operator === "+" || - stmt.operator === "-") || - stmt.type === "AssignmentExpression" || - stmt.type === "LogicalExpression" || - stmt.type === "SequenceExpression" || - stmt.type === "VariableDeclarator") { - - stmt = stmt.parent; - } - - return (( - stmt.type === "ExpressionStatement" || - stmt.type === "VariableDeclaration") && - stmt.parent && stmt.parent.type === "Program" - ); - } - - /** - * Check indent for function block content - * @param {ASTNode} node A BlockStatement node that is inside of a function. - * @returns {void} - */ - function checkIndentInFunctionBlock(node) { - - /* - * Search first caller in chain. - * Ex.: - * - * Models <- Identifier - * .User - * .find() - * .exec(function() { - * // function body - * }); - * - * Looks for 'Models' - */ - const calleeNode = node.parent; // FunctionExpression - let indent; - - if (calleeNode.parent && - (calleeNode.parent.type === "Property" || - calleeNode.parent.type === "ArrayExpression")) { - - // If function is part of array or object, comma can be put at left - indent = getNodeIndent(calleeNode, false).goodChar; - } else { - - // If function is standalone, simple calculate indent - indent = getNodeIndent(calleeNode).goodChar; - } - - if (calleeNode.parent.type === "CallExpression") { - const calleeParent = calleeNode.parent; - - if (calleeNode.type !== "FunctionExpression" && calleeNode.type !== "ArrowFunctionExpression") { - if (calleeParent && calleeParent.loc.start.line < node.loc.start.line) { - indent = getNodeIndent(calleeParent).goodChar; - } - } else { - if (isArgBeforeCalleeNodeMultiline(calleeNode) && - calleeParent.callee.loc.start.line === calleeParent.callee.loc.end.line && - !isNodeFirstInLine(calleeNode)) { - indent = getNodeIndent(calleeParent).goodChar; - } - } - } - - /* - * function body indent should be indent + indent size, unless this - * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled. - */ - let functionOffset = indentSize; - - if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) { - functionOffset = options.outerIIFEBody * indentSize; - } else if (calleeNode.type === "FunctionExpression") { - functionOffset = options.FunctionExpression.body * indentSize; - } else if (calleeNode.type === "FunctionDeclaration") { - functionOffset = options.FunctionDeclaration.body * indentSize; - } - indent += functionOffset; - - // check if the node is inside a variable - const parentVarNode = getVariableDeclaratorNode(node); - - if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { - indent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; - } - - if (node.body.length > 0) { - checkNodesIndent(node.body, indent); - } - - checkLastNodeLineIndent(node, indent - functionOffset); - } - - - /** - * Checks if the given node starts and ends on the same line - * @param {ASTNode} node The node to check - * @returns {boolean} Whether or not the block starts and ends on the same line. - */ - function isSingleLineNode(node) { - const lastToken = sourceCode.getLastToken(node), - startLine = node.loc.start.line, - endLine = lastToken.loc.end.line; - - return startLine === endLine; - } - - /** - * Check indent for array block content or object block content - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkIndentInArrayOrObjectBlock(node) { - - // Skip inline - if (isSingleLineNode(node)) { - return; - } - - let elements = (node.type === "ArrayExpression") ? node.elements : node.properties; - - // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null - elements = elements.filter(elem => elem !== null); - - let nodeIndent; - let elementsIndent; - const parentVarNode = getVariableDeclaratorNode(node); - - // TODO - come up with a better strategy in future - if (isNodeFirstInLine(node)) { - const parent = node.parent; - - nodeIndent = getNodeIndent(parent).goodChar; - if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) { - if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) { - if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) { - nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]); - } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") { - const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements; - - if (parentElements[0] && - parentElements[0].loc.start.line === parent.loc.start.line && - parentElements[0].loc.end.line !== parent.loc.start.line) { - - /* - * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest. - * e.g. [{ - * foo: 1 - * }, - * { - * bar: 1 - * }] - * the second object is not indented. - */ - } else if (typeof options[parent.type] === "number") { - nodeIndent += options[parent.type] * indentSize; - } else { - nodeIndent = parentElements[0].loc.start.column; - } - } else if (parent.type === "CallExpression" || parent.type === "NewExpression") { - if (typeof options.CallExpression.arguments === "number") { - nodeIndent += options.CallExpression.arguments * indentSize; - } else if (options.CallExpression.arguments === "first") { - if (parent.arguments.includes(node)) { - nodeIndent = parent.arguments[0].loc.start.column; - } - } else { - nodeIndent += indentSize; - } - } else if (parent.type === "LogicalExpression" || parent.type === "ArrowFunctionExpression") { - nodeIndent += indentSize; - } - } - } - - checkFirstNodeLineIndent(node, nodeIndent); - } else { - nodeIndent = getNodeIndent(node).goodChar; - } - - if (options[node.type] === "first") { - elementsIndent = elements.length ? elements[0].loc.start.column : 0; // If there are no elements, elementsIndent doesn't matter. - } else { - elementsIndent = nodeIndent + indentSize * options[node.type]; - } - - /* - * Check if the node is a multiple variable declaration; if so, then - * make sure indentation takes that into account. - */ - if (isNodeInVarOnTop(node, parentVarNode)) { - elementsIndent += indentSize * options.VariableDeclarator[parentVarNode.parent.kind]; - } - - checkNodesIndent(elements, elementsIndent); - - if (elements.length > 0) { - - // Skip last block line check if last item in same line - if (elements.at(-1).loc.end.line === node.loc.end.line) { - return; - } - } - - checkLastNodeLineIndent(node, nodeIndent + - (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0)); - } - - /** - * Check if the node or node body is a BlockStatement or not - * @param {ASTNode} node node to test - * @returns {boolean} True if it or its body is a block statement - */ - function isNodeBodyBlock(node) { - return node.type === "BlockStatement" || node.type === "ClassBody" || (node.body && node.body.type === "BlockStatement") || - (node.consequent && node.consequent.type === "BlockStatement"); - } - - /** - * Check indentation for blocks - * @param {ASTNode} node node to check - * @returns {void} - */ - function blockIndentationCheck(node) { - - // Skip inline blocks - if (isSingleLineNode(node)) { - return; - } - - if (node.parent && ( - node.parent.type === "FunctionExpression" || - node.parent.type === "FunctionDeclaration" || - node.parent.type === "ArrowFunctionExpression") - ) { - checkIndentInFunctionBlock(node); - return; - } - - let indent; - let nodesToCheck; - - /* - * For this statements we should check indent from statement beginning, - * not from the beginning of the block. - */ - const statementsWithProperties = [ - "IfStatement", "WhileStatement", "ForStatement", "ForInStatement", "ForOfStatement", "DoWhileStatement", "ClassDeclaration", "TryStatement" - ]; - - if (node.parent && statementsWithProperties.includes(node.parent.type) && isNodeBodyBlock(node)) { - indent = getNodeIndent(node.parent).goodChar; - } else if (node.parent && node.parent.type === "CatchClause") { - indent = getNodeIndent(node.parent.parent).goodChar; - } else { - indent = getNodeIndent(node).goodChar; - } - - if (node.type === "IfStatement" && node.consequent.type !== "BlockStatement") { - nodesToCheck = [node.consequent]; - } else if (Array.isArray(node.body)) { - nodesToCheck = node.body; - } else { - nodesToCheck = [node.body]; - } - - if (nodesToCheck.length > 0) { - checkNodesIndent(nodesToCheck, indent + indentSize); - } - - if (node.type === "BlockStatement") { - checkLastNodeLineIndent(node, indent); - } - } - - /** - * Filter out the elements which are on the same line of each other or the node. - * basically have only 1 elements from each line except the variable declaration line. - * @param {ASTNode} node Variable declaration node - * @returns {ASTNode[]} Filtered elements - */ - function filterOutSameLineVars(node) { - return node.declarations.reduce((finalCollection, elem) => { - const lastElem = finalCollection.at(-1); - - if ((elem.loc.start.line !== node.loc.start.line && !lastElem) || - (lastElem && lastElem.loc.start.line !== elem.loc.start.line)) { - finalCollection.push(elem); - } - - return finalCollection; - }, []); - } - - /** - * Check indentation for variable declarations - * @param {ASTNode} node node to examine - * @returns {void} - */ - function checkIndentInVariableDeclarations(node) { - const elements = filterOutSameLineVars(node); - const nodeIndent = getNodeIndent(node).goodChar; - const lastElement = elements.at(-1); - - const elementsIndent = nodeIndent + indentSize * options.VariableDeclarator[node.kind]; - - checkNodesIndent(elements, elementsIndent); - - // Only check the last line if there is any token after the last item - if (sourceCode.getLastToken(node).loc.end.line <= lastElement.loc.end.line) { - return; - } - - const tokenBeforeLastElement = sourceCode.getTokenBefore(lastElement); - - if (tokenBeforeLastElement.value === ",") { - - // Special case for comma-first syntax where the semicolon is indented - checkLastNodeLineIndent(node, getNodeIndent(tokenBeforeLastElement).goodChar); - } else { - checkLastNodeLineIndent(node, elementsIndent - indentSize); - } - } - - /** - * Check and decide whether to check for indentation for blockless nodes - * Scenarios are for or while statements without braces around them - * @param {ASTNode} node node to examine - * @returns {void} - */ - function blockLessNodes(node) { - if (node.body.type !== "BlockStatement") { - blockIndentationCheck(node); - } - } - - /** - * Returns the expected indentation for the case statement - * @param {ASTNode} node node to examine - * @param {int} [providedSwitchIndent] indent for switch statement - * @returns {int} indent size - */ - function expectedCaseIndent(node, providedSwitchIndent) { - const switchNode = (node.type === "SwitchStatement") ? node : node.parent; - const switchIndent = typeof providedSwitchIndent === "undefined" - ? getNodeIndent(switchNode).goodChar - : providedSwitchIndent; - let caseIndent; - - if (caseIndentStore[switchNode.loc.start.line]) { - return caseIndentStore[switchNode.loc.start.line]; - } - - if (switchNode.cases.length > 0 && options.SwitchCase === 0) { - caseIndent = switchIndent; - } else { - caseIndent = switchIndent + (indentSize * options.SwitchCase); - } - - caseIndentStore[switchNode.loc.start.line] = caseIndent; - return caseIndent; - - } - - /** - * Checks whether a return statement is wrapped in () - * @param {ASTNode} node node to examine - * @returns {boolean} the result - */ - function isWrappedInParenthesis(node) { - const regex = /^return\s*?\(\s*?\);*?/u; - - const statementWithoutArgument = sourceCode.getText(node).replace( - sourceCode.getText(node.argument), "" - ); - - return regex.test(statementWithoutArgument); - } - - return { - Program(node) { - if (node.body.length > 0) { - - // Root nodes should have no indent - checkNodesIndent(node.body, getNodeIndent(node).goodChar); - } - }, - - ClassBody: blockIndentationCheck, - - BlockStatement: blockIndentationCheck, - - WhileStatement: blockLessNodes, - - ForStatement: blockLessNodes, - - ForInStatement: blockLessNodes, - - ForOfStatement: blockLessNodes, - - DoWhileStatement: blockLessNodes, - - IfStatement(node) { - if (node.consequent.type !== "BlockStatement" && node.consequent.loc.start.line > node.loc.start.line) { - blockIndentationCheck(node); - } - }, - - VariableDeclaration(node) { - if (node.declarations.at(-1).loc.start.line > node.declarations[0].loc.start.line) { - checkIndentInVariableDeclarations(node); - } - }, - - ObjectExpression(node) { - checkIndentInArrayOrObjectBlock(node); - }, - - ArrayExpression(node) { - checkIndentInArrayOrObjectBlock(node); - }, - - MemberExpression(node) { - - if (typeof options.MemberExpression === "undefined") { - return; - } - - if (isSingleLineNode(node)) { - return; - } - - /* - * The typical layout of variable declarations and assignments - * alter the expectation of correct indentation. Skip them. - * TODO: Add appropriate configuration options for variable - * declarations and assignments. - */ - if (getParentNodeByType(node, "VariableDeclarator", ["FunctionExpression", "ArrowFunctionExpression"])) { - return; - } - - if (getParentNodeByType(node, "AssignmentExpression", ["FunctionExpression"])) { - return; - } - - const propertyIndent = getNodeIndent(node).goodChar + indentSize * options.MemberExpression; - - const checkNodes = [node.property]; - - const dot = sourceCode.getTokenBefore(node.property); - - if (dot.type === "Punctuator" && dot.value === ".") { - checkNodes.push(dot); - } - - checkNodesIndent(checkNodes, propertyIndent); - }, - - SwitchStatement(node) { - - // Switch is not a 'BlockStatement' - const switchIndent = getNodeIndent(node).goodChar; - const caseIndent = expectedCaseIndent(node, switchIndent); - - checkNodesIndent(node.cases, caseIndent); - - - checkLastNodeLineIndent(node, switchIndent); - }, - - SwitchCase(node) { - - // Skip inline cases - if (isSingleLineNode(node)) { - return; - } - const caseIndent = expectedCaseIndent(node); - - checkNodesIndent(node.consequent, caseIndent + indentSize); - }, - - FunctionDeclaration(node) { - if (isSingleLineNode(node)) { - return; - } - if (options.FunctionDeclaration.parameters === "first" && node.params.length) { - checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); - } else if (options.FunctionDeclaration.parameters !== null) { - checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionDeclaration.parameters); - } - }, - - FunctionExpression(node) { - if (isSingleLineNode(node)) { - return; - } - if (options.FunctionExpression.parameters === "first" && node.params.length) { - checkNodesIndent(node.params.slice(1), node.params[0].loc.start.column); - } else if (options.FunctionExpression.parameters !== null) { - checkNodesIndent(node.params, getNodeIndent(node).goodChar + indentSize * options.FunctionExpression.parameters); - } - }, - - ReturnStatement(node) { - if (isSingleLineNode(node)) { - return; - } - - const firstLineIndent = getNodeIndent(node).goodChar; - - // in case if return statement is wrapped in parenthesis - if (isWrappedInParenthesis(node)) { - checkLastReturnStatementLineIndent(node, firstLineIndent); - } else { - checkNodeIndent(node, firstLineIndent); - } - }, - - CallExpression(node) { - if (isSingleLineNode(node)) { - return; - } - if (options.CallExpression.arguments === "first" && node.arguments.length) { - checkNodesIndent(node.arguments.slice(1), node.arguments[0].loc.start.column); - } else if (options.CallExpression.arguments !== null) { - checkNodesIndent(node.arguments, getNodeIndent(node).goodChar + indentSize * options.CallExpression.arguments); - } - } - - }; - - } + meta: { + type: "layout", + + docs: { + description: "Enforce consistent indentation", + recommended: false, + url: "https://eslint.org/docs/latest/rules/indent-legacy", + }, + + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "indent", + url: "https://eslint.style/rules/js/indent", + }, + }, + ], + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["tab"], + }, + { + type: "integer", + minimum: 0, + }, + ], + }, + { + type: "object", + properties: { + SwitchCase: { + type: "integer", + minimum: 0, + }, + VariableDeclarator: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + var: { + type: "integer", + minimum: 0, + }, + let: { + type: "integer", + minimum: 0, + }, + const: { + type: "integer", + minimum: 0, + }, + }, + }, + ], + }, + outerIIFEBody: { + type: "integer", + minimum: 0, + }, + MemberExpression: { + type: "integer", + minimum: 0, + }, + FunctionDeclaration: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first"], + }, + ], + }, + body: { + type: "integer", + minimum: 0, + }, + }, + }, + FunctionExpression: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first"], + }, + ], + }, + body: { + type: "integer", + minimum: 0, + }, + }, + }, + CallExpression: { + type: "object", + properties: { + parameters: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first"], + }, + ], + }, + }, + }, + ArrayExpression: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first"], + }, + ], + }, + ObjectExpression: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first"], + }, + ], + }, + }, + additionalProperties: false, + }, + ], + messages: { + expected: + "Expected indentation of {{expected}} but found {{actual}}.", + }, + }, + + create(context) { + const DEFAULT_VARIABLE_INDENT = 1; + const DEFAULT_PARAMETER_INDENT = null; // For backwards compatibility, don't check parameter indentation unless specified in the config + const DEFAULT_FUNCTION_BODY_INDENT = 1; + + let indentType = "space"; + let indentSize = 4; + const options = { + SwitchCase: 0, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT, + }, + outerIIFEBody: null, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT, + }, + ArrayExpression: 1, + ObjectExpression: 1, + }; + + const sourceCode = context.sourceCode; + + if (context.options.length) { + if (context.options[0] === "tab") { + indentSize = 1; + indentType = "tab"; + } /* c8 ignore start */ else if ( + typeof context.options[0] === "number" + ) { + indentSize = context.options[0]; + indentType = "space"; + } /* c8 ignore stop */ + + if (context.options[1]) { + const opts = context.options[1]; + + options.SwitchCase = opts.SwitchCase || 0; + const variableDeclaratorRules = opts.VariableDeclarator; + + if (typeof variableDeclaratorRules === "number") { + options.VariableDeclarator = { + var: variableDeclaratorRules, + let: variableDeclaratorRules, + const: variableDeclaratorRules, + }; + } else if (typeof variableDeclaratorRules === "object") { + Object.assign( + options.VariableDeclarator, + variableDeclaratorRules, + ); + } + + if (typeof opts.outerIIFEBody === "number") { + options.outerIIFEBody = opts.outerIIFEBody; + } + + if (typeof opts.MemberExpression === "number") { + options.MemberExpression = opts.MemberExpression; + } + + if (typeof opts.FunctionDeclaration === "object") { + Object.assign( + options.FunctionDeclaration, + opts.FunctionDeclaration, + ); + } + + if (typeof opts.FunctionExpression === "object") { + Object.assign( + options.FunctionExpression, + opts.FunctionExpression, + ); + } + + if (typeof opts.CallExpression === "object") { + Object.assign(options.CallExpression, opts.CallExpression); + } + + if ( + typeof opts.ArrayExpression === "number" || + typeof opts.ArrayExpression === "string" + ) { + options.ArrayExpression = opts.ArrayExpression; + } + + if ( + typeof opts.ObjectExpression === "number" || + typeof opts.ObjectExpression === "string" + ) { + options.ObjectExpression = opts.ObjectExpression; + } + } + } + + const caseIndentStore = {}; + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param {int} expectedAmount The expected amount of indentation characters for this line + * @param {int} actualSpaces The actual number of indentation spaces that were found on this line + * @param {int} actualTabs The actual number of indentation tabs that were found on this line + * @returns {string} An error message for this line + */ + function createErrorMessageData( + expectedAmount, + actualSpaces, + actualTabs, + ) { + const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0 && actualTabs > 0) { + foundStatement = `${actualSpaces} ${foundSpacesWord} and ${actualTabs} ${foundTabsWord}`; // e.g. "1 space and 2 tabs" + } else if (actualSpaces > 0) { + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = + indentType === "space" + ? actualSpaces + : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = + indentType === "tab" + ? actualTabs + : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = "0"; + } + return { + expected: expectedStatement, + actual: foundStatement, + }; + } + + /** + * Reports a given indent violation + * @param {ASTNode} node Node violating the indent rule + * @param {int} needed Expected indentation character count + * @param {int} gottenSpaces Indentation space count in the actual node/code + * @param {int} gottenTabs Indentation tab count in the actual node/code + * @param {Object} [loc] Error line and column location + * @param {boolean} isLastNodeCheck Is the error for last node check + * @returns {void} + */ + function report( + node, + needed, + gottenSpaces, + gottenTabs, + loc, + isLastNodeCheck, + ) { + if (gottenSpaces && gottenTabs) { + // To avoid conflicts with `no-mixed-spaces-and-tabs`, don't report lines that have both spaces and tabs. + return; + } + + const desiredIndent = (indentType === "space" ? " " : "\t").repeat( + needed, + ); + + const textRange = isLastNodeCheck + ? [ + node.range[1] - node.loc.end.column, + node.range[1] - + node.loc.end.column + + gottenSpaces + + gottenTabs, + ] + : [ + node.range[0] - node.loc.start.column, + node.range[0] - + node.loc.start.column + + gottenSpaces + + gottenTabs, + ]; + + context.report({ + node, + loc, + messageId: "expected", + data: createErrorMessageData(needed, gottenSpaces, gottenTabs), + fix: fixer => fixer.replaceTextRange(textRange, desiredIndent), + }); + } + + /** + * Get the actual indent of node + * @param {ASTNode|Token} node Node to examine + * @param {boolean} [byLastLine=false] get indent of node's last line + * @returns {Object} The node's indent. Contains keys `space` and `tab`, representing the indent of each character. Also + * contains keys `goodChar` and `badChar`, where `goodChar` is the amount of the user's desired indentation character, and + * `badChar` is the amount of the other indentation character. + */ + function getNodeIndent(node, byLastLine) { + const token = byLastLine + ? sourceCode.getLastToken(node) + : sourceCode.getFirstToken(node); + const srcCharsBeforeNode = sourceCode + .getText(token, token.loc.start.column) + .split(""); + const indentChars = srcCharsBeforeNode.slice( + 0, + srcCharsBeforeNode.findIndex( + char => char !== " " && char !== "\t", + ), + ); + const spaces = indentChars.filter(char => char === " ").length; + const tabs = indentChars.filter(char => char === "\t").length; + + return { + space: spaces, + tab: tabs, + goodChar: indentType === "space" ? spaces : tabs, + badChar: indentType === "space" ? tabs : spaces, + }; + } + + /** + * Checks node is the first in its own start line. By default it looks by start line. + * @param {ASTNode} node The node to check + * @param {boolean} [byEndLocation=false] Lookup based on start position or end + * @returns {boolean} true if its the first in the its start line + */ + function isNodeFirstInLine(node, byEndLocation) { + const firstToken = + byEndLocation === true + ? sourceCode.getLastToken(node, 1) + : sourceCode.getTokenBefore(node), + startLine = + byEndLocation === true + ? node.loc.end.line + : node.loc.start.line, + endLine = firstToken ? firstToken.loc.end.line : -1; + + return startLine !== endLine; + } + + /** + * Check indent for node + * @param {ASTNode} node Node to check + * @param {int} neededIndent needed indent + * @returns {void} + */ + function checkNodeIndent(node, neededIndent) { + const actualIndent = getNodeIndent(node, false); + + if ( + node.type !== "ArrayExpression" && + node.type !== "ObjectExpression" && + (actualIndent.goodChar !== neededIndent || + actualIndent.badChar !== 0) && + isNodeFirstInLine(node) + ) { + report( + node, + neededIndent, + actualIndent.space, + actualIndent.tab, + ); + } + + if (node.type === "IfStatement" && node.alternate) { + const elseToken = sourceCode.getTokenBefore(node.alternate); + + checkNodeIndent(elseToken, neededIndent); + + if (!isNodeFirstInLine(node.alternate)) { + checkNodeIndent(node.alternate, neededIndent); + } + } + + if (node.type === "TryStatement" && node.handler) { + const catchToken = sourceCode.getFirstToken(node.handler); + + checkNodeIndent(catchToken, neededIndent); + } + + if (node.type === "TryStatement" && node.finalizer) { + const finallyToken = sourceCode.getTokenBefore(node.finalizer); + + checkNodeIndent(finallyToken, neededIndent); + } + + if (node.type === "DoWhileStatement") { + const whileToken = sourceCode.getTokenAfter(node.body); + + checkNodeIndent(whileToken, neededIndent); + } + } + + /** + * Check indent for nodes list + * @param {ASTNode[]} nodes list of node objects + * @param {int} indent needed indent + * @returns {void} + */ + function checkNodesIndent(nodes, indent) { + nodes.forEach(node => checkNodeIndent(node, indent)); + } + + /** + * Check last node line indent this detects, that block closed correctly + * @param {ASTNode} node Node to examine + * @param {int} lastLineIndent needed indent + * @returns {void} + */ + function checkLastNodeLineIndent(node, lastLineIndent) { + const lastToken = sourceCode.getLastToken(node); + const endIndent = getNodeIndent(lastToken, true); + + if ( + (endIndent.goodChar !== lastLineIndent || + endIndent.badChar !== 0) && + isNodeFirstInLine(node, true) + ) { + report( + node, + lastLineIndent, + endIndent.space, + endIndent.tab, + { + line: lastToken.loc.start.line, + column: lastToken.loc.start.column, + }, + true, + ); + } + } + + /** + * Check last node line indent this detects, that block closed correctly + * This function for more complicated return statement case, where closing parenthesis may be followed by ';' + * @param {ASTNode} node Node to examine + * @param {int} firstLineIndent first line needed indent + * @returns {void} + */ + function checkLastReturnStatementLineIndent(node, firstLineIndent) { + /* + * in case if return statement ends with ');' we have traverse back to ')' + * otherwise we'll measure indent for ';' and replace ')' + */ + const lastToken = sourceCode.getLastToken( + node, + astUtils.isClosingParenToken, + ); + const textBeforeClosingParenthesis = sourceCode + .getText(lastToken, lastToken.loc.start.column) + .slice(0, -1); + + if (textBeforeClosingParenthesis.trim()) { + // There are tokens before the closing paren, don't report this case + return; + } + + const endIndent = getNodeIndent(lastToken, true); + + if (endIndent.goodChar !== firstLineIndent) { + report( + node, + firstLineIndent, + endIndent.space, + endIndent.tab, + { + line: lastToken.loc.start.line, + column: lastToken.loc.start.column, + }, + true, + ); + } + } + + /** + * Check first node line indent is correct + * @param {ASTNode} node Node to examine + * @param {int} firstLineIndent needed indent + * @returns {void} + */ + function checkFirstNodeLineIndent(node, firstLineIndent) { + const startIndent = getNodeIndent(node, false); + + if ( + (startIndent.goodChar !== firstLineIndent || + startIndent.badChar !== 0) && + isNodeFirstInLine(node) + ) { + report( + node, + firstLineIndent, + startIndent.space, + startIndent.tab, + { + line: node.loc.start.line, + column: node.loc.start.column, + }, + ); + } + } + + /** + * Returns a parent node of given node based on a specified type + * if not present then return null + * @param {ASTNode} node node to examine + * @param {string} type type that is being looked for + * @param {string} stopAtList end points for the evaluating code + * @returns {ASTNode|void} if found then node otherwise null + */ + function getParentNodeByType(node, type, stopAtList) { + let parent = node.parent; + const stopAtSet = new Set(stopAtList || ["Program"]); + + while ( + parent.type !== type && + !stopAtSet.has(parent.type) && + parent.type !== "Program" + ) { + parent = parent.parent; + } + + return parent.type === type ? parent : null; + } + + /** + * Returns the VariableDeclarator based on the current node + * if not present then return null + * @param {ASTNode} node node to examine + * @returns {ASTNode|void} if found then node otherwise null + */ + function getVariableDeclaratorNode(node) { + return getParentNodeByType(node, "VariableDeclarator"); + } + + /** + * Check to see if the node is part of the multi-line variable declaration. + * Also if its on the same line as the varNode + * @param {ASTNode} node node to check + * @param {ASTNode} varNode variable declaration node to check against + * @returns {boolean} True if all the above condition satisfy + */ + function isNodeInVarOnTop(node, varNode) { + return ( + varNode && + varNode.parent.loc.start.line === node.loc.start.line && + varNode.parent.declarations.length > 1 + ); + } + + /** + * Check to see if the argument before the callee node is multi-line and + * there should only be 1 argument before the callee node + * @param {ASTNode} node node to check + * @returns {boolean} True if arguments are multi-line + */ + function isArgBeforeCalleeNodeMultiline(node) { + const parent = node.parent; + + if (parent.arguments.length >= 2 && parent.arguments[1] === node) { + return ( + parent.arguments[0].loc.end.line > + parent.arguments[0].loc.start.line + ); + } + + return false; + } + + /** + * Check to see if the node is a file level IIFE + * @param {ASTNode} node The function node to check. + * @returns {boolean} True if the node is the outer IIFE + */ + function isOuterIIFE(node) { + const parent = node.parent; + let stmt = parent.parent; + + /* + * Verify that the node is an IIEF + */ + if (parent.type !== "CallExpression" || parent.callee !== node) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIEF is outer + */ + while ( + (stmt.type === "UnaryExpression" && + (stmt.operator === "!" || + stmt.operator === "~" || + stmt.operator === "+" || + stmt.operator === "-")) || + stmt.type === "AssignmentExpression" || + stmt.type === "LogicalExpression" || + stmt.type === "SequenceExpression" || + stmt.type === "VariableDeclarator" + ) { + stmt = stmt.parent; + } + + return ( + (stmt.type === "ExpressionStatement" || + stmt.type === "VariableDeclaration") && + stmt.parent && + stmt.parent.type === "Program" + ); + } + + /** + * Check indent for function block content + * @param {ASTNode} node A BlockStatement node that is inside of a function. + * @returns {void} + */ + function checkIndentInFunctionBlock(node) { + /* + * Search first caller in chain. + * Ex.: + * + * Models <- Identifier + * .User + * .find() + * .exec(function() { + * // function body + * }); + * + * Looks for 'Models' + */ + const calleeNode = node.parent; // FunctionExpression + let indent; + + if ( + calleeNode.parent && + (calleeNode.parent.type === "Property" || + calleeNode.parent.type === "ArrayExpression") + ) { + // If function is part of array or object, comma can be put at left + indent = getNodeIndent(calleeNode, false).goodChar; + } else { + // If function is standalone, simple calculate indent + indent = getNodeIndent(calleeNode).goodChar; + } + + if (calleeNode.parent.type === "CallExpression") { + const calleeParent = calleeNode.parent; + + if ( + calleeNode.type !== "FunctionExpression" && + calleeNode.type !== "ArrowFunctionExpression" + ) { + if ( + calleeParent && + calleeParent.loc.start.line < node.loc.start.line + ) { + indent = getNodeIndent(calleeParent).goodChar; + } + } else { + if ( + isArgBeforeCalleeNodeMultiline(calleeNode) && + calleeParent.callee.loc.start.line === + calleeParent.callee.loc.end.line && + !isNodeFirstInLine(calleeNode) + ) { + indent = getNodeIndent(calleeParent).goodChar; + } + } + } + + /* + * function body indent should be indent + indent size, unless this + * is a FunctionDeclaration, FunctionExpression, or outer IIFE and the corresponding options are enabled. + */ + let functionOffset = indentSize; + + if (options.outerIIFEBody !== null && isOuterIIFE(calleeNode)) { + functionOffset = options.outerIIFEBody * indentSize; + } else if (calleeNode.type === "FunctionExpression") { + functionOffset = options.FunctionExpression.body * indentSize; + } else if (calleeNode.type === "FunctionDeclaration") { + functionOffset = options.FunctionDeclaration.body * indentSize; + } + indent += functionOffset; + + // check if the node is inside a variable + const parentVarNode = getVariableDeclaratorNode(node); + + if (parentVarNode && isNodeInVarOnTop(node, parentVarNode)) { + indent += + indentSize * + options.VariableDeclarator[parentVarNode.parent.kind]; + } + + if (node.body.length > 0) { + checkNodesIndent(node.body, indent); + } + + checkLastNodeLineIndent(node, indent - functionOffset); + } + + /** + * Checks if the given node starts and ends on the same line + * @param {ASTNode} node The node to check + * @returns {boolean} Whether or not the block starts and ends on the same line. + */ + function isSingleLineNode(node) { + const lastToken = sourceCode.getLastToken(node), + startLine = node.loc.start.line, + endLine = lastToken.loc.end.line; + + return startLine === endLine; + } + + /** + * Check indent for array block content or object block content + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInArrayOrObjectBlock(node) { + // Skip inline + if (isSingleLineNode(node)) { + return; + } + + let elements = + node.type === "ArrayExpression" + ? node.elements + : node.properties; + + // filter out empty elements example would be [ , 2] so remove first element as espree considers it as null + elements = elements.filter(elem => elem !== null); + + let nodeIndent; + let elementsIndent; + const parentVarNode = getVariableDeclaratorNode(node); + + // TODO - come up with a better strategy in future + if (isNodeFirstInLine(node)) { + const parent = node.parent; + + nodeIndent = getNodeIndent(parent).goodChar; + if ( + !parentVarNode || + parentVarNode.loc.start.line !== node.loc.start.line + ) { + if ( + parent.type !== "VariableDeclarator" || + parentVarNode === parentVarNode.parent.declarations[0] + ) { + if ( + parent.type === "VariableDeclarator" && + parentVarNode.loc.start.line === + parent.loc.start.line + ) { + nodeIndent += + indentSize * + options.VariableDeclarator[ + parentVarNode.parent.kind + ]; + } else if ( + parent.type === "ObjectExpression" || + parent.type === "ArrayExpression" + ) { + const parentElements = + node.parent.type === "ObjectExpression" + ? node.parent.properties + : node.parent.elements; + + if ( + parentElements[0] && + parentElements[0].loc.start.line === + parent.loc.start.line && + parentElements[0].loc.end.line !== + parent.loc.start.line + ) { + /* + * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest. + * e.g. [{ + * foo: 1 + * }, + * { + * bar: 1 + * }] + * the second object is not indented. + */ + } else if ( + typeof options[parent.type] === "number" + ) { + nodeIndent += options[parent.type] * indentSize; + } else { + nodeIndent = parentElements[0].loc.start.column; + } + } else if ( + parent.type === "CallExpression" || + parent.type === "NewExpression" + ) { + if ( + typeof options.CallExpression.arguments === + "number" + ) { + nodeIndent += + options.CallExpression.arguments * + indentSize; + } else if ( + options.CallExpression.arguments === "first" + ) { + if (parent.arguments.includes(node)) { + nodeIndent = + parent.arguments[0].loc.start.column; + } + } else { + nodeIndent += indentSize; + } + } else if ( + parent.type === "LogicalExpression" || + parent.type === "ArrowFunctionExpression" + ) { + nodeIndent += indentSize; + } + } + } + + checkFirstNodeLineIndent(node, nodeIndent); + } else { + nodeIndent = getNodeIndent(node).goodChar; + } + + if (options[node.type] === "first") { + elementsIndent = elements.length + ? elements[0].loc.start.column + : 0; // If there are no elements, elementsIndent doesn't matter. + } else { + elementsIndent = nodeIndent + indentSize * options[node.type]; + } + + /* + * Check if the node is a multiple variable declaration; if so, then + * make sure indentation takes that into account. + */ + if (isNodeInVarOnTop(node, parentVarNode)) { + elementsIndent += + indentSize * + options.VariableDeclarator[parentVarNode.parent.kind]; + } + + checkNodesIndent(elements, elementsIndent); + + if (elements.length > 0) { + // Skip last block line check if last item in same line + if (elements.at(-1).loc.end.line === node.loc.end.line) { + return; + } + } + + checkLastNodeLineIndent( + node, + nodeIndent + + (isNodeInVarOnTop(node, parentVarNode) + ? options.VariableDeclarator[ + parentVarNode.parent.kind + ] * indentSize + : 0), + ); + } + + /** + * Check if the node or node body is a BlockStatement or not + * @param {ASTNode} node node to test + * @returns {boolean} True if it or its body is a block statement + */ + function isNodeBodyBlock(node) { + return ( + node.type === "BlockStatement" || + node.type === "ClassBody" || + (node.body && node.body.type === "BlockStatement") || + (node.consequent && node.consequent.type === "BlockStatement") + ); + } + + /** + * Check indentation for blocks + * @param {ASTNode} node node to check + * @returns {void} + */ + function blockIndentationCheck(node) { + // Skip inline blocks + if (isSingleLineNode(node)) { + return; + } + + if ( + node.parent && + (node.parent.type === "FunctionExpression" || + node.parent.type === "FunctionDeclaration" || + node.parent.type === "ArrowFunctionExpression") + ) { + checkIndentInFunctionBlock(node); + return; + } + + let indent; + let nodesToCheck; + + /* + * For this statements we should check indent from statement beginning, + * not from the beginning of the block. + */ + const statementsWithProperties = [ + "IfStatement", + "WhileStatement", + "ForStatement", + "ForInStatement", + "ForOfStatement", + "DoWhileStatement", + "ClassDeclaration", + "TryStatement", + ]; + + if ( + node.parent && + statementsWithProperties.includes(node.parent.type) && + isNodeBodyBlock(node) + ) { + indent = getNodeIndent(node.parent).goodChar; + } else if (node.parent && node.parent.type === "CatchClause") { + indent = getNodeIndent(node.parent.parent).goodChar; + } else { + indent = getNodeIndent(node).goodChar; + } + + if ( + node.type === "IfStatement" && + node.consequent.type !== "BlockStatement" + ) { + nodesToCheck = [node.consequent]; + } else if (Array.isArray(node.body)) { + nodesToCheck = node.body; + } else { + nodesToCheck = [node.body]; + } + + if (nodesToCheck.length > 0) { + checkNodesIndent(nodesToCheck, indent + indentSize); + } + + if (node.type === "BlockStatement") { + checkLastNodeLineIndent(node, indent); + } + } + + /** + * Filter out the elements which are on the same line of each other or the node. + * basically have only 1 elements from each line except the variable declaration line. + * @param {ASTNode} node Variable declaration node + * @returns {ASTNode[]} Filtered elements + */ + function filterOutSameLineVars(node) { + return node.declarations.reduce((finalCollection, elem) => { + const lastElem = finalCollection.at(-1); + + if ( + (elem.loc.start.line !== node.loc.start.line && + !lastElem) || + (lastElem && + lastElem.loc.start.line !== elem.loc.start.line) + ) { + finalCollection.push(elem); + } + + return finalCollection; + }, []); + } + + /** + * Check indentation for variable declarations + * @param {ASTNode} node node to examine + * @returns {void} + */ + function checkIndentInVariableDeclarations(node) { + const elements = filterOutSameLineVars(node); + const nodeIndent = getNodeIndent(node).goodChar; + const lastElement = elements.at(-1); + + const elementsIndent = + nodeIndent + indentSize * options.VariableDeclarator[node.kind]; + + checkNodesIndent(elements, elementsIndent); + + // Only check the last line if there is any token after the last item + if ( + sourceCode.getLastToken(node).loc.end.line <= + lastElement.loc.end.line + ) { + return; + } + + const tokenBeforeLastElement = + sourceCode.getTokenBefore(lastElement); + + if (tokenBeforeLastElement.value === ",") { + // Special case for comma-first syntax where the semicolon is indented + checkLastNodeLineIndent( + node, + getNodeIndent(tokenBeforeLastElement).goodChar, + ); + } else { + checkLastNodeLineIndent(node, elementsIndent - indentSize); + } + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + * @param {ASTNode} node node to examine + * @returns {void} + */ + function blockLessNodes(node) { + if (node.body.type !== "BlockStatement") { + blockIndentationCheck(node); + } + } + + /** + * Returns the expected indentation for the case statement + * @param {ASTNode} node node to examine + * @param {int} [providedSwitchIndent] indent for switch statement + * @returns {int} indent size + */ + function expectedCaseIndent(node, providedSwitchIndent) { + const switchNode = + node.type === "SwitchStatement" ? node : node.parent; + const switchIndent = + typeof providedSwitchIndent === "undefined" + ? getNodeIndent(switchNode).goodChar + : providedSwitchIndent; + let caseIndent; + + if (caseIndentStore[switchNode.loc.start.line]) { + return caseIndentStore[switchNode.loc.start.line]; + } + + if (switchNode.cases.length > 0 && options.SwitchCase === 0) { + caseIndent = switchIndent; + } else { + caseIndent = switchIndent + indentSize * options.SwitchCase; + } + + caseIndentStore[switchNode.loc.start.line] = caseIndent; + return caseIndent; + } + + /** + * Checks whether a return statement is wrapped in () + * @param {ASTNode} node node to examine + * @returns {boolean} the result + */ + function isWrappedInParenthesis(node) { + const regex = /^return\s*?\(\s*?\);*?/u; + + const statementWithoutArgument = sourceCode + .getText(node) + .replace(sourceCode.getText(node.argument), ""); + + return regex.test(statementWithoutArgument); + } + + return { + Program(node) { + if (node.body.length > 0) { + // Root nodes should have no indent + checkNodesIndent(node.body, getNodeIndent(node).goodChar); + } + }, + + ClassBody: blockIndentationCheck, + + BlockStatement: blockIndentationCheck, + + WhileStatement: blockLessNodes, + + ForStatement: blockLessNodes, + + ForInStatement: blockLessNodes, + + ForOfStatement: blockLessNodes, + + DoWhileStatement: blockLessNodes, + + IfStatement(node) { + if ( + node.consequent.type !== "BlockStatement" && + node.consequent.loc.start.line > node.loc.start.line + ) { + blockIndentationCheck(node); + } + }, + + VariableDeclaration(node) { + if ( + node.declarations.at(-1).loc.start.line > + node.declarations[0].loc.start.line + ) { + checkIndentInVariableDeclarations(node); + } + }, + + ObjectExpression(node) { + checkIndentInArrayOrObjectBlock(node); + }, + + ArrayExpression(node) { + checkIndentInArrayOrObjectBlock(node); + }, + + MemberExpression(node) { + if (typeof options.MemberExpression === "undefined") { + return; + } + + if (isSingleLineNode(node)) { + return; + } + + /* + * The typical layout of variable declarations and assignments + * alter the expectation of correct indentation. Skip them. + * TODO: Add appropriate configuration options for variable + * declarations and assignments. + */ + if ( + getParentNodeByType(node, "VariableDeclarator", [ + "FunctionExpression", + "ArrowFunctionExpression", + ]) + ) { + return; + } + + if ( + getParentNodeByType(node, "AssignmentExpression", [ + "FunctionExpression", + ]) + ) { + return; + } + + const propertyIndent = + getNodeIndent(node).goodChar + + indentSize * options.MemberExpression; + + const checkNodes = [node.property]; + + const dot = sourceCode.getTokenBefore(node.property); + + if (dot.type === "Punctuator" && dot.value === ".") { + checkNodes.push(dot); + } + + checkNodesIndent(checkNodes, propertyIndent); + }, + + SwitchStatement(node) { + // Switch is not a 'BlockStatement' + const switchIndent = getNodeIndent(node).goodChar; + const caseIndent = expectedCaseIndent(node, switchIndent); + + checkNodesIndent(node.cases, caseIndent); + + checkLastNodeLineIndent(node, switchIndent); + }, + + SwitchCase(node) { + // Skip inline cases + if (isSingleLineNode(node)) { + return; + } + const caseIndent = expectedCaseIndent(node); + + checkNodesIndent(node.consequent, caseIndent + indentSize); + }, + + FunctionDeclaration(node) { + if (isSingleLineNode(node)) { + return; + } + if ( + options.FunctionDeclaration.parameters === "first" && + node.params.length + ) { + checkNodesIndent( + node.params.slice(1), + node.params[0].loc.start.column, + ); + } else if (options.FunctionDeclaration.parameters !== null) { + checkNodesIndent( + node.params, + getNodeIndent(node).goodChar + + indentSize * options.FunctionDeclaration.parameters, + ); + } + }, + + FunctionExpression(node) { + if (isSingleLineNode(node)) { + return; + } + if ( + options.FunctionExpression.parameters === "first" && + node.params.length + ) { + checkNodesIndent( + node.params.slice(1), + node.params[0].loc.start.column, + ); + } else if (options.FunctionExpression.parameters !== null) { + checkNodesIndent( + node.params, + getNodeIndent(node).goodChar + + indentSize * options.FunctionExpression.parameters, + ); + } + }, + + ReturnStatement(node) { + if (isSingleLineNode(node)) { + return; + } + + const firstLineIndent = getNodeIndent(node).goodChar; + + // in case if return statement is wrapped in parenthesis + if (isWrappedInParenthesis(node)) { + checkLastReturnStatementLineIndent(node, firstLineIndent); + } else { + checkNodeIndent(node, firstLineIndent); + } + }, + + CallExpression(node) { + if (isSingleLineNode(node)) { + return; + } + if ( + options.CallExpression.arguments === "first" && + node.arguments.length + ) { + checkNodesIndent( + node.arguments.slice(1), + node.arguments[0].loc.start.column, + ); + } else if (options.CallExpression.arguments !== null) { + checkNodesIndent( + node.arguments, + getNodeIndent(node).goodChar + + indentSize * options.CallExpression.arguments, + ); + } + }, + }; + }, }; diff --git a/lib/rules/indent.js b/lib/rules/indent.js index 6b0d579ba1af..e3d5b2afe6e6 100644 --- a/lib/rules/indent.js +++ b/lib/rules/indent.js @@ -20,93 +20,93 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const KNOWN_NODES = new Set([ - "AssignmentExpression", - "AssignmentPattern", - "ArrayExpression", - "ArrayPattern", - "ArrowFunctionExpression", - "AwaitExpression", - "BlockStatement", - "BinaryExpression", - "BreakStatement", - "CallExpression", - "CatchClause", - "ChainExpression", - "ClassBody", - "ClassDeclaration", - "ClassExpression", - "ConditionalExpression", - "ContinueStatement", - "DoWhileStatement", - "DebuggerStatement", - "EmptyStatement", - "ExperimentalRestProperty", - "ExperimentalSpreadProperty", - "ExpressionStatement", - "ForStatement", - "ForInStatement", - "ForOfStatement", - "FunctionDeclaration", - "FunctionExpression", - "Identifier", - "IfStatement", - "Literal", - "LabeledStatement", - "LogicalExpression", - "MemberExpression", - "MetaProperty", - "MethodDefinition", - "NewExpression", - "ObjectExpression", - "ObjectPattern", - "PrivateIdentifier", - "Program", - "Property", - "PropertyDefinition", - "RestElement", - "ReturnStatement", - "SequenceExpression", - "SpreadElement", - "StaticBlock", - "Super", - "SwitchCase", - "SwitchStatement", - "TaggedTemplateExpression", - "TemplateElement", - "TemplateLiteral", - "ThisExpression", - "ThrowStatement", - "TryStatement", - "UnaryExpression", - "UpdateExpression", - "VariableDeclaration", - "VariableDeclarator", - "WhileStatement", - "WithStatement", - "YieldExpression", - "JSXFragment", - "JSXOpeningFragment", - "JSXClosingFragment", - "JSXIdentifier", - "JSXNamespacedName", - "JSXMemberExpression", - "JSXEmptyExpression", - "JSXExpressionContainer", - "JSXElement", - "JSXClosingElement", - "JSXOpeningElement", - "JSXAttribute", - "JSXSpreadAttribute", - "JSXText", - "ExportDefaultDeclaration", - "ExportNamedDeclaration", - "ExportAllDeclaration", - "ExportSpecifier", - "ImportDeclaration", - "ImportSpecifier", - "ImportDefaultSpecifier", - "ImportNamespaceSpecifier", - "ImportExpression" + "AssignmentExpression", + "AssignmentPattern", + "ArrayExpression", + "ArrayPattern", + "ArrowFunctionExpression", + "AwaitExpression", + "BlockStatement", + "BinaryExpression", + "BreakStatement", + "CallExpression", + "CatchClause", + "ChainExpression", + "ClassBody", + "ClassDeclaration", + "ClassExpression", + "ConditionalExpression", + "ContinueStatement", + "DoWhileStatement", + "DebuggerStatement", + "EmptyStatement", + "ExperimentalRestProperty", + "ExperimentalSpreadProperty", + "ExpressionStatement", + "ForStatement", + "ForInStatement", + "ForOfStatement", + "FunctionDeclaration", + "FunctionExpression", + "Identifier", + "IfStatement", + "Literal", + "LabeledStatement", + "LogicalExpression", + "MemberExpression", + "MetaProperty", + "MethodDefinition", + "NewExpression", + "ObjectExpression", + "ObjectPattern", + "PrivateIdentifier", + "Program", + "Property", + "PropertyDefinition", + "RestElement", + "ReturnStatement", + "SequenceExpression", + "SpreadElement", + "StaticBlock", + "Super", + "SwitchCase", + "SwitchStatement", + "TaggedTemplateExpression", + "TemplateElement", + "TemplateLiteral", + "ThisExpression", + "ThrowStatement", + "TryStatement", + "UnaryExpression", + "UpdateExpression", + "VariableDeclaration", + "VariableDeclarator", + "WhileStatement", + "WithStatement", + "YieldExpression", + "JSXFragment", + "JSXOpeningFragment", + "JSXClosingFragment", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXEmptyExpression", + "JSXExpressionContainer", + "JSXElement", + "JSXClosingElement", + "JSXOpeningElement", + "JSXAttribute", + "JSXSpreadAttribute", + "JSXText", + "ExportDefaultDeclaration", + "ExportNamedDeclaration", + "ExportAllDeclaration", + "ExportSpecifier", + "ImportDeclaration", + "ImportSpecifier", + "ImportDefaultSpecifier", + "ImportNamespaceSpecifier", + "ImportExpression", ]); /* @@ -122,1700 +122,2213 @@ const KNOWN_NODES = new Set([ * and report the token if the two values are not equal. */ - /** * A mutable map that stores (key, value) pairs. The keys are numeric indices, and must be unique. * This is intended to be a generic wrapper around a map with non-negative integer keys, so that the underlying implementation * can easily be swapped out. */ class IndexMap { - - /** - * Creates an empty map - * @param {number} maxKey The maximum key - */ - constructor(maxKey) { - - // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance. - this._values = Array(maxKey + 1); - } - - /** - * Inserts an entry into the map. - * @param {number} key The entry's key - * @param {any} value The entry's value - * @returns {void} - */ - insert(key, value) { - this._values[key] = value; - } - - /** - * Finds the value of the entry with the largest key less than or equal to the provided key - * @param {number} key The provided key - * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists. - */ - findLastNotAfter(key) { - const values = this._values; - - for (let index = key; index >= 0; index--) { - const value = values[index]; - - if (value) { - return value; - } - } - return void 0; - } - - /** - * Deletes all of the keys in the interval [start, end) - * @param {number} start The start of the range - * @param {number} end The end of the range - * @returns {void} - */ - deleteRange(start, end) { - this._values.fill(void 0, start, end); - } + /** + * Creates an empty map + * @param {number} maxKey The maximum key + */ + constructor(maxKey) { + // Initializing the array with the maximum expected size avoids dynamic reallocations that could degrade performance. + this._values = Array(maxKey + 1); + } + + /** + * Inserts an entry into the map. + * @param {number} key The entry's key + * @param {any} value The entry's value + * @returns {void} + */ + insert(key, value) { + this._values[key] = value; + } + + /** + * Finds the value of the entry with the largest key less than or equal to the provided key + * @param {number} key The provided key + * @returns {*|undefined} The value of the found entry, or undefined if no such entry exists. + */ + findLastNotAfter(key) { + const values = this._values; + + for (let index = key; index >= 0; index--) { + const value = values[index]; + + if (value) { + return value; + } + } + return void 0; + } + + /** + * Deletes all of the keys in the interval [start, end) + * @param {number} start The start of the range + * @param {number} end The end of the range + * @returns {void} + */ + deleteRange(start, end) { + this._values.fill(void 0, start, end); + } } /** * A helper class to get token-based info related to indentation */ class TokenInfo { - - /** - * @param {SourceCode} sourceCode A SourceCode object - */ - constructor(sourceCode) { - this.sourceCode = sourceCode; - this.firstTokensByLineNumber = new Map(); - const tokens = sourceCode.tokensAndComments; - - for (let i = 0; i < tokens.length; i++) { - const token = tokens[i]; - - if (!this.firstTokensByLineNumber.has(token.loc.start.line)) { - this.firstTokensByLineNumber.set(token.loc.start.line, token); - } - if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) { - this.firstTokensByLineNumber.set(token.loc.end.line, token); - } - } - } - - /** - * Gets the first token on a given token's line - * @param {Token|ASTNode} token a node or token - * @returns {Token} The first token on the given line - */ - getFirstTokenOfLine(token) { - return this.firstTokensByLineNumber.get(token.loc.start.line); - } - - /** - * Determines whether a token is the first token in its line - * @param {Token} token The token - * @returns {boolean} `true` if the token is the first on its line - */ - isFirstTokenOfLine(token) { - return this.getFirstTokenOfLine(token) === token; - } - - /** - * Get the actual indent of a token - * @param {Token} token Token to examine. This should be the first token on its line. - * @returns {string} The indentation characters that precede the token - */ - getTokenIndent(token) { - return this.sourceCode.text.slice(token.range[0] - token.loc.start.column, token.range[0]); - } + /** + * @param {SourceCode} sourceCode A SourceCode object + */ + constructor(sourceCode) { + this.sourceCode = sourceCode; + this.firstTokensByLineNumber = new Map(); + const tokens = sourceCode.tokensAndComments; + + for (let i = 0; i < tokens.length; i++) { + const token = tokens[i]; + + if (!this.firstTokensByLineNumber.has(token.loc.start.line)) { + this.firstTokensByLineNumber.set(token.loc.start.line, token); + } + if ( + !this.firstTokensByLineNumber.has(token.loc.end.line) && + sourceCode.text + .slice( + token.range[1] - token.loc.end.column, + token.range[1], + ) + .trim() + ) { + this.firstTokensByLineNumber.set(token.loc.end.line, token); + } + } + } + + /** + * Gets the first token on a given token's line + * @param {Token|ASTNode} token a node or token + * @returns {Token} The first token on the given line + */ + getFirstTokenOfLine(token) { + return this.firstTokensByLineNumber.get(token.loc.start.line); + } + + /** + * Determines whether a token is the first token in its line + * @param {Token} token The token + * @returns {boolean} `true` if the token is the first on its line + */ + isFirstTokenOfLine(token) { + return this.getFirstTokenOfLine(token) === token; + } + + /** + * Get the actual indent of a token + * @param {Token} token Token to examine. This should be the first token on its line. + * @returns {string} The indentation characters that precede the token + */ + getTokenIndent(token) { + return this.sourceCode.text.slice( + token.range[0] - token.loc.start.column, + token.range[0], + ); + } } /** * A class to store information on desired offsets of tokens from each other */ class OffsetStorage { - - /** - * @param {TokenInfo} tokenInfo a TokenInfo instance - * @param {number} indentSize The desired size of each indentation level - * @param {string} indentType The indentation character - * @param {number} maxIndex The maximum end index of any token - */ - constructor(tokenInfo, indentSize, indentType, maxIndex) { - this._tokenInfo = tokenInfo; - this._indentSize = indentSize; - this._indentType = indentType; - - this._indexMap = new IndexMap(maxIndex); - this._indexMap.insert(0, { offset: 0, from: null, force: false }); - - this._lockedFirstTokens = new WeakMap(); - this._desiredIndentCache = new WeakMap(); - this._ignoredTokens = new WeakSet(); - } - - _getOffsetDescriptor(token) { - return this._indexMap.findLastNotAfter(token.range[0]); - } - - /** - * Sets the offset column of token B to match the offset column of token A. - * - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In - * most cases, `setDesiredOffset` should be used instead. - * @param {Token} baseToken The first token - * @param {Token} offsetToken The second token, whose offset should be matched to the first token - * @returns {void} - */ - matchOffsetOf(baseToken, offsetToken) { - - /* - * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to - * the token that it depends on. For example, with the `ArrayExpression: first` option, the first - * token of each element in the array after the first will be mapped to the first token of the first - * element. The desired indentation of each of these tokens is computed based on the desired indentation - * of the "first" element, rather than through the normal offset mechanism. - */ - this._lockedFirstTokens.set(offsetToken, baseToken); - } - - /** - * Sets the desired offset of a token. - * - * This uses a line-based offset collapsing behavior to handle tokens on the same line. - * For example, consider the following two cases: - * - * ( - * [ - * bar - * ] - * ) - * - * ([ - * bar - * ]) - * - * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from - * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is - * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) - * from the start of its line. - * - * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level - * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the - * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented - * by 1 indent level from the start of the line. - * - * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, - * without needing to check which lines those tokens are on. - * - * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive - * behavior can occur. For example, consider the following cases: - * - * foo( - * ). - * bar( - * baz - * ) - * - * foo( - * ).bar( - * baz - * ) - * - * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` - * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` - * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no - * collapsing would occur). - * - * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and - * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed - * in the second case. - * @param {Token} token The token - * @param {Token} fromToken The token that `token` should be offset from - * @param {number} offset The desired indent level - * @returns {void} - */ - setDesiredOffset(token, fromToken, offset) { - return this.setDesiredOffsets(token.range, fromToken, offset); - } - - /** - * Sets the desired offset of all tokens in a range - * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. - * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains - * it). This means that the offset of each token is updated O(AST depth) times. - * It would not be performant to store and update the offsets for each token independently, because the rule would end - * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. - * - * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following - * list could represent the state of the offset tree at a given point: - * - * - Tokens starting in the interval [0, 15) are aligned with the beginning of the file - * - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token - * - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token - * - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token - * - Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token - * - * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: - * `setDesiredOffsets([30, 43], fooToken, 1);` - * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. - * @param {Token} fromToken The token that this is offset from - * @param {number} offset The desired indent level - * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. - * @returns {void} - */ - setDesiredOffsets(range, fromToken, offset, force) { - - /* - * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset - * descriptor. The tree for the example above would have the following nodes: - * - * * key: 0, value: { offset: 0, from: null } - * * key: 15, value: { offset: 1, from: barToken } - * * key: 30, value: { offset: 1, from: fooToken } - * * key: 43, value: { offset: 2, from: barToken } - * * key: 820, value: { offset: 1, from: bazToken } - * - * To find the offset descriptor for any given token, one needs to find the node with the largest key - * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key. - */ - - const descriptorToInsert = { offset, from: fromToken, force }; - - const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]); - - const fromTokenIsInRange = fromToken && fromToken.range[0] >= range[0] && fromToken.range[1] <= range[1]; - const fromTokenDescriptor = fromTokenIsInRange && this._getOffsetDescriptor(fromToken); - - // First, remove any existing nodes in the range from the map. - this._indexMap.deleteRange(range[0] + 1, range[1]); - - // Insert a new node into the map for this range - this._indexMap.insert(range[0], descriptorToInsert); - - /* - * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, - * even if it's in the current range. - */ - if (fromTokenIsInRange) { - this._indexMap.insert(fromToken.range[0], fromTokenDescriptor); - this._indexMap.insert(fromToken.range[1], descriptorToInsert); - } - - /* - * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following - * tokens the same as it was before. - */ - this._indexMap.insert(range[1], descriptorAfterRange); - } - - /** - * Gets the desired indent of a token - * @param {Token} token The token - * @returns {string} The desired indent of the token - */ - getDesiredIndent(token) { - if (!this._desiredIndentCache.has(token)) { - - if (this._ignoredTokens.has(token)) { - - /* - * If the token is ignored, use the actual indent of the token as the desired indent. - * This ensures that no errors are reported for this token. - */ - this._desiredIndentCache.set( - token, - this._tokenInfo.getTokenIndent(token) - ); - } else if (this._lockedFirstTokens.has(token)) { - const firstToken = this._lockedFirstTokens.get(token); - - this._desiredIndentCache.set( - token, - - // (indentation for the first element's line) - this.getDesiredIndent(this._tokenInfo.getFirstTokenOfLine(firstToken)) + - - // (space between the start of the first element's line and the first element) - this._indentType.repeat(firstToken.loc.start.column - this._tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column) - ); - } else { - const offsetInfo = this._getOffsetDescriptor(token); - const offset = ( - offsetInfo.from && - offsetInfo.from.loc.start.line === token.loc.start.line && - !/^\s*?\n/u.test(token.value) && - !offsetInfo.force - ) ? 0 : offsetInfo.offset * this._indentSize; - - this._desiredIndentCache.set( - token, - (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : "") + this._indentType.repeat(offset) - ); - } - } - return this._desiredIndentCache.get(token); - } - - /** - * Ignores a token, preventing it from being reported. - * @param {Token} token The token - * @returns {void} - */ - ignoreToken(token) { - if (this._tokenInfo.isFirstTokenOfLine(token)) { - this._ignoredTokens.add(token); - } - } - - /** - * Gets the first token that the given token's indentation is dependent on - * @param {Token} token The token - * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level - */ - getFirstDependency(token) { - return this._getOffsetDescriptor(token).from; - } + /** + * @param {TokenInfo} tokenInfo a TokenInfo instance + * @param {number} indentSize The desired size of each indentation level + * @param {string} indentType The indentation character + * @param {number} maxIndex The maximum end index of any token + */ + constructor(tokenInfo, indentSize, indentType, maxIndex) { + this._tokenInfo = tokenInfo; + this._indentSize = indentSize; + this._indentType = indentType; + + this._indexMap = new IndexMap(maxIndex); + this._indexMap.insert(0, { offset: 0, from: null, force: false }); + + this._lockedFirstTokens = new WeakMap(); + this._desiredIndentCache = new WeakMap(); + this._ignoredTokens = new WeakSet(); + } + + _getOffsetDescriptor(token) { + return this._indexMap.findLastNotAfter(token.range[0]); + } + + /** + * Sets the offset column of token B to match the offset column of token A. + * - **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * most cases, `setDesiredOffset` should be used instead. + * @param {Token} baseToken The first token + * @param {Token} offsetToken The second token, whose offset should be matched to the first token + * @returns {void} + */ + matchOffsetOf(baseToken, offsetToken) { + /* + * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to + * the token that it depends on. For example, with the `ArrayExpression: first` option, the first + * token of each element in the array after the first will be mapped to the first token of the first + * element. The desired indentation of each of these tokens is computed based on the desired indentation + * of the "first" element, rather than through the normal offset mechanism. + */ + this._lockedFirstTokens.set(offsetToken, baseToken); + } + + /** + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * @param {Token} token The token + * @param {Token} fromToken The token that `token` should be offset from + * @param {number} offset The desired indent level + * @returns {void} + */ + setDesiredOffset(token, fromToken, offset) { + return this.setDesiredOffsets(token.range, fromToken, offset); + } + + /** + * Sets the desired offset of all tokens in a range + * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. + * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains + * it). This means that the offset of each token is updated O(AST depth) times. + * It would not be performant to store and update the offsets for each token independently, because the rule would end + * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. + * + * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following + * list could represent the state of the offset tree at a given point: + * + * - Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * - Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * - Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * - Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * - Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * + * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: + * `setDesiredOffsets([30, 43], fooToken, 1);` + * @param {[number, number]} range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. + * @param {Token} fromToken The token that this is offset from + * @param {number} offset The desired indent level + * @param {boolean} force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. + * @returns {void} + */ + setDesiredOffsets(range, fromToken, offset, force) { + /* + * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset + * descriptor. The tree for the example above would have the following nodes: + * + * * key: 0, value: { offset: 0, from: null } + * * key: 15, value: { offset: 1, from: barToken } + * * key: 30, value: { offset: 1, from: fooToken } + * * key: 43, value: { offset: 2, from: barToken } + * * key: 820, value: { offset: 1, from: bazToken } + * + * To find the offset descriptor for any given token, one needs to find the node with the largest key + * which is <= token.start. To make this operation fast, the nodes are stored in a map indexed by key. + */ + + const descriptorToInsert = { offset, from: fromToken, force }; + + const descriptorAfterRange = this._indexMap.findLastNotAfter(range[1]); + + const fromTokenIsInRange = + fromToken && + fromToken.range[0] >= range[0] && + fromToken.range[1] <= range[1]; + const fromTokenDescriptor = + fromTokenIsInRange && this._getOffsetDescriptor(fromToken); + + // First, remove any existing nodes in the range from the map. + this._indexMap.deleteRange(range[0] + 1, range[1]); + + // Insert a new node into the map for this range + this._indexMap.insert(range[0], descriptorToInsert); + + /* + * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, + * even if it's in the current range. + */ + if (fromTokenIsInRange) { + this._indexMap.insert(fromToken.range[0], fromTokenDescriptor); + this._indexMap.insert(fromToken.range[1], descriptorToInsert); + } + + /* + * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following + * tokens the same as it was before. + */ + this._indexMap.insert(range[1], descriptorAfterRange); + } + + /** + * Gets the desired indent of a token + * @param {Token} token The token + * @returns {string} The desired indent of the token + */ + getDesiredIndent(token) { + if (!this._desiredIndentCache.has(token)) { + if (this._ignoredTokens.has(token)) { + /* + * If the token is ignored, use the actual indent of the token as the desired indent. + * This ensures that no errors are reported for this token. + */ + this._desiredIndentCache.set( + token, + this._tokenInfo.getTokenIndent(token), + ); + } else if (this._lockedFirstTokens.has(token)) { + const firstToken = this._lockedFirstTokens.get(token); + + this._desiredIndentCache.set( + token, + + // (indentation for the first element's line) + this.getDesiredIndent( + this._tokenInfo.getFirstTokenOfLine(firstToken), + ) + + // (space between the start of the first element's line and the first element) + this._indentType.repeat( + firstToken.loc.start.column - + this._tokenInfo.getFirstTokenOfLine(firstToken) + .loc.start.column, + ), + ); + } else { + const offsetInfo = this._getOffsetDescriptor(token); + const offset = + offsetInfo.from && + offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/u.test(token.value) && + !offsetInfo.force + ? 0 + : offsetInfo.offset * this._indentSize; + + this._desiredIndentCache.set( + token, + (offsetInfo.from + ? this.getDesiredIndent(offsetInfo.from) + : "") + this._indentType.repeat(offset), + ); + } + } + return this._desiredIndentCache.get(token); + } + + /** + * Ignores a token, preventing it from being reported. + * @param {Token} token The token + * @returns {void} + */ + ignoreToken(token) { + if (this._tokenInfo.isFirstTokenOfLine(token)) { + this._ignoredTokens.add(token); + } + } + + /** + * Gets the first token that the given token's indentation is dependent on + * @param {Token} token The token + * @returns {Token} The token that the given token depends on, or `null` if the given token is at the top level + */ + getFirstDependency(token) { + return this._getOffsetDescriptor(token).from; + } } const ELEMENT_LIST_SCHEMA = { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["first", "off"] - } - ] + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["first", "off"], + }, + ], }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "indent", - url: "https://eslint.style/rules/js/indent" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent indentation", - recommended: false, - url: "https://eslint.org/docs/latest/rules/indent" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["tab"] - }, - { - type: "integer", - minimum: 0 - } - ] - }, - { - type: "object", - properties: { - SwitchCase: { - type: "integer", - minimum: 0, - default: 0 - }, - VariableDeclarator: { - oneOf: [ - ELEMENT_LIST_SCHEMA, - { - type: "object", - properties: { - var: ELEMENT_LIST_SCHEMA, - let: ELEMENT_LIST_SCHEMA, - const: ELEMENT_LIST_SCHEMA - }, - additionalProperties: false - } - ] - }, - outerIIFEBody: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["off"] - } - ] - }, - MemberExpression: { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - enum: ["off"] - } - ] - }, - FunctionDeclaration: { - type: "object", - properties: { - parameters: ELEMENT_LIST_SCHEMA, - body: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - }, - FunctionExpression: { - type: "object", - properties: { - parameters: ELEMENT_LIST_SCHEMA, - body: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - }, - StaticBlock: { - type: "object", - properties: { - body: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - }, - CallExpression: { - type: "object", - properties: { - arguments: ELEMENT_LIST_SCHEMA - }, - additionalProperties: false - }, - ArrayExpression: ELEMENT_LIST_SCHEMA, - ObjectExpression: ELEMENT_LIST_SCHEMA, - ImportDeclaration: ELEMENT_LIST_SCHEMA, - flatTernaryExpressions: { - type: "boolean", - default: false - }, - offsetTernaryExpressions: { - type: "boolean", - default: false - }, - ignoredNodes: { - type: "array", - items: { - type: "string", - not: { - pattern: ":exit$" - } - } - }, - ignoreComments: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - messages: { - wrongIndentation: "Expected indentation of {{expected}} but found {{actual}}." - } - }, - - create(context) { - const DEFAULT_VARIABLE_INDENT = 1; - const DEFAULT_PARAMETER_INDENT = 1; - const DEFAULT_FUNCTION_BODY_INDENT = 1; - - let indentType = "space"; - let indentSize = 4; - const options = { - SwitchCase: 0, - VariableDeclarator: { - var: DEFAULT_VARIABLE_INDENT, - let: DEFAULT_VARIABLE_INDENT, - const: DEFAULT_VARIABLE_INDENT - }, - outerIIFEBody: 1, - FunctionDeclaration: { - parameters: DEFAULT_PARAMETER_INDENT, - body: DEFAULT_FUNCTION_BODY_INDENT - }, - FunctionExpression: { - parameters: DEFAULT_PARAMETER_INDENT, - body: DEFAULT_FUNCTION_BODY_INDENT - }, - StaticBlock: { - body: DEFAULT_FUNCTION_BODY_INDENT - }, - CallExpression: { - arguments: DEFAULT_PARAMETER_INDENT - }, - MemberExpression: 1, - ArrayExpression: 1, - ObjectExpression: 1, - ImportDeclaration: 1, - flatTernaryExpressions: false, - ignoredNodes: [], - ignoreComments: false - }; - - if (context.options.length) { - if (context.options[0] === "tab") { - indentSize = 1; - indentType = "tab"; - } else { - indentSize = context.options[0]; - indentType = "space"; - } - - if (context.options[1]) { - Object.assign(options, context.options[1]); - - if (typeof options.VariableDeclarator === "number" || options.VariableDeclarator === "first") { - options.VariableDeclarator = { - var: options.VariableDeclarator, - let: options.VariableDeclarator, - const: options.VariableDeclarator - }; - } - } - } - - const sourceCode = context.sourceCode; - const tokenInfo = new TokenInfo(sourceCode); - const offsets = new OffsetStorage(tokenInfo, indentSize, indentType === "space" ? " " : "\t", sourceCode.text.length); - const parameterParens = new WeakSet(); - - /** - * Creates an error message for a line, given the expected/actual indentation. - * @param {int} expectedAmount The expected amount of indentation characters for this line - * @param {int} actualSpaces The actual number of indentation spaces that were found on this line - * @param {int} actualTabs The actual number of indentation tabs that were found on this line - * @returns {string} An error message for this line - */ - function createErrorMessageData(expectedAmount, actualSpaces, actualTabs) { - const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" - const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" - const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" - let foundStatement; - - if (actualSpaces > 0) { - - /* - * Abbreviate the message if the expected indentation is also spaces. - * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' - */ - foundStatement = indentType === "space" ? actualSpaces : `${actualSpaces} ${foundSpacesWord}`; - } else if (actualTabs > 0) { - foundStatement = indentType === "tab" ? actualTabs : `${actualTabs} ${foundTabsWord}`; - } else { - foundStatement = "0"; - } - return { - expected: expectedStatement, - actual: foundStatement - }; - } - - /** - * Reports a given indent violation - * @param {Token} token Token violating the indent rule - * @param {string} neededIndent Expected indentation string - * @returns {void} - */ - function report(token, neededIndent) { - const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); - const numSpaces = actualIndent.filter(char => char === " ").length; - const numTabs = actualIndent.filter(char => char === "\t").length; - - context.report({ - node: token, - messageId: "wrongIndentation", - data: createErrorMessageData(neededIndent.length, numSpaces, numTabs), - loc: { - start: { line: token.loc.start.line, column: 0 }, - end: { line: token.loc.start.line, column: token.loc.start.column } - }, - fix(fixer) { - const range = [token.range[0] - token.loc.start.column, token.range[0]]; - const newText = neededIndent; - - return fixer.replaceTextRange(range, newText); - } - }); - } - - /** - * Checks if a token's indentation is correct - * @param {Token} token Token to examine - * @param {string} desiredIndent Desired indentation of the string - * @returns {boolean} `true` if the token's indentation is correct - */ - function validateTokenIndent(token, desiredIndent) { - const indentation = tokenInfo.getTokenIndent(token); - - return indentation === desiredIndent || - - // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. - indentation.includes(" ") && indentation.includes("\t"); - } - - /** - * Check to see if the node is a file level IIFE - * @param {ASTNode} node The function node to check. - * @returns {boolean} True if the node is the outer IIFE - */ - function isOuterIIFE(node) { - - /* - * Verify that the node is an IIFE - */ - if (!node.parent || node.parent.type !== "CallExpression" || node.parent.callee !== node) { - return false; - } - - /* - * Navigate legal ancestors to determine whether this IIFE is outer. - * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. - * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. - */ - let statement = node.parent && node.parent.parent; - - while ( - statement.type === "UnaryExpression" && ["!", "~", "+", "-"].includes(statement.operator) || - statement.type === "AssignmentExpression" || - statement.type === "LogicalExpression" || - statement.type === "SequenceExpression" || - statement.type === "VariableDeclarator" - ) { - statement = statement.parent; - } - - return (statement.type === "ExpressionStatement" || statement.type === "VariableDeclaration") && statement.parent.type === "Program"; - } - - /** - * Counts the number of linebreaks that follow the last non-whitespace character in a string - * @param {string} string The string to check - * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character, - * or the total number of linebreaks if the string is all whitespace. - */ - function countTrailingLinebreaks(string) { - const trailingWhitespace = string.match(/\s*$/u)[0]; - const linebreakMatches = trailingWhitespace.match(astUtils.createGlobalLinebreakMatcher()); - - return linebreakMatches === null ? 0 : linebreakMatches.length; - } - - /** - * Check indentation for lists of elements (arrays, objects, function params) - * @param {ASTNode[]} elements List of elements that should be offset - * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '[' - * @param {Token} endToken The end token of the list, e.g. ']' - * @param {number|string} offset The amount that the elements should be offset - * @returns {void} - */ - function addElementListIndent(elements, startToken, endToken, offset) { - - /** - * Gets the first token of a given element, including surrounding parentheses. - * @param {ASTNode} element A node in the `elements` list - * @returns {Token} The first token of this element - */ - function getFirstToken(element) { - let token = sourceCode.getTokenBefore(element); - - while (astUtils.isOpeningParenToken(token) && token !== startToken) { - token = sourceCode.getTokenBefore(token); - } - return sourceCode.getTokenAfter(token); - } - - // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) - offsets.setDesiredOffsets( - [startToken.range[1], endToken.range[0]], - startToken, - typeof offset === "number" ? offset : 1 - ); - offsets.setDesiredOffset(endToken, startToken, 0); - - // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. - if (offset === "first" && elements.length && !elements[0]) { - return; - } - elements.forEach((element, index) => { - if (!element) { - - // Skip holes in arrays - return; - } - if (offset === "off") { - - // Ignore the first token of every element if the "off" option is used - offsets.ignoreToken(getFirstToken(element)); - } - - // Offset the following elements correctly relative to the first element - if (index === 0) { - return; - } - if (offset === "first" && tokenInfo.isFirstTokenOfLine(getFirstToken(element))) { - offsets.matchOffsetOf(getFirstToken(elements[0]), getFirstToken(element)); - } else { - const previousElement = elements[index - 1]; - const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement); - const previousElementLastToken = previousElement && sourceCode.getLastToken(previousElement); - - if ( - previousElement && - previousElementLastToken.loc.end.line - countTrailingLinebreaks(previousElementLastToken.value) > startToken.loc.end.line - ) { - offsets.setDesiredOffsets( - [previousElement.range[1], element.range[1]], - firstTokenOfPreviousElement, - 0 - ); - } - } - }); - } - - /** - * Check and decide whether to check for indentation for blockless nodes - * Scenarios are for or while statements without braces around them - * @param {ASTNode} node node to examine - * @returns {void} - */ - function addBlocklessNodeIndent(node) { - if (node.type !== "BlockStatement") { - const lastParentToken = sourceCode.getTokenBefore(node, astUtils.isNotOpeningParenToken); - - let firstBodyToken = sourceCode.getFirstToken(node); - let lastBodyToken = sourceCode.getLastToken(node); - - while ( - astUtils.isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)) && - astUtils.isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken)) - ) { - firstBodyToken = sourceCode.getTokenBefore(firstBodyToken); - lastBodyToken = sourceCode.getTokenAfter(lastBodyToken); - } - - offsets.setDesiredOffsets([firstBodyToken.range[0], lastBodyToken.range[1]], lastParentToken, 1); - } - } - - /** - * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`) - * @param {ASTNode} node A CallExpression or NewExpression node - * @returns {void} - */ - function addFunctionCallIndent(node) { - let openingParen; - - if (node.arguments.length) { - openingParen = sourceCode.getFirstTokenBetween(node.callee, node.arguments[0], astUtils.isOpeningParenToken); - } else { - openingParen = sourceCode.getLastToken(node, 1); - } - const closingParen = sourceCode.getLastToken(node); - - parameterParens.add(openingParen); - parameterParens.add(closingParen); - - /* - * If `?.` token exists, set desired offset for that. - * This logic is copied from `MemberExpression`'s. - */ - if (node.optional) { - const dotToken = sourceCode.getTokenAfter(node.callee, astUtils.isQuestionDotToken); - const calleeParenCount = sourceCode.getTokensBetween(node.callee, dotToken, { filter: astUtils.isClosingParenToken }).length; - const firstTokenOfCallee = calleeParenCount - ? sourceCode.getTokenBefore(node.callee, { skip: calleeParenCount - 1 }) - : sourceCode.getFirstToken(node.callee); - const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken); - const offsetBase = lastTokenOfCallee.loc.end.line === openingParen.loc.start.line - ? lastTokenOfCallee - : firstTokenOfCallee; - - offsets.setDesiredOffset(dotToken, offsetBase, 1); - } - - const offsetAfterToken = node.callee.type === "TaggedTemplateExpression" ? sourceCode.getFirstToken(node.callee.quasi) : openingParen; - const offsetToken = sourceCode.getTokenBefore(offsetAfterToken); - - offsets.setDesiredOffset(openingParen, offsetToken, 0); - - addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments); - } - - /** - * Checks the indentation of parenthesized values, given a list of tokens in a program - * @param {Token[]} tokens A list of tokens - * @returns {void} - */ - function addParensIndent(tokens) { - const parenStack = []; - const parenPairs = []; - - for (let i = 0; i < tokens.length; i++) { - const nextToken = tokens[i]; - - if (astUtils.isOpeningParenToken(nextToken)) { - parenStack.push(nextToken); - } else if (astUtils.isClosingParenToken(nextToken)) { - parenPairs.push({ left: parenStack.pop(), right: nextToken }); - } - } - - for (let i = parenPairs.length - 1; i >= 0; i--) { - const leftParen = parenPairs[i].left; - const rightParen = parenPairs[i].right; - - // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. - if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) { - const parenthesizedTokens = new Set(sourceCode.getTokensBetween(leftParen, rightParen)); - - parenthesizedTokens.forEach(token => { - if (!parenthesizedTokens.has(offsets.getFirstDependency(token))) { - offsets.setDesiredOffset(token, leftParen, 1); - } - }); - } - - offsets.setDesiredOffset(rightParen, leftParen, 0); - } - } - - /** - * Ignore all tokens within an unknown node whose offset do not depend - * on another token's offset within the unknown node - * @param {ASTNode} node Unknown Node - * @returns {void} - */ - function ignoreNode(node) { - const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true })); - - unknownNodeTokens.forEach(token => { - if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) { - const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token); - - if (token === firstTokenOfLine) { - offsets.ignoreToken(token); - } else { - offsets.setDesiredOffset(token, firstTokenOfLine, 0); - } - } - }); - } - - /** - * Check whether the given token is on the first line of a statement. - * @param {Token} token The token to check. - * @param {ASTNode} leafNode The expression node that the token belongs directly. - * @returns {boolean} `true` if the token is on the first line of a statement. - */ - function isOnFirstLineOfStatement(token, leafNode) { - let node = leafNode; - - while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) { - node = node.parent; - } - node = node.parent; - - return !node || node.loc.start.line === token.loc.start.line; - } - - /** - * Check whether there are any blank (whitespace-only) lines between - * two tokens on separate lines. - * @param {Token} firstToken The first token. - * @param {Token} secondToken The second token. - * @returns {boolean} `true` if the tokens are on separate lines and - * there exists a blank line between them, `false` otherwise. - */ - function hasBlankLinesBetween(firstToken, secondToken) { - const firstTokenLine = firstToken.loc.end.line; - const secondTokenLine = secondToken.loc.start.line; - - if (firstTokenLine === secondTokenLine || firstTokenLine === secondTokenLine - 1) { - return false; - } - - for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) { - if (!tokenInfo.firstTokensByLineNumber.has(line)) { - return true; - } - } - - return false; - } - - const ignoredNodeFirstTokens = new Set(); - - const baseOffsetListeners = { - "ArrayExpression, ArrayPattern"(node) { - const openingBracket = sourceCode.getFirstToken(node); - const closingBracket = sourceCode.getTokenAfter([...node.elements].reverse().find(_ => _) || openingBracket, astUtils.isClosingBracketToken); - - addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression); - }, - - "ObjectExpression, ObjectPattern"(node) { - const openingCurly = sourceCode.getFirstToken(node); - const closingCurly = sourceCode.getTokenAfter( - node.properties.length ? node.properties.at(-1) : openingCurly, - astUtils.isClosingBraceToken - ); - - addElementListIndent(node.properties, openingCurly, closingCurly, options.ObjectExpression); - }, - - ArrowFunctionExpression(node) { - const maybeOpeningParen = sourceCode.getFirstToken(node, { skip: node.async ? 1 : 0 }); - - if (astUtils.isOpeningParenToken(maybeOpeningParen)) { - const openingParen = maybeOpeningParen; - const closingParen = sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken); - - parameterParens.add(openingParen); - parameterParens.add(closingParen); - addElementListIndent(node.params, openingParen, closingParen, options.FunctionExpression.parameters); - } - - addBlocklessNodeIndent(node.body); - }, - - AssignmentExpression(node) { - const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); - - offsets.setDesiredOffsets([operator.range[0], node.range[1]], sourceCode.getLastToken(node.left), 1); - offsets.ignoreToken(operator); - offsets.ignoreToken(sourceCode.getTokenAfter(operator)); - }, - - "BinaryExpression, LogicalExpression"(node) { - const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); - - /* - * For backwards compatibility, don't check BinaryExpression indents, e.g. - * var foo = bar && - * baz; - */ - - const tokenAfterOperator = sourceCode.getTokenAfter(operator); - - offsets.ignoreToken(operator); - offsets.ignoreToken(tokenAfterOperator); - offsets.setDesiredOffset(tokenAfterOperator, operator, 0); - }, - - "BlockStatement, ClassBody"(node) { - let blockIndentLevel; - - if (node.parent && isOuterIIFE(node.parent)) { - blockIndentLevel = options.outerIIFEBody; - } else if (node.parent && (node.parent.type === "FunctionExpression" || node.parent.type === "ArrowFunctionExpression")) { - blockIndentLevel = options.FunctionExpression.body; - } else if (node.parent && node.parent.type === "FunctionDeclaration") { - blockIndentLevel = options.FunctionDeclaration.body; - } else { - blockIndentLevel = 1; - } - - /* - * For blocks that aren't lone statements, ensure that the opening curly brace - * is aligned with the parent. - */ - if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { - offsets.setDesiredOffset(sourceCode.getFirstToken(node), sourceCode.getFirstToken(node.parent), 0); - } - - addElementListIndent(node.body, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), blockIndentLevel); - }, - - CallExpression: addFunctionCallIndent, - - "ClassDeclaration[superClass], ClassExpression[superClass]"(node) { - const classToken = sourceCode.getFirstToken(node); - const extendsToken = sourceCode.getTokenBefore(node.superClass, astUtils.isNotOpeningParenToken); - - offsets.setDesiredOffsets([extendsToken.range[0], node.body.range[0]], classToken, 1); - }, - - ConditionalExpression(node) { - const firstToken = sourceCode.getFirstToken(node); - - // `flatTernaryExpressions` option is for the following style: - // var a = - // foo > 0 ? bar : - // foo < 0 ? baz : - // /*else*/ qiz ; - if (!options.flatTernaryExpressions || - !astUtils.isTokenOnSameLine(node.test, node.consequent) || - isOnFirstLineOfStatement(firstToken, node) - ) { - const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?"); - const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":"); - - const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken); - const lastConsequentToken = sourceCode.getTokenBefore(colonToken); - const firstAlternateToken = sourceCode.getTokenAfter(colonToken); - - offsets.setDesiredOffset(questionMarkToken, firstToken, 1); - offsets.setDesiredOffset(colonToken, firstToken, 1); - - offsets.setDesiredOffset(firstConsequentToken, firstToken, firstConsequentToken.type === "Punctuator" && - options.offsetTernaryExpressions ? 2 : 1); - - /* - * The alternate and the consequent should usually have the same indentation. - * If they share part of a line, align the alternate against the first token of the consequent. - * This allows the alternate to be indented correctly in cases like this: - * foo ? ( - * bar - * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` - * baz // as a result, `baz` is offset by 1 rather than 2 - * ) - */ - if (lastConsequentToken.loc.end.line === firstAlternateToken.loc.start.line) { - offsets.setDesiredOffset(firstAlternateToken, firstConsequentToken, 0); - } else { - - /** - * If the alternate and consequent do not share part of a line, offset the alternate from the first - * token of the conditional expression. For example: - * foo ? bar - * : baz - * - * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up - * having no expected indentation. - */ - offsets.setDesiredOffset(firstAlternateToken, firstToken, firstAlternateToken.type === "Punctuator" && - options.offsetTernaryExpressions ? 2 : 1); - } - } - }, - - "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement": node => addBlocklessNodeIndent(node.body), - - ExportNamedDeclaration(node) { - if (node.declaration === null) { - const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken); - - // Indent the specifiers in `export {foo, bar, baz}` - addElementListIndent(node.specifiers, sourceCode.getFirstToken(node, { skip: 1 }), closingCurly, 1); - - if (node.source) { - - // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` - offsets.setDesiredOffsets([closingCurly.range[1], node.range[1]], sourceCode.getFirstToken(node), 1); - } - } - }, - - ForStatement(node) { - const forOpeningParen = sourceCode.getFirstToken(node, 1); - - if (node.init) { - offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1); - } - if (node.test) { - offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1); - } - if (node.update) { - offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1); - } - addBlocklessNodeIndent(node.body); - }, - - "FunctionDeclaration, FunctionExpression"(node) { - const closingParen = sourceCode.getTokenBefore(node.body); - const openingParen = sourceCode.getTokenBefore(node.params.length ? node.params[0] : closingParen); - - parameterParens.add(openingParen); - parameterParens.add(closingParen); - addElementListIndent(node.params, openingParen, closingParen, options[node.type].parameters); - }, - - IfStatement(node) { - addBlocklessNodeIndent(node.consequent); - if (node.alternate) { - addBlocklessNodeIndent(node.alternate); - } - }, - - /* - * For blockless nodes with semicolon-first style, don't indent the semicolon. - * e.g. - * if (foo) - * bar() - * ; [1, 2, 3].map(foo) - * - * Traversal into the node sets indentation of the semicolon, so we need to override it on exit. - */ - ":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"(node) { - let nodesToCheck; - - if (node.type === "IfStatement") { - nodesToCheck = [node.consequent]; - if (node.alternate) { - nodesToCheck.push(node.alternate); - } - } else { - nodesToCheck = [node.body]; - } - - for (const nodeToCheck of nodesToCheck) { - const lastToken = sourceCode.getLastToken(nodeToCheck); - - if (astUtils.isSemicolonToken(lastToken)) { - const tokenBeforeLast = sourceCode.getTokenBefore(lastToken); - const tokenAfterLast = sourceCode.getTokenAfter(lastToken); - - // override indentation of `;` only if its line looks like a semicolon-first style line - if ( - !astUtils.isTokenOnSameLine(tokenBeforeLast, lastToken) && - tokenAfterLast && - astUtils.isTokenOnSameLine(lastToken, tokenAfterLast) - ) { - offsets.setDesiredOffset( - lastToken, - sourceCode.getFirstToken(node), - 0 - ); - } - } - } - }, - - ImportDeclaration(node) { - if (node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) { - const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken); - const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken); - - addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, options.ImportDeclaration); - } - - const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from"); - const sourceToken = sourceCode.getLastToken(node, token => token.type === "String"); - const semiToken = sourceCode.getLastToken(node, token => token.type === "Punctuator" && token.value === ";"); - - if (fromToken) { - const end = semiToken && semiToken.range[1] === sourceToken.range[1] ? node.range[1] : sourceToken.range[1]; - - offsets.setDesiredOffsets([fromToken.range[0], end], sourceCode.getFirstToken(node), 1); - } - }, - - ImportExpression(node) { - const openingParen = sourceCode.getFirstToken(node, 1); - const closingParen = sourceCode.getLastToken(node); - - parameterParens.add(openingParen); - parameterParens.add(closingParen); - offsets.setDesiredOffset(openingParen, sourceCode.getTokenBefore(openingParen), 0); - - addElementListIndent([node.source], openingParen, closingParen, options.CallExpression.arguments); - }, - - "MemberExpression, JSXMemberExpression, MetaProperty"(node) { - const object = node.type === "MetaProperty" ? node.meta : node.object; - const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken); - const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken); - - const objectParenCount = sourceCode.getTokensBetween(object, node.property, { filter: astUtils.isClosingParenToken }).length; - const firstObjectToken = objectParenCount - ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 }) - : sourceCode.getFirstToken(object); - const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken); - const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken; - - if (node.computed) { - - // For computed MemberExpressions, match the closing bracket with the opening bracket. - offsets.setDesiredOffset(sourceCode.getLastToken(node), firstNonObjectToken, 0); - offsets.setDesiredOffsets(node.property.range, firstNonObjectToken, 1); - } - - /* - * If the object ends on the same line that the property starts, match against the last token - * of the object, to ensure that the MemberExpression is not indented. - * - * Otherwise, match against the first token of the object, e.g. - * foo - * .bar - * .baz // <-- offset by 1 from `foo` - */ - const offsetBase = lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line - ? lastObjectToken - : firstObjectToken; - - if (typeof options.MemberExpression === "number") { - - // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. - offsets.setDesiredOffset(firstNonObjectToken, offsetBase, options.MemberExpression); - - /* - * For computed MemberExpressions, match the first token of the property against the opening bracket. - * Otherwise, match the first token of the property against the object. - */ - offsets.setDesiredOffset(secondNonObjectToken, node.computed ? firstNonObjectToken : offsetBase, options.MemberExpression); - } else { - - // If the MemberExpression option is off, ignore the dot and the first token of the property. - offsets.ignoreToken(firstNonObjectToken); - offsets.ignoreToken(secondNonObjectToken); - - // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. - offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0); - offsets.setDesiredOffset(secondNonObjectToken, firstNonObjectToken, 0); - } - }, - - NewExpression(node) { - - // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` - if (node.arguments.length > 0 || - astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && - astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) { - addFunctionCallIndent(node); - } - }, - - Property(node) { - if (!node.shorthand && !node.method && node.kind === "init") { - const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken); - - offsets.ignoreToken(sourceCode.getTokenAfter(colon)); - } - }, - - PropertyDefinition(node) { - const firstToken = sourceCode.getFirstToken(node); - const maybeSemicolonToken = sourceCode.getLastToken(node); - let keyLastToken; - - // Indent key. - if (node.computed) { - const bracketTokenL = sourceCode.getTokenBefore(node.key, astUtils.isOpeningBracketToken); - const bracketTokenR = keyLastToken = sourceCode.getTokenAfter(node.key, astUtils.isClosingBracketToken); - const keyRange = [bracketTokenL.range[1], bracketTokenR.range[0]]; - - if (bracketTokenL !== firstToken) { - offsets.setDesiredOffset(bracketTokenL, firstToken, 0); - } - offsets.setDesiredOffsets(keyRange, bracketTokenL, 1); - offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0); - } else { - const idToken = keyLastToken = sourceCode.getFirstToken(node.key); - - if (idToken !== firstToken) { - offsets.setDesiredOffset(idToken, firstToken, 1); - } - } - - // Indent initializer. - if (node.value) { - const eqToken = sourceCode.getTokenBefore(node.value, astUtils.isEqToken); - const valueToken = sourceCode.getTokenAfter(eqToken); - - offsets.setDesiredOffset(eqToken, keyLastToken, 1); - offsets.setDesiredOffset(valueToken, eqToken, 1); - if (astUtils.isSemicolonToken(maybeSemicolonToken)) { - offsets.setDesiredOffset(maybeSemicolonToken, eqToken, 1); - } - } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) { - offsets.setDesiredOffset(maybeSemicolonToken, keyLastToken, 1); - } - }, - - StaticBlock(node) { - const openingCurly = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token - const closingCurly = sourceCode.getLastToken(node); - - addElementListIndent(node.body, openingCurly, closingCurly, options.StaticBlock.body); - }, - - SwitchStatement(node) { - const openingCurly = sourceCode.getTokenAfter(node.discriminant, astUtils.isOpeningBraceToken); - const closingCurly = sourceCode.getLastToken(node); - - offsets.setDesiredOffsets([openingCurly.range[1], closingCurly.range[0]], openingCurly, options.SwitchCase); - - if (node.cases.length) { - sourceCode.getTokensBetween( - node.cases.at(-1), - closingCurly, - { includeComments: true, filter: astUtils.isCommentToken } - ).forEach(token => offsets.ignoreToken(token)); - } - }, - - SwitchCase(node) { - if (!(node.consequent.length === 1 && node.consequent[0].type === "BlockStatement")) { - const caseKeyword = sourceCode.getFirstToken(node); - const tokenAfterCurrentCase = sourceCode.getTokenAfter(node); - - offsets.setDesiredOffsets([caseKeyword.range[1], tokenAfterCurrentCase.range[0]], caseKeyword, 1); - } - }, - - TemplateLiteral(node) { - node.expressions.forEach((expression, index) => { - const previousQuasi = node.quasis[index]; - const nextQuasi = node.quasis[index + 1]; - const tokenToAlignFrom = previousQuasi.loc.start.line === previousQuasi.loc.end.line - ? sourceCode.getFirstToken(previousQuasi) - : null; - - offsets.setDesiredOffsets([previousQuasi.range[1], nextQuasi.range[0]], tokenToAlignFrom, 1); - offsets.setDesiredOffset(sourceCode.getFirstToken(nextQuasi), tokenToAlignFrom, 0); - }); - }, - - VariableDeclaration(node) { - let variableIndent = Object.hasOwn(options.VariableDeclarator, node.kind) - ? options.VariableDeclarator[node.kind] - : DEFAULT_VARIABLE_INDENT; - - const firstToken = sourceCode.getFirstToken(node), - lastToken = sourceCode.getLastToken(node); - - if (options.VariableDeclarator[node.kind] === "first") { - if (node.declarations.length > 1) { - addElementListIndent( - node.declarations, - firstToken, - lastToken, - "first" - ); - return; - } - - variableIndent = DEFAULT_VARIABLE_INDENT; - } - - if (node.declarations.at(-1).loc.start.line > node.loc.start.line) { - - /* - * VariableDeclarator indentation is a bit different from other forms of indentation, in that the - * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, - * the following indentations are correct: - * - * var foo = { - * ok: true - * }; - * - * var foo = { - * ok: true, - * }, - * bar = 1; - * - * Account for when exiting the AST (after indentations have already been set for the nodes in - * the declaration) by manually increasing the indentation level of the tokens in this declarator - * on the same line as the start of the declaration, provided that there are declarators that - * follow this one. - */ - offsets.setDesiredOffsets(node.range, firstToken, variableIndent, true); - } else { - offsets.setDesiredOffsets(node.range, firstToken, variableIndent); - } - - if (astUtils.isSemicolonToken(lastToken)) { - offsets.ignoreToken(lastToken); - } - }, - - VariableDeclarator(node) { - if (node.init) { - const equalOperator = sourceCode.getTokenBefore(node.init, astUtils.isNotOpeningParenToken); - const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator); - - offsets.ignoreToken(equalOperator); - offsets.ignoreToken(tokenAfterOperator); - offsets.setDesiredOffsets([tokenAfterOperator.range[0], node.range[1]], equalOperator, 1); - offsets.setDesiredOffset(equalOperator, sourceCode.getLastToken(node.id), 0); - } - }, - - "JSXAttribute[value]"(node) { - const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "="); - - offsets.setDesiredOffsets([equalsToken.range[0], node.value.range[1]], sourceCode.getFirstToken(node.name), 1); - }, - - JSXElement(node) { - if (node.closingElement) { - addElementListIndent(node.children, sourceCode.getFirstToken(node.openingElement), sourceCode.getFirstToken(node.closingElement), 1); - } - }, - - JSXOpeningElement(node) { - const firstToken = sourceCode.getFirstToken(node); - let closingToken; - - if (node.selfClosing) { - closingToken = sourceCode.getLastToken(node, { skip: 1 }); - offsets.setDesiredOffset(sourceCode.getLastToken(node), closingToken, 0); - } else { - closingToken = sourceCode.getLastToken(node); - } - offsets.setDesiredOffsets(node.name.range, sourceCode.getFirstToken(node)); - addElementListIndent(node.attributes, firstToken, closingToken, 1); - }, - - JSXClosingElement(node) { - const firstToken = sourceCode.getFirstToken(node); - - offsets.setDesiredOffsets(node.name.range, firstToken, 1); - }, - - JSXFragment(node) { - const firstOpeningToken = sourceCode.getFirstToken(node.openingFragment); - const firstClosingToken = sourceCode.getFirstToken(node.closingFragment); - - addElementListIndent(node.children, firstOpeningToken, firstClosingToken, 1); - }, - - JSXOpeningFragment(node) { - const firstToken = sourceCode.getFirstToken(node); - const closingToken = sourceCode.getLastToken(node); - - offsets.setDesiredOffsets(node.range, firstToken, 1); - offsets.matchOffsetOf(firstToken, closingToken); - }, - - JSXClosingFragment(node) { - const firstToken = sourceCode.getFirstToken(node); - const slashToken = sourceCode.getLastToken(node, { skip: 1 }); - const closingToken = sourceCode.getLastToken(node); - const tokenToMatch = astUtils.isTokenOnSameLine(slashToken, closingToken) ? slashToken : closingToken; - - offsets.setDesiredOffsets(node.range, firstToken, 1); - offsets.matchOffsetOf(firstToken, tokenToMatch); - }, - - JSXExpressionContainer(node) { - const openingCurly = sourceCode.getFirstToken(node); - const closingCurly = sourceCode.getLastToken(node); - - offsets.setDesiredOffsets( - [openingCurly.range[1], closingCurly.range[0]], - openingCurly, - 1 - ); - }, - - JSXSpreadAttribute(node) { - const openingCurly = sourceCode.getFirstToken(node); - const closingCurly = sourceCode.getLastToken(node); - - offsets.setDesiredOffsets( - [openingCurly.range[1], closingCurly.range[0]], - openingCurly, - 1 - ); - }, - - "*"(node) { - const firstToken = sourceCode.getFirstToken(node); - - // Ensure that the children of every node are indented at least as much as the first token. - if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { - offsets.setDesiredOffsets(node.range, firstToken, 0); - } - } - }; - - const listenerCallQueue = []; - - /* - * To ignore the indentation of a node: - * 1. Don't call the node's listener when entering it (if it has a listener) - * 2. Don't set any offsets against the first token of the node. - * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. - */ - const offsetListeners = {}; - - for (const [selector, listener] of Object.entries(baseOffsetListeners)) { - - /* - * Offset listener calls are deferred until traversal is finished, and are called as - * part of the final `Program:exit` listener. This is necessary because a node might - * be matched by multiple selectors. - * - * Example: Suppose there is an offset listener for `Identifier`, and the user has - * specified in configuration that `MemberExpression > Identifier` should be ignored. - * Due to selector specificity rules, the `Identifier` listener will get called first. However, - * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener - * should not have been called at all. Without doing extra selector matching, we don't know - * whether the Identifier matches the `MemberExpression > Identifier` selector until the - * `MemberExpression > Identifier` listener is called. - * - * To avoid this, the `Identifier` listener isn't called until traversal finishes and all - * ignored nodes are known. - */ - offsetListeners[selector] = node => listenerCallQueue.push({ listener, node }); - } - - // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. - const ignoredNodes = new Set(); - - /** - * Ignores a node - * @param {ASTNode} node The node to ignore - * @returns {void} - */ - function addToIgnoredNodes(node) { - ignoredNodes.add(node); - ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); - } - - const ignoredNodeListeners = options.ignoredNodes.reduce( - (listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), - {} - ); - - /* - * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation - * at the end. - * - * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears - * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, - * so those listeners wouldn't be called anyway. - */ - return Object.assign( - offsetListeners, - ignoredNodeListeners, - { - "*:exit"(node) { - - // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. - if (!KNOWN_NODES.has(node.type)) { - addToIgnoredNodes(node); - } - }, - "Program:exit"() { - - // If ignoreComments option is enabled, ignore all comment tokens. - if (options.ignoreComments) { - sourceCode.getAllComments() - .forEach(comment => offsets.ignoreToken(comment)); - } - - // Invoke the queued offset listeners for the nodes that aren't ignored. - for (let i = 0; i < listenerCallQueue.length; i++) { - const nodeInfo = listenerCallQueue[i]; - - if (!ignoredNodes.has(nodeInfo.node)) { - nodeInfo.listener(nodeInfo.node); - } - } - - // Update the offsets for ignored nodes to prevent their child tokens from being reported. - ignoredNodes.forEach(ignoreNode); - - addParensIndent(sourceCode.ast.tokens); - - /* - * Create a Map from (tokenOrComment) => (precedingToken). - * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. - */ - const precedingTokens = new WeakMap(); - - for (let i = 0; i < sourceCode.ast.comments.length; i++) { - const comment = sourceCode.ast.comments[i]; - - const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); - const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore; - - precedingTokens.set(comment, hasToken); - } - - for (let i = 1; i < sourceCode.lines.length + 1; i++) { - - if (!tokenInfo.firstTokensByLineNumber.has(i)) { - - // Don't check indentation on blank lines - continue; - } - - const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i); - - if (firstTokenOfLine.loc.start.line !== i) { - - // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. - continue; - } - - if (astUtils.isCommentToken(firstTokenOfLine)) { - const tokenBefore = precedingTokens.get(firstTokenOfLine); - const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0]; - const mayAlignWithBefore = tokenBefore && !hasBlankLinesBetween(tokenBefore, firstTokenOfLine); - const mayAlignWithAfter = tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); - - /* - * If a comment precedes a line that begins with a semicolon token, align to that token, i.e. - * - * let foo - * // comment - * ;(async () => {})() - */ - if (tokenAfter && astUtils.isSemicolonToken(tokenAfter) && !astUtils.isTokenOnSameLine(firstTokenOfLine, tokenAfter)) { - offsets.setDesiredOffset(firstTokenOfLine, tokenAfter, 0); - } - - // If a comment matches the expected indentation of the token immediately before or after, don't report it. - if ( - mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) || - mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter)) - ) { - continue; - } - } - - // If the token matches the expected indentation, don't report it. - if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) { - continue; - } - - // Otherwise, report the token/comment. - report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); - } - } - } - ); - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "indent", + url: "https://eslint.style/rules/js/indent", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent indentation", + recommended: false, + url: "https://eslint.org/docs/latest/rules/indent", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["tab"], + }, + { + type: "integer", + minimum: 0, + }, + ], + }, + { + type: "object", + properties: { + SwitchCase: { + type: "integer", + minimum: 0, + default: 0, + }, + VariableDeclarator: { + oneOf: [ + ELEMENT_LIST_SCHEMA, + { + type: "object", + properties: { + var: ELEMENT_LIST_SCHEMA, + let: ELEMENT_LIST_SCHEMA, + const: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ], + }, + outerIIFEBody: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["off"], + }, + ], + }, + MemberExpression: { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + enum: ["off"], + }, + ], + }, + FunctionDeclaration: { + type: "object", + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + FunctionExpression: { + type: "object", + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + StaticBlock: { + type: "object", + properties: { + body: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + CallExpression: { + type: "object", + properties: { + arguments: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ArrayExpression: ELEMENT_LIST_SCHEMA, + ObjectExpression: ELEMENT_LIST_SCHEMA, + ImportDeclaration: ELEMENT_LIST_SCHEMA, + flatTernaryExpressions: { + type: "boolean", + default: false, + }, + offsetTernaryExpressions: { + type: "boolean", + default: false, + }, + ignoredNodes: { + type: "array", + items: { + type: "string", + not: { + pattern: ":exit$", + }, + }, + }, + ignoreComments: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + wrongIndentation: + "Expected indentation of {{expected}} but found {{actual}}.", + }, + }, + + create(context) { + const DEFAULT_VARIABLE_INDENT = 1; + const DEFAULT_PARAMETER_INDENT = 1; + const DEFAULT_FUNCTION_BODY_INDENT = 1; + + let indentType = "space"; + let indentSize = 4; + const options = { + SwitchCase: 0, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT, + }, + outerIIFEBody: 1, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + StaticBlock: { + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT, + }, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoredNodes: [], + ignoreComments: false, + }; + + if (context.options.length) { + if (context.options[0] === "tab") { + indentSize = 1; + indentType = "tab"; + } else { + indentSize = context.options[0]; + indentType = "space"; + } + + if (context.options[1]) { + Object.assign(options, context.options[1]); + + if ( + typeof options.VariableDeclarator === "number" || + options.VariableDeclarator === "first" + ) { + options.VariableDeclarator = { + var: options.VariableDeclarator, + let: options.VariableDeclarator, + const: options.VariableDeclarator, + }; + } + } + } + + const sourceCode = context.sourceCode; + const tokenInfo = new TokenInfo(sourceCode); + const offsets = new OffsetStorage( + tokenInfo, + indentSize, + indentType === "space" ? " " : "\t", + sourceCode.text.length, + ); + const parameterParens = new WeakSet(); + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param {int} expectedAmount The expected amount of indentation characters for this line + * @param {int} actualSpaces The actual number of indentation spaces that were found on this line + * @param {int} actualTabs The actual number of indentation tabs that were found on this line + * @returns {string} An error message for this line + */ + function createErrorMessageData( + expectedAmount, + actualSpaces, + actualTabs, + ) { + const expectedStatement = `${expectedAmount} ${indentType}${expectedAmount === 1 ? "" : "s"}`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? "" : "s"}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? "" : "s"}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0) { + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = + indentType === "space" + ? actualSpaces + : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = + indentType === "tab" + ? actualTabs + : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = "0"; + } + return { + expected: expectedStatement, + actual: foundStatement, + }; + } + + /** + * Reports a given indent violation + * @param {Token} token Token violating the indent rule + * @param {string} neededIndent Expected indentation string + * @returns {void} + */ + function report(token, neededIndent) { + const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); + const numSpaces = actualIndent.filter(char => char === " ").length; + const numTabs = actualIndent.filter(char => char === "\t").length; + + context.report({ + node: token, + messageId: "wrongIndentation", + data: createErrorMessageData( + neededIndent.length, + numSpaces, + numTabs, + ), + loc: { + start: { line: token.loc.start.line, column: 0 }, + end: { + line: token.loc.start.line, + column: token.loc.start.column, + }, + }, + fix(fixer) { + const range = [ + token.range[0] - token.loc.start.column, + token.range[0], + ]; + const newText = neededIndent; + + return fixer.replaceTextRange(range, newText); + }, + }); + } + + /** + * Checks if a token's indentation is correct + * @param {Token} token Token to examine + * @param {string} desiredIndent Desired indentation of the string + * @returns {boolean} `true` if the token's indentation is correct + */ + function validateTokenIndent(token, desiredIndent) { + const indentation = tokenInfo.getTokenIndent(token); + + return ( + indentation === desiredIndent || + // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. + (indentation.includes(" ") && indentation.includes("\t")) + ); + } + + /** + * Check to see if the node is a file level IIFE + * @param {ASTNode} node The function node to check. + * @returns {boolean} True if the node is the outer IIFE + */ + function isOuterIIFE(node) { + /* + * Verify that the node is an IIFE + */ + if ( + !node.parent || + node.parent.type !== "CallExpression" || + node.parent.callee !== node + ) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIFE is outer. + * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. + * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. + */ + let statement = node.parent && node.parent.parent; + + while ( + (statement.type === "UnaryExpression" && + ["!", "~", "+", "-"].includes(statement.operator)) || + statement.type === "AssignmentExpression" || + statement.type === "LogicalExpression" || + statement.type === "SequenceExpression" || + statement.type === "VariableDeclarator" + ) { + statement = statement.parent; + } + + return ( + (statement.type === "ExpressionStatement" || + statement.type === "VariableDeclaration") && + statement.parent.type === "Program" + ); + } + + /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param {string} string The string to check + * @returns {number} The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(string) { + const trailingWhitespace = string.match(/\s*$/u)[0]; + const linebreakMatches = trailingWhitespace.match( + astUtils.createGlobalLinebreakMatcher(), + ); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** + * Check indentation for lists of elements (arrays, objects, function params) + * @param {ASTNode[]} elements List of elements that should be offset + * @param {Token} startToken The start token of the list that element should be aligned against, e.g. '[' + * @param {Token} endToken The end token of the list, e.g. ']' + * @param {number|string} offset The amount that the elements should be offset + * @returns {void} + */ + function addElementListIndent(elements, startToken, endToken, offset) { + /** + * Gets the first token of a given element, including surrounding parentheses. + * @param {ASTNode} element A node in the `elements` list + * @returns {Token} The first token of this element + */ + function getFirstToken(element) { + let token = sourceCode.getTokenBefore(element); + + while ( + astUtils.isOpeningParenToken(token) && + token !== startToken + ) { + token = sourceCode.getTokenBefore(token); + } + return sourceCode.getTokenAfter(token); + } + + // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) + offsets.setDesiredOffsets( + [startToken.range[1], endToken.range[0]], + startToken, + typeof offset === "number" ? offset : 1, + ); + offsets.setDesiredOffset(endToken, startToken, 0); + + // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. + if (offset === "first" && elements.length && !elements[0]) { + return; + } + elements.forEach((element, index) => { + if (!element) { + // Skip holes in arrays + return; + } + if (offset === "off") { + // Ignore the first token of every element if the "off" option is used + offsets.ignoreToken(getFirstToken(element)); + } + + // Offset the following elements correctly relative to the first element + if (index === 0) { + return; + } + if ( + offset === "first" && + tokenInfo.isFirstTokenOfLine(getFirstToken(element)) + ) { + offsets.matchOffsetOf( + getFirstToken(elements[0]), + getFirstToken(element), + ); + } else { + const previousElement = elements[index - 1]; + const firstTokenOfPreviousElement = + previousElement && getFirstToken(previousElement); + const previousElementLastToken = + previousElement && + sourceCode.getLastToken(previousElement); + + if ( + previousElement && + previousElementLastToken.loc.end.line - + countTrailingLinebreaks( + previousElementLastToken.value, + ) > + startToken.loc.end.line + ) { + offsets.setDesiredOffsets( + [previousElement.range[1], element.range[1]], + firstTokenOfPreviousElement, + 0, + ); + } + } + }); + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + * @param {ASTNode} node node to examine + * @returns {void} + */ + function addBlocklessNodeIndent(node) { + if (node.type !== "BlockStatement") { + const lastParentToken = sourceCode.getTokenBefore( + node, + astUtils.isNotOpeningParenToken, + ); + + let firstBodyToken = sourceCode.getFirstToken(node); + let lastBodyToken = sourceCode.getLastToken(node); + + while ( + astUtils.isOpeningParenToken( + sourceCode.getTokenBefore(firstBodyToken), + ) && + astUtils.isClosingParenToken( + sourceCode.getTokenAfter(lastBodyToken), + ) + ) { + firstBodyToken = sourceCode.getTokenBefore(firstBodyToken); + lastBodyToken = sourceCode.getTokenAfter(lastBodyToken); + } + + offsets.setDesiredOffsets( + [firstBodyToken.range[0], lastBodyToken.range[1]], + lastParentToken, + 1, + ); + } + } + + /** + * Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`) + * @param {ASTNode} node A CallExpression or NewExpression node + * @returns {void} + */ + function addFunctionCallIndent(node) { + let openingParen; + + if (node.arguments.length) { + openingParen = sourceCode.getFirstTokenBetween( + node.callee, + node.arguments[0], + astUtils.isOpeningParenToken, + ); + } else { + openingParen = sourceCode.getLastToken(node, 1); + } + const closingParen = sourceCode.getLastToken(node); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + + /* + * If `?.` token exists, set desired offset for that. + * This logic is copied from `MemberExpression`'s. + */ + if (node.optional) { + const dotToken = sourceCode.getTokenAfter( + node.callee, + astUtils.isQuestionDotToken, + ); + const calleeParenCount = sourceCode.getTokensBetween( + node.callee, + dotToken, + { filter: astUtils.isClosingParenToken }, + ).length; + const firstTokenOfCallee = calleeParenCount + ? sourceCode.getTokenBefore(node.callee, { + skip: calleeParenCount - 1, + }) + : sourceCode.getFirstToken(node.callee); + const lastTokenOfCallee = sourceCode.getTokenBefore(dotToken); + const offsetBase = + lastTokenOfCallee.loc.end.line === + openingParen.loc.start.line + ? lastTokenOfCallee + : firstTokenOfCallee; + + offsets.setDesiredOffset(dotToken, offsetBase, 1); + } + + const offsetAfterToken = + node.callee.type === "TaggedTemplateExpression" + ? sourceCode.getFirstToken(node.callee.quasi) + : openingParen; + const offsetToken = sourceCode.getTokenBefore(offsetAfterToken); + + offsets.setDesiredOffset(openingParen, offsetToken, 0); + + addElementListIndent( + node.arguments, + openingParen, + closingParen, + options.CallExpression.arguments, + ); + } + + /** + * Checks the indentation of parenthesized values, given a list of tokens in a program + * @param {Token[]} tokens A list of tokens + * @returns {void} + */ + function addParensIndent(tokens) { + const parenStack = []; + const parenPairs = []; + + for (let i = 0; i < tokens.length; i++) { + const nextToken = tokens[i]; + + if (astUtils.isOpeningParenToken(nextToken)) { + parenStack.push(nextToken); + } else if (astUtils.isClosingParenToken(nextToken)) { + parenPairs.push({ + left: parenStack.pop(), + right: nextToken, + }); + } + } + + for (let i = parenPairs.length - 1; i >= 0; i--) { + const leftParen = parenPairs[i].left; + const rightParen = parenPairs[i].right; + + // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. + if ( + !parameterParens.has(leftParen) && + !parameterParens.has(rightParen) + ) { + const parenthesizedTokens = new Set( + sourceCode.getTokensBetween(leftParen, rightParen), + ); + + parenthesizedTokens.forEach(token => { + if ( + !parenthesizedTokens.has( + offsets.getFirstDependency(token), + ) + ) { + offsets.setDesiredOffset(token, leftParen, 1); + } + }); + } + + offsets.setDesiredOffset(rightParen, leftParen, 0); + } + } + + /** + * Ignore all tokens within an unknown node whose offset do not depend + * on another token's offset within the unknown node + * @param {ASTNode} node Unknown Node + * @returns {void} + */ + function ignoreNode(node) { + const unknownNodeTokens = new Set( + sourceCode.getTokens(node, { includeComments: true }), + ); + + unknownNodeTokens.forEach(token => { + if (!unknownNodeTokens.has(offsets.getFirstDependency(token))) { + const firstTokenOfLine = + tokenInfo.getFirstTokenOfLine(token); + + if (token === firstTokenOfLine) { + offsets.ignoreToken(token); + } else { + offsets.setDesiredOffset(token, firstTokenOfLine, 0); + } + } + }); + } + + /** + * Check whether the given token is on the first line of a statement. + * @param {Token} token The token to check. + * @param {ASTNode} leafNode The expression node that the token belongs directly. + * @returns {boolean} `true` if the token is on the first line of a statement. + */ + function isOnFirstLineOfStatement(token, leafNode) { + let node = leafNode; + + while ( + node.parent && + !node.parent.type.endsWith("Statement") && + !node.parent.type.endsWith("Declaration") + ) { + node = node.parent; + } + node = node.parent; + + return !node || node.loc.start.line === token.loc.start.line; + } + + /** + * Check whether there are any blank (whitespace-only) lines between + * two tokens on separate lines. + * @param {Token} firstToken The first token. + * @param {Token} secondToken The second token. + * @returns {boolean} `true` if the tokens are on separate lines and + * there exists a blank line between them, `false` otherwise. + */ + function hasBlankLinesBetween(firstToken, secondToken) { + const firstTokenLine = firstToken.loc.end.line; + const secondTokenLine = secondToken.loc.start.line; + + if ( + firstTokenLine === secondTokenLine || + firstTokenLine === secondTokenLine - 1 + ) { + return false; + } + + for ( + let line = firstTokenLine + 1; + line < secondTokenLine; + ++line + ) { + if (!tokenInfo.firstTokensByLineNumber.has(line)) { + return true; + } + } + + return false; + } + + const ignoredNodeFirstTokens = new Set(); + + const baseOffsetListeners = { + "ArrayExpression, ArrayPattern"(node) { + const openingBracket = sourceCode.getFirstToken(node); + const closingBracket = sourceCode.getTokenAfter( + [...node.elements].reverse().find(_ => _) || openingBracket, + astUtils.isClosingBracketToken, + ); + + addElementListIndent( + node.elements, + openingBracket, + closingBracket, + options.ArrayExpression, + ); + }, + + "ObjectExpression, ObjectPattern"(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getTokenAfter( + node.properties.length + ? node.properties.at(-1) + : openingCurly, + astUtils.isClosingBraceToken, + ); + + addElementListIndent( + node.properties, + openingCurly, + closingCurly, + options.ObjectExpression, + ); + }, + + ArrowFunctionExpression(node) { + const maybeOpeningParen = sourceCode.getFirstToken(node, { + skip: node.async ? 1 : 0, + }); + + if (astUtils.isOpeningParenToken(maybeOpeningParen)) { + const openingParen = maybeOpeningParen; + const closingParen = sourceCode.getTokenBefore( + node.body, + astUtils.isClosingParenToken, + ); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options.FunctionExpression.parameters, + ); + } + + addBlocklessNodeIndent(node.body); + }, + + AssignmentExpression(node) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + + offsets.setDesiredOffsets( + [operator.range[0], node.range[1]], + sourceCode.getLastToken(node.left), + 1, + ); + offsets.ignoreToken(operator); + offsets.ignoreToken(sourceCode.getTokenAfter(operator)); + }, + + "BinaryExpression, LogicalExpression"(node) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + + /* + * For backwards compatibility, don't check BinaryExpression indents, e.g. + * var foo = bar && + * baz; + */ + + const tokenAfterOperator = sourceCode.getTokenAfter(operator); + + offsets.ignoreToken(operator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffset(tokenAfterOperator, operator, 0); + }, + + "BlockStatement, ClassBody"(node) { + let blockIndentLevel; + + if (node.parent && isOuterIIFE(node.parent)) { + blockIndentLevel = options.outerIIFEBody; + } else if ( + node.parent && + (node.parent.type === "FunctionExpression" || + node.parent.type === "ArrowFunctionExpression") + ) { + blockIndentLevel = options.FunctionExpression.body; + } else if ( + node.parent && + node.parent.type === "FunctionDeclaration" + ) { + blockIndentLevel = options.FunctionDeclaration.body; + } else { + blockIndentLevel = 1; + } + + /* + * For blocks that aren't lone statements, ensure that the opening curly brace + * is aligned with the parent. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) { + offsets.setDesiredOffset( + sourceCode.getFirstToken(node), + sourceCode.getFirstToken(node.parent), + 0, + ); + } + + addElementListIndent( + node.body, + sourceCode.getFirstToken(node), + sourceCode.getLastToken(node), + blockIndentLevel, + ); + }, + + CallExpression: addFunctionCallIndent, + + "ClassDeclaration[superClass], ClassExpression[superClass]"(node) { + const classToken = sourceCode.getFirstToken(node); + const extendsToken = sourceCode.getTokenBefore( + node.superClass, + astUtils.isNotOpeningParenToken, + ); + + offsets.setDesiredOffsets( + [extendsToken.range[0], node.body.range[0]], + classToken, + 1, + ); + }, + + ConditionalExpression(node) { + const firstToken = sourceCode.getFirstToken(node); + + // `flatTernaryExpressions` option is for the following style: + // var a = + // foo > 0 ? bar : + // foo < 0 ? baz : + // /*else*/ qiz ; + if ( + !options.flatTernaryExpressions || + !astUtils.isTokenOnSameLine(node.test, node.consequent) || + isOnFirstLineOfStatement(firstToken, node) + ) { + const questionMarkToken = sourceCode.getFirstTokenBetween( + node.test, + node.consequent, + token => + token.type === "Punctuator" && token.value === "?", + ); + const colonToken = sourceCode.getFirstTokenBetween( + node.consequent, + node.alternate, + token => + token.type === "Punctuator" && token.value === ":", + ); + + const firstConsequentToken = + sourceCode.getTokenAfter(questionMarkToken); + const lastConsequentToken = + sourceCode.getTokenBefore(colonToken); + const firstAlternateToken = + sourceCode.getTokenAfter(colonToken); + + offsets.setDesiredOffset(questionMarkToken, firstToken, 1); + offsets.setDesiredOffset(colonToken, firstToken, 1); + + offsets.setDesiredOffset( + firstConsequentToken, + firstToken, + firstConsequentToken.type === "Punctuator" && + options.offsetTernaryExpressions + ? 2 + : 1, + ); + + /* + * The alternate and the consequent should usually have the same indentation. + * If they share part of a line, align the alternate against the first token of the consequent. + * This allows the alternate to be indented correctly in cases like this: + * foo ? ( + * bar + * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` + * baz // as a result, `baz` is offset by 1 rather than 2 + * ) + */ + if ( + lastConsequentToken.loc.end.line === + firstAlternateToken.loc.start.line + ) { + offsets.setDesiredOffset( + firstAlternateToken, + firstConsequentToken, + 0, + ); + } else { + /** + * If the alternate and consequent do not share part of a line, offset the alternate from the first + * token of the conditional expression. For example: + * foo ? bar + * : baz + * + * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up + * having no expected indentation. + */ + offsets.setDesiredOffset( + firstAlternateToken, + firstToken, + firstAlternateToken.type === "Punctuator" && + options.offsetTernaryExpressions + ? 2 + : 1, + ); + } + } + }, + + "DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement, WithStatement": + node => addBlocklessNodeIndent(node.body), + + ExportNamedDeclaration(node) { + if (node.declaration === null) { + const closingCurly = sourceCode.getLastToken( + node, + astUtils.isClosingBraceToken, + ); + + // Indent the specifiers in `export {foo, bar, baz}` + addElementListIndent( + node.specifiers, + sourceCode.getFirstToken(node, { skip: 1 }), + closingCurly, + 1, + ); + + if (node.source) { + // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` + offsets.setDesiredOffsets( + [closingCurly.range[1], node.range[1]], + sourceCode.getFirstToken(node), + 1, + ); + } + } + }, + + ForStatement(node) { + const forOpeningParen = sourceCode.getFirstToken(node, 1); + + if (node.init) { + offsets.setDesiredOffsets( + node.init.range, + forOpeningParen, + 1, + ); + } + if (node.test) { + offsets.setDesiredOffsets( + node.test.range, + forOpeningParen, + 1, + ); + } + if (node.update) { + offsets.setDesiredOffsets( + node.update.range, + forOpeningParen, + 1, + ); + } + addBlocklessNodeIndent(node.body); + }, + + "FunctionDeclaration, FunctionExpression"(node) { + const closingParen = sourceCode.getTokenBefore(node.body); + const openingParen = sourceCode.getTokenBefore( + node.params.length ? node.params[0] : closingParen, + ); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options[node.type].parameters, + ); + }, + + IfStatement(node) { + addBlocklessNodeIndent(node.consequent); + if (node.alternate) { + addBlocklessNodeIndent(node.alternate); + } + }, + + /* + * For blockless nodes with semicolon-first style, don't indent the semicolon. + * e.g. + * if (foo) + * bar() + * ; [1, 2, 3].map(foo) + * + * Traversal into the node sets indentation of the semicolon, so we need to override it on exit. + */ + ":matches(DoWhileStatement, ForStatement, ForInStatement, ForOfStatement, IfStatement, WhileStatement, WithStatement):exit"( + node, + ) { + let nodesToCheck; + + if (node.type === "IfStatement") { + nodesToCheck = [node.consequent]; + if (node.alternate) { + nodesToCheck.push(node.alternate); + } + } else { + nodesToCheck = [node.body]; + } + + for (const nodeToCheck of nodesToCheck) { + const lastToken = sourceCode.getLastToken(nodeToCheck); + + if (astUtils.isSemicolonToken(lastToken)) { + const tokenBeforeLast = + sourceCode.getTokenBefore(lastToken); + const tokenAfterLast = + sourceCode.getTokenAfter(lastToken); + + // override indentation of `;` only if its line looks like a semicolon-first style line + if ( + !astUtils.isTokenOnSameLine( + tokenBeforeLast, + lastToken, + ) && + tokenAfterLast && + astUtils.isTokenOnSameLine( + lastToken, + tokenAfterLast, + ) + ) { + offsets.setDesiredOffset( + lastToken, + sourceCode.getFirstToken(node), + 0, + ); + } + } + } + }, + + ImportDeclaration(node) { + if ( + node.specifiers.some( + specifier => specifier.type === "ImportSpecifier", + ) + ) { + const openingCurly = sourceCode.getFirstToken( + node, + astUtils.isOpeningBraceToken, + ); + const closingCurly = sourceCode.getLastToken( + node, + astUtils.isClosingBraceToken, + ); + + addElementListIndent( + node.specifiers.filter( + specifier => specifier.type === "ImportSpecifier", + ), + openingCurly, + closingCurly, + options.ImportDeclaration, + ); + } + + const fromToken = sourceCode.getLastToken( + node, + token => + token.type === "Identifier" && token.value === "from", + ); + const sourceToken = sourceCode.getLastToken( + node, + token => token.type === "String", + ); + const semiToken = sourceCode.getLastToken( + node, + token => token.type === "Punctuator" && token.value === ";", + ); + + if (fromToken) { + const end = + semiToken && semiToken.range[1] === sourceToken.range[1] + ? node.range[1] + : sourceToken.range[1]; + + offsets.setDesiredOffsets( + [fromToken.range[0], end], + sourceCode.getFirstToken(node), + 1, + ); + } + }, + + ImportExpression(node) { + const openingParen = sourceCode.getFirstToken(node, 1); + const closingParen = sourceCode.getLastToken(node); + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + offsets.setDesiredOffset( + openingParen, + sourceCode.getTokenBefore(openingParen), + 0, + ); + + addElementListIndent( + [node.source], + openingParen, + closingParen, + options.CallExpression.arguments, + ); + }, + + "MemberExpression, JSXMemberExpression, MetaProperty"(node) { + const object = + node.type === "MetaProperty" ? node.meta : node.object; + const firstNonObjectToken = sourceCode.getFirstTokenBetween( + object, + node.property, + astUtils.isNotClosingParenToken, + ); + const secondNonObjectToken = + sourceCode.getTokenAfter(firstNonObjectToken); + + const objectParenCount = sourceCode.getTokensBetween( + object, + node.property, + { filter: astUtils.isClosingParenToken }, + ).length; + const firstObjectToken = objectParenCount + ? sourceCode.getTokenBefore(object, { + skip: objectParenCount - 1, + }) + : sourceCode.getFirstToken(object); + const lastObjectToken = + sourceCode.getTokenBefore(firstNonObjectToken); + const firstPropertyToken = node.computed + ? firstNonObjectToken + : secondNonObjectToken; + + if (node.computed) { + // For computed MemberExpressions, match the closing bracket with the opening bracket. + offsets.setDesiredOffset( + sourceCode.getLastToken(node), + firstNonObjectToken, + 0, + ); + offsets.setDesiredOffsets( + node.property.range, + firstNonObjectToken, + 1, + ); + } + + /* + * If the object ends on the same line that the property starts, match against the last token + * of the object, to ensure that the MemberExpression is not indented. + * + * Otherwise, match against the first token of the object, e.g. + * foo + * .bar + * .baz // <-- offset by 1 from `foo` + */ + const offsetBase = + lastObjectToken.loc.end.line === + firstPropertyToken.loc.start.line + ? lastObjectToken + : firstObjectToken; + + if (typeof options.MemberExpression === "number") { + // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. + offsets.setDesiredOffset( + firstNonObjectToken, + offsetBase, + options.MemberExpression, + ); + + /* + * For computed MemberExpressions, match the first token of the property against the opening bracket. + * Otherwise, match the first token of the property against the object. + */ + offsets.setDesiredOffset( + secondNonObjectToken, + node.computed ? firstNonObjectToken : offsetBase, + options.MemberExpression, + ); + } else { + // If the MemberExpression option is off, ignore the dot and the first token of the property. + offsets.ignoreToken(firstNonObjectToken); + offsets.ignoreToken(secondNonObjectToken); + + // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. + offsets.setDesiredOffset( + firstNonObjectToken, + offsetBase, + 0, + ); + offsets.setDesiredOffset( + secondNonObjectToken, + firstNonObjectToken, + 0, + ); + } + }, + + NewExpression(node) { + // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` + if ( + node.arguments.length > 0 || + (astUtils.isClosingParenToken( + sourceCode.getLastToken(node), + ) && + astUtils.isOpeningParenToken( + sourceCode.getLastToken(node, 1), + )) + ) { + addFunctionCallIndent(node); + } + }, + + Property(node) { + if (!node.shorthand && !node.method && node.kind === "init") { + const colon = sourceCode.getFirstTokenBetween( + node.key, + node.value, + astUtils.isColonToken, + ); + + offsets.ignoreToken(sourceCode.getTokenAfter(colon)); + } + }, + + PropertyDefinition(node) { + const firstToken = sourceCode.getFirstToken(node); + const maybeSemicolonToken = sourceCode.getLastToken(node); + let keyLastToken; + + // Indent key. + if (node.computed) { + const bracketTokenL = sourceCode.getTokenBefore( + node.key, + astUtils.isOpeningBracketToken, + ); + const bracketTokenR = (keyLastToken = + sourceCode.getTokenAfter( + node.key, + astUtils.isClosingBracketToken, + )); + const keyRange = [ + bracketTokenL.range[1], + bracketTokenR.range[0], + ]; + + if (bracketTokenL !== firstToken) { + offsets.setDesiredOffset(bracketTokenL, firstToken, 0); + } + offsets.setDesiredOffsets(keyRange, bracketTokenL, 1); + offsets.setDesiredOffset(bracketTokenR, bracketTokenL, 0); + } else { + const idToken = (keyLastToken = sourceCode.getFirstToken( + node.key, + )); + + if (idToken !== firstToken) { + offsets.setDesiredOffset(idToken, firstToken, 1); + } + } + + // Indent initializer. + if (node.value) { + const eqToken = sourceCode.getTokenBefore( + node.value, + astUtils.isEqToken, + ); + const valueToken = sourceCode.getTokenAfter(eqToken); + + offsets.setDesiredOffset(eqToken, keyLastToken, 1); + offsets.setDesiredOffset(valueToken, eqToken, 1); + if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset( + maybeSemicolonToken, + eqToken, + 1, + ); + } + } else if (astUtils.isSemicolonToken(maybeSemicolonToken)) { + offsets.setDesiredOffset( + maybeSemicolonToken, + keyLastToken, + 1, + ); + } + }, + + StaticBlock(node) { + const openingCurly = sourceCode.getFirstToken(node, { + skip: 1, + }); // skip the `static` token + const closingCurly = sourceCode.getLastToken(node); + + addElementListIndent( + node.body, + openingCurly, + closingCurly, + options.StaticBlock.body, + ); + }, + + SwitchStatement(node) { + const openingCurly = sourceCode.getTokenAfter( + node.discriminant, + astUtils.isOpeningBraceToken, + ); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + options.SwitchCase, + ); + + if (node.cases.length) { + sourceCode + .getTokensBetween(node.cases.at(-1), closingCurly, { + includeComments: true, + filter: astUtils.isCommentToken, + }) + .forEach(token => offsets.ignoreToken(token)); + } + }, + + SwitchCase(node) { + if ( + !( + node.consequent.length === 1 && + node.consequent[0].type === "BlockStatement" + ) + ) { + const caseKeyword = sourceCode.getFirstToken(node); + const tokenAfterCurrentCase = + sourceCode.getTokenAfter(node); + + offsets.setDesiredOffsets( + [caseKeyword.range[1], tokenAfterCurrentCase.range[0]], + caseKeyword, + 1, + ); + } + }, + + TemplateLiteral(node) { + node.expressions.forEach((expression, index) => { + const previousQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + const tokenToAlignFrom = + previousQuasi.loc.start.line === + previousQuasi.loc.end.line + ? sourceCode.getFirstToken(previousQuasi) + : null; + + offsets.setDesiredOffsets( + [previousQuasi.range[1], nextQuasi.range[0]], + tokenToAlignFrom, + 1, + ); + offsets.setDesiredOffset( + sourceCode.getFirstToken(nextQuasi), + tokenToAlignFrom, + 0, + ); + }); + }, + + VariableDeclaration(node) { + let variableIndent = Object.hasOwn( + options.VariableDeclarator, + node.kind, + ) + ? options.VariableDeclarator[node.kind] + : DEFAULT_VARIABLE_INDENT; + + const firstToken = sourceCode.getFirstToken(node), + lastToken = sourceCode.getLastToken(node); + + if (options.VariableDeclarator[node.kind] === "first") { + if (node.declarations.length > 1) { + addElementListIndent( + node.declarations, + firstToken, + lastToken, + "first", + ); + return; + } + + variableIndent = DEFAULT_VARIABLE_INDENT; + } + + if ( + node.declarations.at(-1).loc.start.line > + node.loc.start.line + ) { + /* + * VariableDeclarator indentation is a bit different from other forms of indentation, in that the + * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, + * the following indentations are correct: + * + * var foo = { + * ok: true + * }; + * + * var foo = { + * ok: true, + * }, + * bar = 1; + * + * Account for when exiting the AST (after indentations have already been set for the nodes in + * the declaration) by manually increasing the indentation level of the tokens in this declarator + * on the same line as the start of the declaration, provided that there are declarators that + * follow this one. + */ + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent, + true, + ); + } else { + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent, + ); + } + + if (astUtils.isSemicolonToken(lastToken)) { + offsets.ignoreToken(lastToken); + } + }, + + VariableDeclarator(node) { + if (node.init) { + const equalOperator = sourceCode.getTokenBefore( + node.init, + astUtils.isNotOpeningParenToken, + ); + const tokenAfterOperator = + sourceCode.getTokenAfter(equalOperator); + + offsets.ignoreToken(equalOperator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffsets( + [tokenAfterOperator.range[0], node.range[1]], + equalOperator, + 1, + ); + offsets.setDesiredOffset( + equalOperator, + sourceCode.getLastToken(node.id), + 0, + ); + } + }, + + "JSXAttribute[value]"(node) { + const equalsToken = sourceCode.getFirstTokenBetween( + node.name, + node.value, + token => token.type === "Punctuator" && token.value === "=", + ); + + offsets.setDesiredOffsets( + [equalsToken.range[0], node.value.range[1]], + sourceCode.getFirstToken(node.name), + 1, + ); + }, + + JSXElement(node) { + if (node.closingElement) { + addElementListIndent( + node.children, + sourceCode.getFirstToken(node.openingElement), + sourceCode.getFirstToken(node.closingElement), + 1, + ); + } + }, + + JSXOpeningElement(node) { + const firstToken = sourceCode.getFirstToken(node); + let closingToken; + + if (node.selfClosing) { + closingToken = sourceCode.getLastToken(node, { skip: 1 }); + offsets.setDesiredOffset( + sourceCode.getLastToken(node), + closingToken, + 0, + ); + } else { + closingToken = sourceCode.getLastToken(node); + } + offsets.setDesiredOffsets( + node.name.range, + sourceCode.getFirstToken(node), + ); + addElementListIndent( + node.attributes, + firstToken, + closingToken, + 1, + ); + }, + + JSXClosingElement(node) { + const firstToken = sourceCode.getFirstToken(node); + + offsets.setDesiredOffsets(node.name.range, firstToken, 1); + }, + + JSXFragment(node) { + const firstOpeningToken = sourceCode.getFirstToken( + node.openingFragment, + ); + const firstClosingToken = sourceCode.getFirstToken( + node.closingFragment, + ); + + addElementListIndent( + node.children, + firstOpeningToken, + firstClosingToken, + 1, + ); + }, + + JSXOpeningFragment(node) { + const firstToken = sourceCode.getFirstToken(node); + const closingToken = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets(node.range, firstToken, 1); + offsets.matchOffsetOf(firstToken, closingToken); + }, + + JSXClosingFragment(node) { + const firstToken = sourceCode.getFirstToken(node); + const slashToken = sourceCode.getLastToken(node, { skip: 1 }); + const closingToken = sourceCode.getLastToken(node); + const tokenToMatch = astUtils.isTokenOnSameLine( + slashToken, + closingToken, + ) + ? slashToken + : closingToken; + + offsets.setDesiredOffsets(node.range, firstToken, 1); + offsets.matchOffsetOf(firstToken, tokenToMatch); + }, + + JSXExpressionContainer(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1, + ); + }, + + JSXSpreadAttribute(node) { + const openingCurly = sourceCode.getFirstToken(node); + const closingCurly = sourceCode.getLastToken(node); + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1, + ); + }, + + "*"(node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } + }, + }; + + const listenerCallQueue = []; + + /* + * To ignore the indentation of a node: + * 1. Don't call the node's listener when entering it (if it has a listener) + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + */ + const offsetListeners = {}; + + for (const [selector, listener] of Object.entries( + baseOffsetListeners, + )) { + /* + * Offset listener calls are deferred until traversal is finished, and are called as + * part of the final `Program:exit` listener. This is necessary because a node might + * be matched by multiple selectors. + * + * Example: Suppose there is an offset listener for `Identifier`, and the user has + * specified in configuration that `MemberExpression > Identifier` should be ignored. + * Due to selector specificity rules, the `Identifier` listener will get called first. However, + * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener + * should not have been called at all. Without doing extra selector matching, we don't know + * whether the Identifier matches the `MemberExpression > Identifier` selector until the + * `MemberExpression > Identifier` listener is called. + * + * To avoid this, the `Identifier` listener isn't called until traversal finishes and all + * ignored nodes are known. + */ + offsetListeners[selector] = node => + listenerCallQueue.push({ listener, node }); + } + + // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. + const ignoredNodes = new Set(); + + /** + * Ignores a node + * @param {ASTNode} node The node to ignore + * @returns {void} + */ + function addToIgnoredNodes(node) { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } + + const ignoredNodeListeners = options.ignoredNodes.reduce( + (listeners, ignoredSelector) => + Object.assign(listeners, { + [ignoredSelector]: addToIgnoredNodes, + }), + {}, + ); + + /* + * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation + * at the end. + * + * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears + * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, + * so those listeners wouldn't be called anyway. + */ + return Object.assign(offsetListeners, ignoredNodeListeners, { + "*:exit"(node) { + // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. + if (!KNOWN_NODES.has(node.type)) { + addToIgnoredNodes(node); + } + }, + "Program:exit"() { + // If ignoreComments option is enabled, ignore all comment tokens. + if (options.ignoreComments) { + sourceCode + .getAllComments() + .forEach(comment => offsets.ignoreToken(comment)); + } + + // Invoke the queued offset listeners for the nodes that aren't ignored. + for (let i = 0; i < listenerCallQueue.length; i++) { + const nodeInfo = listenerCallQueue[i]; + + if (!ignoredNodes.has(nodeInfo.node)) { + nodeInfo.listener(nodeInfo.node); + } + } + + // Update the offsets for ignored nodes to prevent their child tokens from being reported. + ignoredNodes.forEach(ignoreNode); + + addParensIndent(sourceCode.ast.tokens); + + /* + * Create a Map from (tokenOrComment) => (precedingToken). + * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. + */ + const precedingTokens = new WeakMap(); + + for (let i = 0; i < sourceCode.ast.comments.length; i++) { + const comment = sourceCode.ast.comments[i]; + + const tokenOrCommentBefore = sourceCode.getTokenBefore( + comment, + { includeComments: true }, + ); + const hasToken = precedingTokens.has(tokenOrCommentBefore) + ? precedingTokens.get(tokenOrCommentBefore) + : tokenOrCommentBefore; + + precedingTokens.set(comment, hasToken); + } + + for (let i = 1; i < sourceCode.lines.length + 1; i++) { + if (!tokenInfo.firstTokensByLineNumber.has(i)) { + // Don't check indentation on blank lines + continue; + } + + const firstTokenOfLine = + tokenInfo.firstTokensByLineNumber.get(i); + + if (firstTokenOfLine.loc.start.line !== i) { + // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. + continue; + } + + if (astUtils.isCommentToken(firstTokenOfLine)) { + const tokenBefore = + precedingTokens.get(firstTokenOfLine); + const tokenAfter = tokenBefore + ? sourceCode.getTokenAfter(tokenBefore) + : sourceCode.ast.tokens[0]; + const mayAlignWithBefore = + tokenBefore && + !hasBlankLinesBetween( + tokenBefore, + firstTokenOfLine, + ); + const mayAlignWithAfter = + tokenAfter && + !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); + + /* + * If a comment precedes a line that begins with a semicolon token, align to that token, i.e. + * + * let foo + * // comment + * ;(async () => {})() + */ + if ( + tokenAfter && + astUtils.isSemicolonToken(tokenAfter) && + !astUtils.isTokenOnSameLine( + firstTokenOfLine, + tokenAfter, + ) + ) { + offsets.setDesiredOffset( + firstTokenOfLine, + tokenAfter, + 0, + ); + } + + // If a comment matches the expected indentation of the token immediately before or after, don't report it. + if ( + (mayAlignWithBefore && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenBefore), + )) || + (mayAlignWithAfter && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenAfter), + )) + ) { + continue; + } + } + + // If the token matches the expected indentation, don't report it. + if ( + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(firstTokenOfLine), + ) + ) { + continue; + } + + // Otherwise, report the token/comment. + report( + firstTokenOfLine, + offsets.getDesiredIndent(firstTokenOfLine), + ); + } + }, + }); + }, }; diff --git a/lib/rules/index.js b/lib/rules/index.js index 5ff7b6f5dde2..8b5064e78241 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -11,295 +11,320 @@ const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map"); /** @type {Map} */ -module.exports = new LazyLoadingRuleMap(Object.entries({ - "accessor-pairs": () => require("./accessor-pairs"), - "array-bracket-newline": () => require("./array-bracket-newline"), - "array-bracket-spacing": () => require("./array-bracket-spacing"), - "array-callback-return": () => require("./array-callback-return"), - "array-element-newline": () => require("./array-element-newline"), - "arrow-body-style": () => require("./arrow-body-style"), - "arrow-parens": () => require("./arrow-parens"), - "arrow-spacing": () => require("./arrow-spacing"), - "block-scoped-var": () => require("./block-scoped-var"), - "block-spacing": () => require("./block-spacing"), - "brace-style": () => require("./brace-style"), - "callback-return": () => require("./callback-return"), - camelcase: () => require("./camelcase"), - "capitalized-comments": () => require("./capitalized-comments"), - "class-methods-use-this": () => require("./class-methods-use-this"), - "comma-dangle": () => require("./comma-dangle"), - "comma-spacing": () => require("./comma-spacing"), - "comma-style": () => require("./comma-style"), - complexity: () => require("./complexity"), - "computed-property-spacing": () => require("./computed-property-spacing"), - "consistent-return": () => require("./consistent-return"), - "consistent-this": () => require("./consistent-this"), - "constructor-super": () => require("./constructor-super"), - curly: () => require("./curly"), - "default-case": () => require("./default-case"), - "default-case-last": () => require("./default-case-last"), - "default-param-last": () => require("./default-param-last"), - "dot-location": () => require("./dot-location"), - "dot-notation": () => require("./dot-notation"), - "eol-last": () => require("./eol-last"), - eqeqeq: () => require("./eqeqeq"), - "for-direction": () => require("./for-direction"), - "func-call-spacing": () => require("./func-call-spacing"), - "func-name-matching": () => require("./func-name-matching"), - "func-names": () => require("./func-names"), - "func-style": () => require("./func-style"), - "function-call-argument-newline": () => require("./function-call-argument-newline"), - "function-paren-newline": () => require("./function-paren-newline"), - "generator-star-spacing": () => require("./generator-star-spacing"), - "getter-return": () => require("./getter-return"), - "global-require": () => require("./global-require"), - "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"), - "guard-for-in": () => require("./guard-for-in"), - "handle-callback-err": () => require("./handle-callback-err"), - "id-blacklist": () => require("./id-blacklist"), - "id-denylist": () => require("./id-denylist"), - "id-length": () => require("./id-length"), - "id-match": () => require("./id-match"), - "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"), - indent: () => require("./indent"), - "indent-legacy": () => require("./indent-legacy"), - "init-declarations": () => require("./init-declarations"), - "jsx-quotes": () => require("./jsx-quotes"), - "key-spacing": () => require("./key-spacing"), - "keyword-spacing": () => require("./keyword-spacing"), - "line-comment-position": () => require("./line-comment-position"), - "linebreak-style": () => require("./linebreak-style"), - "lines-around-comment": () => require("./lines-around-comment"), - "lines-around-directive": () => require("./lines-around-directive"), - "lines-between-class-members": () => require("./lines-between-class-members"), - "logical-assignment-operators": () => require("./logical-assignment-operators"), - "max-classes-per-file": () => require("./max-classes-per-file"), - "max-depth": () => require("./max-depth"), - "max-len": () => require("./max-len"), - "max-lines": () => require("./max-lines"), - "max-lines-per-function": () => require("./max-lines-per-function"), - "max-nested-callbacks": () => require("./max-nested-callbacks"), - "max-params": () => require("./max-params"), - "max-statements": () => require("./max-statements"), - "max-statements-per-line": () => require("./max-statements-per-line"), - "multiline-comment-style": () => require("./multiline-comment-style"), - "multiline-ternary": () => require("./multiline-ternary"), - "new-cap": () => require("./new-cap"), - "new-parens": () => require("./new-parens"), - "newline-after-var": () => require("./newline-after-var"), - "newline-before-return": () => require("./newline-before-return"), - "newline-per-chained-call": () => require("./newline-per-chained-call"), - "no-alert": () => require("./no-alert"), - "no-array-constructor": () => require("./no-array-constructor"), - "no-async-promise-executor": () => require("./no-async-promise-executor"), - "no-await-in-loop": () => require("./no-await-in-loop"), - "no-bitwise": () => require("./no-bitwise"), - "no-buffer-constructor": () => require("./no-buffer-constructor"), - "no-caller": () => require("./no-caller"), - "no-case-declarations": () => require("./no-case-declarations"), - "no-catch-shadow": () => require("./no-catch-shadow"), - "no-class-assign": () => require("./no-class-assign"), - "no-compare-neg-zero": () => require("./no-compare-neg-zero"), - "no-cond-assign": () => require("./no-cond-assign"), - "no-confusing-arrow": () => require("./no-confusing-arrow"), - "no-console": () => require("./no-console"), - "no-const-assign": () => require("./no-const-assign"), - "no-constant-binary-expression": () => require("./no-constant-binary-expression"), - "no-constant-condition": () => require("./no-constant-condition"), - "no-constructor-return": () => require("./no-constructor-return"), - "no-continue": () => require("./no-continue"), - "no-control-regex": () => require("./no-control-regex"), - "no-debugger": () => require("./no-debugger"), - "no-delete-var": () => require("./no-delete-var"), - "no-div-regex": () => require("./no-div-regex"), - "no-dupe-args": () => require("./no-dupe-args"), - "no-dupe-class-members": () => require("./no-dupe-class-members"), - "no-dupe-else-if": () => require("./no-dupe-else-if"), - "no-dupe-keys": () => require("./no-dupe-keys"), - "no-duplicate-case": () => require("./no-duplicate-case"), - "no-duplicate-imports": () => require("./no-duplicate-imports"), - "no-else-return": () => require("./no-else-return"), - "no-empty": () => require("./no-empty"), - "no-empty-character-class": () => require("./no-empty-character-class"), - "no-empty-function": () => require("./no-empty-function"), - "no-empty-pattern": () => require("./no-empty-pattern"), - "no-empty-static-block": () => require("./no-empty-static-block"), - "no-eq-null": () => require("./no-eq-null"), - "no-eval": () => require("./no-eval"), - "no-ex-assign": () => require("./no-ex-assign"), - "no-extend-native": () => require("./no-extend-native"), - "no-extra-bind": () => require("./no-extra-bind"), - "no-extra-boolean-cast": () => require("./no-extra-boolean-cast"), - "no-extra-label": () => require("./no-extra-label"), - "no-extra-parens": () => require("./no-extra-parens"), - "no-extra-semi": () => require("./no-extra-semi"), - "no-fallthrough": () => require("./no-fallthrough"), - "no-floating-decimal": () => require("./no-floating-decimal"), - "no-func-assign": () => require("./no-func-assign"), - "no-global-assign": () => require("./no-global-assign"), - "no-implicit-coercion": () => require("./no-implicit-coercion"), - "no-implicit-globals": () => require("./no-implicit-globals"), - "no-implied-eval": () => require("./no-implied-eval"), - "no-import-assign": () => require("./no-import-assign"), - "no-inline-comments": () => require("./no-inline-comments"), - "no-inner-declarations": () => require("./no-inner-declarations"), - "no-invalid-regexp": () => require("./no-invalid-regexp"), - "no-invalid-this": () => require("./no-invalid-this"), - "no-irregular-whitespace": () => require("./no-irregular-whitespace"), - "no-iterator": () => require("./no-iterator"), - "no-label-var": () => require("./no-label-var"), - "no-labels": () => require("./no-labels"), - "no-lone-blocks": () => require("./no-lone-blocks"), - "no-lonely-if": () => require("./no-lonely-if"), - "no-loop-func": () => require("./no-loop-func"), - "no-loss-of-precision": () => require("./no-loss-of-precision"), - "no-magic-numbers": () => require("./no-magic-numbers"), - "no-misleading-character-class": () => require("./no-misleading-character-class"), - "no-mixed-operators": () => require("./no-mixed-operators"), - "no-mixed-requires": () => require("./no-mixed-requires"), - "no-mixed-spaces-and-tabs": () => require("./no-mixed-spaces-and-tabs"), - "no-multi-assign": () => require("./no-multi-assign"), - "no-multi-spaces": () => require("./no-multi-spaces"), - "no-multi-str": () => require("./no-multi-str"), - "no-multiple-empty-lines": () => require("./no-multiple-empty-lines"), - "no-native-reassign": () => require("./no-native-reassign"), - "no-negated-condition": () => require("./no-negated-condition"), - "no-negated-in-lhs": () => require("./no-negated-in-lhs"), - "no-nested-ternary": () => require("./no-nested-ternary"), - "no-new": () => require("./no-new"), - "no-new-func": () => require("./no-new-func"), - "no-new-native-nonconstructor": () => require("./no-new-native-nonconstructor"), - "no-new-object": () => require("./no-new-object"), - "no-new-require": () => require("./no-new-require"), - "no-new-symbol": () => require("./no-new-symbol"), - "no-new-wrappers": () => require("./no-new-wrappers"), - "no-nonoctal-decimal-escape": () => require("./no-nonoctal-decimal-escape"), - "no-obj-calls": () => require("./no-obj-calls"), - "no-object-constructor": () => require("./no-object-constructor"), - "no-octal": () => require("./no-octal"), - "no-octal-escape": () => require("./no-octal-escape"), - "no-param-reassign": () => require("./no-param-reassign"), - "no-path-concat": () => require("./no-path-concat"), - "no-plusplus": () => require("./no-plusplus"), - "no-process-env": () => require("./no-process-env"), - "no-process-exit": () => require("./no-process-exit"), - "no-promise-executor-return": () => require("./no-promise-executor-return"), - "no-proto": () => require("./no-proto"), - "no-prototype-builtins": () => require("./no-prototype-builtins"), - "no-redeclare": () => require("./no-redeclare"), - "no-regex-spaces": () => require("./no-regex-spaces"), - "no-restricted-exports": () => require("./no-restricted-exports"), - "no-restricted-globals": () => require("./no-restricted-globals"), - "no-restricted-imports": () => require("./no-restricted-imports"), - "no-restricted-modules": () => require("./no-restricted-modules"), - "no-restricted-properties": () => require("./no-restricted-properties"), - "no-restricted-syntax": () => require("./no-restricted-syntax"), - "no-return-assign": () => require("./no-return-assign"), - "no-return-await": () => require("./no-return-await"), - "no-script-url": () => require("./no-script-url"), - "no-self-assign": () => require("./no-self-assign"), - "no-self-compare": () => require("./no-self-compare"), - "no-sequences": () => require("./no-sequences"), - "no-setter-return": () => require("./no-setter-return"), - "no-shadow": () => require("./no-shadow"), - "no-shadow-restricted-names": () => require("./no-shadow-restricted-names"), - "no-spaced-func": () => require("./no-spaced-func"), - "no-sparse-arrays": () => require("./no-sparse-arrays"), - "no-sync": () => require("./no-sync"), - "no-tabs": () => require("./no-tabs"), - "no-template-curly-in-string": () => require("./no-template-curly-in-string"), - "no-ternary": () => require("./no-ternary"), - "no-this-before-super": () => require("./no-this-before-super"), - "no-throw-literal": () => require("./no-throw-literal"), - "no-trailing-spaces": () => require("./no-trailing-spaces"), - "no-undef": () => require("./no-undef"), - "no-undef-init": () => require("./no-undef-init"), - "no-undefined": () => require("./no-undefined"), - "no-underscore-dangle": () => require("./no-underscore-dangle"), - "no-unexpected-multiline": () => require("./no-unexpected-multiline"), - "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"), - "no-unneeded-ternary": () => require("./no-unneeded-ternary"), - "no-unreachable": () => require("./no-unreachable"), - "no-unreachable-loop": () => require("./no-unreachable-loop"), - "no-unsafe-finally": () => require("./no-unsafe-finally"), - "no-unsafe-negation": () => require("./no-unsafe-negation"), - "no-unsafe-optional-chaining": () => require("./no-unsafe-optional-chaining"), - "no-unused-expressions": () => require("./no-unused-expressions"), - "no-unused-labels": () => require("./no-unused-labels"), - "no-unused-private-class-members": () => require("./no-unused-private-class-members"), - "no-unused-vars": () => require("./no-unused-vars"), - "no-use-before-define": () => require("./no-use-before-define"), - "no-useless-assignment": () => require("./no-useless-assignment"), - "no-useless-backreference": () => require("./no-useless-backreference"), - "no-useless-call": () => require("./no-useless-call"), - "no-useless-catch": () => require("./no-useless-catch"), - "no-useless-computed-key": () => require("./no-useless-computed-key"), - "no-useless-concat": () => require("./no-useless-concat"), - "no-useless-constructor": () => require("./no-useless-constructor"), - "no-useless-escape": () => require("./no-useless-escape"), - "no-useless-rename": () => require("./no-useless-rename"), - "no-useless-return": () => require("./no-useless-return"), - "no-var": () => require("./no-var"), - "no-void": () => require("./no-void"), - "no-warning-comments": () => require("./no-warning-comments"), - "no-whitespace-before-property": () => require("./no-whitespace-before-property"), - "no-with": () => require("./no-with"), - "nonblock-statement-body-position": () => require("./nonblock-statement-body-position"), - "object-curly-newline": () => require("./object-curly-newline"), - "object-curly-spacing": () => require("./object-curly-spacing"), - "object-property-newline": () => require("./object-property-newline"), - "object-shorthand": () => require("./object-shorthand"), - "one-var": () => require("./one-var"), - "one-var-declaration-per-line": () => require("./one-var-declaration-per-line"), - "operator-assignment": () => require("./operator-assignment"), - "operator-linebreak": () => require("./operator-linebreak"), - "padded-blocks": () => require("./padded-blocks"), - "padding-line-between-statements": () => require("./padding-line-between-statements"), - "prefer-arrow-callback": () => require("./prefer-arrow-callback"), - "prefer-const": () => require("./prefer-const"), - "prefer-destructuring": () => require("./prefer-destructuring"), - "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"), - "prefer-named-capture-group": () => require("./prefer-named-capture-group"), - "prefer-numeric-literals": () => require("./prefer-numeric-literals"), - "prefer-object-has-own": () => require("./prefer-object-has-own"), - "prefer-object-spread": () => require("./prefer-object-spread"), - "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"), - "prefer-reflect": () => require("./prefer-reflect"), - "prefer-regex-literals": () => require("./prefer-regex-literals"), - "prefer-rest-params": () => require("./prefer-rest-params"), - "prefer-spread": () => require("./prefer-spread"), - "prefer-template": () => require("./prefer-template"), - "quote-props": () => require("./quote-props"), - quotes: () => require("./quotes"), - radix: () => require("./radix"), - "require-atomic-updates": () => require("./require-atomic-updates"), - "require-await": () => require("./require-await"), - "require-unicode-regexp": () => require("./require-unicode-regexp"), - "require-yield": () => require("./require-yield"), - "rest-spread-spacing": () => require("./rest-spread-spacing"), - semi: () => require("./semi"), - "semi-spacing": () => require("./semi-spacing"), - "semi-style": () => require("./semi-style"), - "sort-imports": () => require("./sort-imports"), - "sort-keys": () => require("./sort-keys"), - "sort-vars": () => require("./sort-vars"), - "space-before-blocks": () => require("./space-before-blocks"), - "space-before-function-paren": () => require("./space-before-function-paren"), - "space-in-parens": () => require("./space-in-parens"), - "space-infix-ops": () => require("./space-infix-ops"), - "space-unary-ops": () => require("./space-unary-ops"), - "spaced-comment": () => require("./spaced-comment"), - strict: () => require("./strict"), - "switch-colon-spacing": () => require("./switch-colon-spacing"), - "symbol-description": () => require("./symbol-description"), - "template-curly-spacing": () => require("./template-curly-spacing"), - "template-tag-spacing": () => require("./template-tag-spacing"), - "unicode-bom": () => require("./unicode-bom"), - "use-isnan": () => require("./use-isnan"), - "valid-typeof": () => require("./valid-typeof"), - "vars-on-top": () => require("./vars-on-top"), - "wrap-iife": () => require("./wrap-iife"), - "wrap-regex": () => require("./wrap-regex"), - "yield-star-spacing": () => require("./yield-star-spacing"), - yoda: () => require("./yoda") -})); +module.exports = new LazyLoadingRuleMap( + Object.entries({ + "accessor-pairs": () => require("./accessor-pairs"), + "array-bracket-newline": () => require("./array-bracket-newline"), + "array-bracket-spacing": () => require("./array-bracket-spacing"), + "array-callback-return": () => require("./array-callback-return"), + "array-element-newline": () => require("./array-element-newline"), + "arrow-body-style": () => require("./arrow-body-style"), + "arrow-parens": () => require("./arrow-parens"), + "arrow-spacing": () => require("./arrow-spacing"), + "block-scoped-var": () => require("./block-scoped-var"), + "block-spacing": () => require("./block-spacing"), + "brace-style": () => require("./brace-style"), + "callback-return": () => require("./callback-return"), + camelcase: () => require("./camelcase"), + "capitalized-comments": () => require("./capitalized-comments"), + "class-methods-use-this": () => require("./class-methods-use-this"), + "comma-dangle": () => require("./comma-dangle"), + "comma-spacing": () => require("./comma-spacing"), + "comma-style": () => require("./comma-style"), + complexity: () => require("./complexity"), + "computed-property-spacing": () => + require("./computed-property-spacing"), + "consistent-return": () => require("./consistent-return"), + "consistent-this": () => require("./consistent-this"), + "constructor-super": () => require("./constructor-super"), + curly: () => require("./curly"), + "default-case": () => require("./default-case"), + "default-case-last": () => require("./default-case-last"), + "default-param-last": () => require("./default-param-last"), + "dot-location": () => require("./dot-location"), + "dot-notation": () => require("./dot-notation"), + "eol-last": () => require("./eol-last"), + eqeqeq: () => require("./eqeqeq"), + "for-direction": () => require("./for-direction"), + "func-call-spacing": () => require("./func-call-spacing"), + "func-name-matching": () => require("./func-name-matching"), + "func-names": () => require("./func-names"), + "func-style": () => require("./func-style"), + "function-call-argument-newline": () => + require("./function-call-argument-newline"), + "function-paren-newline": () => require("./function-paren-newline"), + "generator-star-spacing": () => require("./generator-star-spacing"), + "getter-return": () => require("./getter-return"), + "global-require": () => require("./global-require"), + "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"), + "guard-for-in": () => require("./guard-for-in"), + "handle-callback-err": () => require("./handle-callback-err"), + "id-blacklist": () => require("./id-blacklist"), + "id-denylist": () => require("./id-denylist"), + "id-length": () => require("./id-length"), + "id-match": () => require("./id-match"), + "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"), + indent: () => require("./indent"), + "indent-legacy": () => require("./indent-legacy"), + "init-declarations": () => require("./init-declarations"), + "jsx-quotes": () => require("./jsx-quotes"), + "key-spacing": () => require("./key-spacing"), + "keyword-spacing": () => require("./keyword-spacing"), + "line-comment-position": () => require("./line-comment-position"), + "linebreak-style": () => require("./linebreak-style"), + "lines-around-comment": () => require("./lines-around-comment"), + "lines-around-directive": () => require("./lines-around-directive"), + "lines-between-class-members": () => + require("./lines-between-class-members"), + "logical-assignment-operators": () => + require("./logical-assignment-operators"), + "max-classes-per-file": () => require("./max-classes-per-file"), + "max-depth": () => require("./max-depth"), + "max-len": () => require("./max-len"), + "max-lines": () => require("./max-lines"), + "max-lines-per-function": () => require("./max-lines-per-function"), + "max-nested-callbacks": () => require("./max-nested-callbacks"), + "max-params": () => require("./max-params"), + "max-statements": () => require("./max-statements"), + "max-statements-per-line": () => require("./max-statements-per-line"), + "multiline-comment-style": () => require("./multiline-comment-style"), + "multiline-ternary": () => require("./multiline-ternary"), + "new-cap": () => require("./new-cap"), + "new-parens": () => require("./new-parens"), + "newline-after-var": () => require("./newline-after-var"), + "newline-before-return": () => require("./newline-before-return"), + "newline-per-chained-call": () => require("./newline-per-chained-call"), + "no-alert": () => require("./no-alert"), + "no-array-constructor": () => require("./no-array-constructor"), + "no-async-promise-executor": () => + require("./no-async-promise-executor"), + "no-await-in-loop": () => require("./no-await-in-loop"), + "no-bitwise": () => require("./no-bitwise"), + "no-buffer-constructor": () => require("./no-buffer-constructor"), + "no-caller": () => require("./no-caller"), + "no-case-declarations": () => require("./no-case-declarations"), + "no-catch-shadow": () => require("./no-catch-shadow"), + "no-class-assign": () => require("./no-class-assign"), + "no-compare-neg-zero": () => require("./no-compare-neg-zero"), + "no-cond-assign": () => require("./no-cond-assign"), + "no-confusing-arrow": () => require("./no-confusing-arrow"), + "no-console": () => require("./no-console"), + "no-const-assign": () => require("./no-const-assign"), + "no-constant-binary-expression": () => + require("./no-constant-binary-expression"), + "no-constant-condition": () => require("./no-constant-condition"), + "no-constructor-return": () => require("./no-constructor-return"), + "no-continue": () => require("./no-continue"), + "no-control-regex": () => require("./no-control-regex"), + "no-debugger": () => require("./no-debugger"), + "no-delete-var": () => require("./no-delete-var"), + "no-div-regex": () => require("./no-div-regex"), + "no-dupe-args": () => require("./no-dupe-args"), + "no-dupe-class-members": () => require("./no-dupe-class-members"), + "no-dupe-else-if": () => require("./no-dupe-else-if"), + "no-dupe-keys": () => require("./no-dupe-keys"), + "no-duplicate-case": () => require("./no-duplicate-case"), + "no-duplicate-imports": () => require("./no-duplicate-imports"), + "no-else-return": () => require("./no-else-return"), + "no-empty": () => require("./no-empty"), + "no-empty-character-class": () => require("./no-empty-character-class"), + "no-empty-function": () => require("./no-empty-function"), + "no-empty-pattern": () => require("./no-empty-pattern"), + "no-empty-static-block": () => require("./no-empty-static-block"), + "no-eq-null": () => require("./no-eq-null"), + "no-eval": () => require("./no-eval"), + "no-ex-assign": () => require("./no-ex-assign"), + "no-extend-native": () => require("./no-extend-native"), + "no-extra-bind": () => require("./no-extra-bind"), + "no-extra-boolean-cast": () => require("./no-extra-boolean-cast"), + "no-extra-label": () => require("./no-extra-label"), + "no-extra-parens": () => require("./no-extra-parens"), + "no-extra-semi": () => require("./no-extra-semi"), + "no-fallthrough": () => require("./no-fallthrough"), + "no-floating-decimal": () => require("./no-floating-decimal"), + "no-func-assign": () => require("./no-func-assign"), + "no-global-assign": () => require("./no-global-assign"), + "no-implicit-coercion": () => require("./no-implicit-coercion"), + "no-implicit-globals": () => require("./no-implicit-globals"), + "no-implied-eval": () => require("./no-implied-eval"), + "no-import-assign": () => require("./no-import-assign"), + "no-inline-comments": () => require("./no-inline-comments"), + "no-inner-declarations": () => require("./no-inner-declarations"), + "no-invalid-regexp": () => require("./no-invalid-regexp"), + "no-invalid-this": () => require("./no-invalid-this"), + "no-irregular-whitespace": () => require("./no-irregular-whitespace"), + "no-iterator": () => require("./no-iterator"), + "no-label-var": () => require("./no-label-var"), + "no-labels": () => require("./no-labels"), + "no-lone-blocks": () => require("./no-lone-blocks"), + "no-lonely-if": () => require("./no-lonely-if"), + "no-loop-func": () => require("./no-loop-func"), + "no-loss-of-precision": () => require("./no-loss-of-precision"), + "no-magic-numbers": () => require("./no-magic-numbers"), + "no-misleading-character-class": () => + require("./no-misleading-character-class"), + "no-mixed-operators": () => require("./no-mixed-operators"), + "no-mixed-requires": () => require("./no-mixed-requires"), + "no-mixed-spaces-and-tabs": () => require("./no-mixed-spaces-and-tabs"), + "no-multi-assign": () => require("./no-multi-assign"), + "no-multi-spaces": () => require("./no-multi-spaces"), + "no-multi-str": () => require("./no-multi-str"), + "no-multiple-empty-lines": () => require("./no-multiple-empty-lines"), + "no-native-reassign": () => require("./no-native-reassign"), + "no-negated-condition": () => require("./no-negated-condition"), + "no-negated-in-lhs": () => require("./no-negated-in-lhs"), + "no-nested-ternary": () => require("./no-nested-ternary"), + "no-new": () => require("./no-new"), + "no-new-func": () => require("./no-new-func"), + "no-new-native-nonconstructor": () => + require("./no-new-native-nonconstructor"), + "no-new-object": () => require("./no-new-object"), + "no-new-require": () => require("./no-new-require"), + "no-new-symbol": () => require("./no-new-symbol"), + "no-new-wrappers": () => require("./no-new-wrappers"), + "no-nonoctal-decimal-escape": () => + require("./no-nonoctal-decimal-escape"), + "no-obj-calls": () => require("./no-obj-calls"), + "no-object-constructor": () => require("./no-object-constructor"), + "no-octal": () => require("./no-octal"), + "no-octal-escape": () => require("./no-octal-escape"), + "no-param-reassign": () => require("./no-param-reassign"), + "no-path-concat": () => require("./no-path-concat"), + "no-plusplus": () => require("./no-plusplus"), + "no-process-env": () => require("./no-process-env"), + "no-process-exit": () => require("./no-process-exit"), + "no-promise-executor-return": () => + require("./no-promise-executor-return"), + "no-proto": () => require("./no-proto"), + "no-prototype-builtins": () => require("./no-prototype-builtins"), + "no-redeclare": () => require("./no-redeclare"), + "no-regex-spaces": () => require("./no-regex-spaces"), + "no-restricted-exports": () => require("./no-restricted-exports"), + "no-restricted-globals": () => require("./no-restricted-globals"), + "no-restricted-imports": () => require("./no-restricted-imports"), + "no-restricted-modules": () => require("./no-restricted-modules"), + "no-restricted-properties": () => require("./no-restricted-properties"), + "no-restricted-syntax": () => require("./no-restricted-syntax"), + "no-return-assign": () => require("./no-return-assign"), + "no-return-await": () => require("./no-return-await"), + "no-script-url": () => require("./no-script-url"), + "no-self-assign": () => require("./no-self-assign"), + "no-self-compare": () => require("./no-self-compare"), + "no-sequences": () => require("./no-sequences"), + "no-setter-return": () => require("./no-setter-return"), + "no-shadow": () => require("./no-shadow"), + "no-shadow-restricted-names": () => + require("./no-shadow-restricted-names"), + "no-spaced-func": () => require("./no-spaced-func"), + "no-sparse-arrays": () => require("./no-sparse-arrays"), + "no-sync": () => require("./no-sync"), + "no-tabs": () => require("./no-tabs"), + "no-template-curly-in-string": () => + require("./no-template-curly-in-string"), + "no-ternary": () => require("./no-ternary"), + "no-this-before-super": () => require("./no-this-before-super"), + "no-throw-literal": () => require("./no-throw-literal"), + "no-trailing-spaces": () => require("./no-trailing-spaces"), + "no-undef": () => require("./no-undef"), + "no-undef-init": () => require("./no-undef-init"), + "no-undefined": () => require("./no-undefined"), + "no-underscore-dangle": () => require("./no-underscore-dangle"), + "no-unexpected-multiline": () => require("./no-unexpected-multiline"), + "no-unmodified-loop-condition": () => + require("./no-unmodified-loop-condition"), + "no-unneeded-ternary": () => require("./no-unneeded-ternary"), + "no-unreachable": () => require("./no-unreachable"), + "no-unreachable-loop": () => require("./no-unreachable-loop"), + "no-unsafe-finally": () => require("./no-unsafe-finally"), + "no-unsafe-negation": () => require("./no-unsafe-negation"), + "no-unsafe-optional-chaining": () => + require("./no-unsafe-optional-chaining"), + "no-unused-expressions": () => require("./no-unused-expressions"), + "no-unused-labels": () => require("./no-unused-labels"), + "no-unused-private-class-members": () => + require("./no-unused-private-class-members"), + "no-unused-vars": () => require("./no-unused-vars"), + "no-use-before-define": () => require("./no-use-before-define"), + "no-useless-assignment": () => require("./no-useless-assignment"), + "no-useless-backreference": () => require("./no-useless-backreference"), + "no-useless-call": () => require("./no-useless-call"), + "no-useless-catch": () => require("./no-useless-catch"), + "no-useless-computed-key": () => require("./no-useless-computed-key"), + "no-useless-concat": () => require("./no-useless-concat"), + "no-useless-constructor": () => require("./no-useless-constructor"), + "no-useless-escape": () => require("./no-useless-escape"), + "no-useless-rename": () => require("./no-useless-rename"), + "no-useless-return": () => require("./no-useless-return"), + "no-var": () => require("./no-var"), + "no-void": () => require("./no-void"), + "no-warning-comments": () => require("./no-warning-comments"), + "no-whitespace-before-property": () => + require("./no-whitespace-before-property"), + "no-with": () => require("./no-with"), + "nonblock-statement-body-position": () => + require("./nonblock-statement-body-position"), + "object-curly-newline": () => require("./object-curly-newline"), + "object-curly-spacing": () => require("./object-curly-spacing"), + "object-property-newline": () => require("./object-property-newline"), + "object-shorthand": () => require("./object-shorthand"), + "one-var": () => require("./one-var"), + "one-var-declaration-per-line": () => + require("./one-var-declaration-per-line"), + "operator-assignment": () => require("./operator-assignment"), + "operator-linebreak": () => require("./operator-linebreak"), + "padded-blocks": () => require("./padded-blocks"), + "padding-line-between-statements": () => + require("./padding-line-between-statements"), + "prefer-arrow-callback": () => require("./prefer-arrow-callback"), + "prefer-const": () => require("./prefer-const"), + "prefer-destructuring": () => require("./prefer-destructuring"), + "prefer-exponentiation-operator": () => + require("./prefer-exponentiation-operator"), + "prefer-named-capture-group": () => + require("./prefer-named-capture-group"), + "prefer-numeric-literals": () => require("./prefer-numeric-literals"), + "prefer-object-has-own": () => require("./prefer-object-has-own"), + "prefer-object-spread": () => require("./prefer-object-spread"), + "prefer-promise-reject-errors": () => + require("./prefer-promise-reject-errors"), + "prefer-reflect": () => require("./prefer-reflect"), + "prefer-regex-literals": () => require("./prefer-regex-literals"), + "prefer-rest-params": () => require("./prefer-rest-params"), + "prefer-spread": () => require("./prefer-spread"), + "prefer-template": () => require("./prefer-template"), + "quote-props": () => require("./quote-props"), + quotes: () => require("./quotes"), + radix: () => require("./radix"), + "require-atomic-updates": () => require("./require-atomic-updates"), + "require-await": () => require("./require-await"), + "require-unicode-regexp": () => require("./require-unicode-regexp"), + "require-yield": () => require("./require-yield"), + "rest-spread-spacing": () => require("./rest-spread-spacing"), + semi: () => require("./semi"), + "semi-spacing": () => require("./semi-spacing"), + "semi-style": () => require("./semi-style"), + "sort-imports": () => require("./sort-imports"), + "sort-keys": () => require("./sort-keys"), + "sort-vars": () => require("./sort-vars"), + "space-before-blocks": () => require("./space-before-blocks"), + "space-before-function-paren": () => + require("./space-before-function-paren"), + "space-in-parens": () => require("./space-in-parens"), + "space-infix-ops": () => require("./space-infix-ops"), + "space-unary-ops": () => require("./space-unary-ops"), + "spaced-comment": () => require("./spaced-comment"), + strict: () => require("./strict"), + "switch-colon-spacing": () => require("./switch-colon-spacing"), + "symbol-description": () => require("./symbol-description"), + "template-curly-spacing": () => require("./template-curly-spacing"), + "template-tag-spacing": () => require("./template-tag-spacing"), + "unicode-bom": () => require("./unicode-bom"), + "use-isnan": () => require("./use-isnan"), + "valid-typeof": () => require("./valid-typeof"), + "vars-on-top": () => require("./vars-on-top"), + "wrap-iife": () => require("./wrap-iife"), + "wrap-regex": () => require("./wrap-regex"), + "yield-star-spacing": () => require("./yield-star-spacing"), + yoda: () => require("./yoda"), + }), +); diff --git a/lib/rules/init-declarations.js b/lib/rules/init-declarations.js index 6ed83ee38c37..8dd64c7780e7 100644 --- a/lib/rules/init-declarations.js +++ b/lib/rules/init-declarations.js @@ -15,9 +15,11 @@ * @returns {boolean} `true` when the node is a for loop. */ function isForLoop(block) { - return block.type === "ForInStatement" || - block.type === "ForOfStatement" || - block.type === "ForStatement"; + return ( + block.type === "ForInStatement" || + block.type === "ForOfStatement" || + block.type === "ForStatement" + ); } /** @@ -26,16 +28,16 @@ function isForLoop(block) { * @returns {boolean} `true` when the node has its initializer. */ function isInitialized(node) { - const declaration = node.parent; - const block = declaration.parent; - - if (isForLoop(block)) { - if (block.type === "ForStatement") { - return block.init === declaration; - } - return block.left === declaration; - } - return Boolean(node.init); + const declaration = node.parent; + const block = declaration.parent; + + if (isForLoop(block)) { + if (block.type === "ForStatement") { + return block.init === declaration; + } + return block.left === declaration; + } + return Boolean(node.init); } //------------------------------------------------------------------------------ @@ -44,97 +46,104 @@ function isInitialized(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require or disallow initialization in variable declarations", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/init-declarations" - }, - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["never"] - }, - { - type: "object", - properties: { - ignoreForLoopInit: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - messages: { - initialized: "Variable '{{idName}}' should be initialized on declaration.", - notInitialized: "Variable '{{idName}}' should not be initialized on declaration." - } - }, - - create(context) { - - const MODE_ALWAYS = "always", - MODE_NEVER = "never"; - - const mode = context.options[0] || MODE_ALWAYS; - const params = context.options[1] || {}; - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "VariableDeclaration:exit"(node) { - - const kind = node.kind, - declarations = node.declarations; - - for (let i = 0; i < declarations.length; ++i) { - const declaration = declarations[i], - id = declaration.id, - initialized = isInitialized(declaration), - isIgnoredForLoop = params.ignoreForLoopInit && isForLoop(node.parent); - let messageId = ""; - - if (mode === MODE_ALWAYS && !initialized) { - messageId = "initialized"; - } else if (mode === MODE_NEVER && kind !== "const" && initialized && !isIgnoredForLoop) { - messageId = "notInitialized"; - } - - if (id.type === "Identifier" && messageId) { - context.report({ - node: declaration, - messageId, - data: { - idName: id.name - } - }); - } - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Require or disallow initialization in variable declarations", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/init-declarations", + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["always"], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["never"], + }, + { + type: "object", + properties: { + ignoreForLoopInit: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + messages: { + initialized: + "Variable '{{idName}}' should be initialized on declaration.", + notInitialized: + "Variable '{{idName}}' should not be initialized on declaration.", + }, + }, + + create(context) { + const MODE_ALWAYS = "always", + MODE_NEVER = "never"; + + const mode = context.options[0] || MODE_ALWAYS; + const params = context.options[1] || {}; + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "VariableDeclaration:exit"(node) { + const kind = node.kind, + declarations = node.declarations; + + for (let i = 0; i < declarations.length; ++i) { + const declaration = declarations[i], + id = declaration.id, + initialized = isInitialized(declaration), + isIgnoredForLoop = + params.ignoreForLoopInit && isForLoop(node.parent); + let messageId = ""; + + if (mode === MODE_ALWAYS && !initialized) { + messageId = "initialized"; + } else if ( + mode === MODE_NEVER && + kind !== "const" && + initialized && + !isIgnoredForLoop + ) { + messageId = "notInitialized"; + } + + if (id.type === "Identifier" && messageId) { + context.report({ + node: declaration, + messageId, + data: { + idName: id.name, + }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/jsx-quotes.js b/lib/rules/jsx-quotes.js index c6fd301123aa..a3c89d7dfec9 100644 --- a/lib/rules/jsx-quotes.js +++ b/lib/rules/jsx-quotes.js @@ -17,20 +17,20 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const QUOTE_SETTINGS = { - "prefer-double": { - quote: "\"", - description: "singlequote", - convert(str) { - return str.replace(/'/gu, "\""); - } - }, - "prefer-single": { - quote: "'", - description: "doublequote", - convert(str) { - return str.replace(/"/gu, "'"); - } - } + "prefer-double": { + quote: '"', + description: "singlequote", + convert(str) { + return str.replace(/'/gu, '"'); + }, + }, + "prefer-single": { + quote: "'", + description: "doublequote", + convert(str) { + return str.replace(/"/gu, "'"); + }, + }, }; //------------------------------------------------------------------------------ @@ -39,78 +39,90 @@ const QUOTE_SETTINGS = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "jsx-quotes", - url: "https://eslint.style/rules/js/jsx-quotes" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "jsx-quotes", + url: "https://eslint.style/rules/js/jsx-quotes", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce the consistent use of either double or single quotes in JSX attributes", - recommended: false, - url: "https://eslint.org/docs/latest/rules/jsx-quotes" - }, + docs: { + description: + "Enforce the consistent use of either double or single quotes in JSX attributes", + recommended: false, + url: "https://eslint.org/docs/latest/rules/jsx-quotes", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - enum: ["prefer-single", "prefer-double"] - } - ], - messages: { - unexpected: "Unexpected usage of {{description}}." - } - }, + schema: [ + { + enum: ["prefer-single", "prefer-double"], + }, + ], + messages: { + unexpected: "Unexpected usage of {{description}}.", + }, + }, - create(context) { - const quoteOption = context.options[0] || "prefer-double", - setting = QUOTE_SETTINGS[quoteOption]; + create(context) { + const quoteOption = context.options[0] || "prefer-double", + setting = QUOTE_SETTINGS[quoteOption]; - /** - * Checks if the given string literal node uses the expected quotes - * @param {ASTNode} node A string literal node. - * @returns {boolean} Whether or not the string literal used the expected quotes. - * @public - */ - function usesExpectedQuotes(node) { - return node.value.includes(setting.quote) || astUtils.isSurroundedBy(node.raw, setting.quote); - } + /** + * Checks if the given string literal node uses the expected quotes + * @param {ASTNode} node A string literal node. + * @returns {boolean} Whether or not the string literal used the expected quotes. + * @public + */ + function usesExpectedQuotes(node) { + return ( + node.value.includes(setting.quote) || + astUtils.isSurroundedBy(node.raw, setting.quote) + ); + } - return { - JSXAttribute(node) { - const attributeValue = node.value; + return { + JSXAttribute(node) { + const attributeValue = node.value; - if (attributeValue && astUtils.isStringLiteral(attributeValue) && !usesExpectedQuotes(attributeValue)) { - context.report({ - node: attributeValue, - messageId: "unexpected", - data: { - description: setting.description - }, - fix(fixer) { - return fixer.replaceText(attributeValue, setting.convert(attributeValue.raw)); - } - }); - } - } - }; - } + if ( + attributeValue && + astUtils.isStringLiteral(attributeValue) && + !usesExpectedQuotes(attributeValue) + ) { + context.report({ + node: attributeValue, + messageId: "unexpected", + data: { + description: setting.description, + }, + fix(fixer) { + return fixer.replaceText( + attributeValue, + setting.convert(attributeValue.raw), + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js index f43c182e71d8..b40ccec09837 100644 --- a/lib/rules/key-spacing.js +++ b/lib/rules/key-spacing.js @@ -19,7 +19,7 @@ const { getGraphemeCount } = require("../shared/string-utils"); * @returns {boolean} True if str contains a line terminator. */ function containsLineTerminator(str) { - return astUtils.LINEBREAK_MATCHER.test(str); + return astUtils.LINEBREAK_MATCHER.test(str); } /** @@ -28,7 +28,7 @@ function containsLineTerminator(str) { * @returns {any} Last element of arr. */ function last(arr) { - return arr.at(-1); + return arr.at(-1); } /** @@ -37,7 +37,7 @@ function last(arr) { * @returns {boolean} True if the node is a single line. */ function isSingleLine(node) { - return (node.loc.end.line === node.loc.start.line); + return node.loc.end.line === node.loc.start.line; } /** @@ -46,10 +46,10 @@ function isSingleLine(node) { * @returns {boolean} True if all properties is on a single line. */ function isSingleLineProperties(properties) { - const [firstProp] = properties, - lastProp = last(properties); + const [firstProp] = properties, + lastProp = last(properties); - return firstProp.loc.start.line === lastProp.loc.end.line; + return firstProp.loc.start.line === lastProp.loc.end.line; } /** @@ -59,37 +59,38 @@ function isSingleLineProperties(properties) { * @returns {Object} The object with correctly initialized options and values */ function initOptionProperty(toOptions, fromOptions) { - toOptions.mode = fromOptions.mode || "strict"; - - // Set value of beforeColon - if (typeof fromOptions.beforeColon !== "undefined") { - toOptions.beforeColon = +fromOptions.beforeColon; - } else { - toOptions.beforeColon = 0; - } - - // Set value of afterColon - if (typeof fromOptions.afterColon !== "undefined") { - toOptions.afterColon = +fromOptions.afterColon; - } else { - toOptions.afterColon = 1; - } - - // Set align if exists - if (typeof fromOptions.align !== "undefined") { - if (typeof fromOptions.align === "object") { - toOptions.align = fromOptions.align; - } else { // "string" - toOptions.align = { - on: fromOptions.align, - mode: toOptions.mode, - beforeColon: toOptions.beforeColon, - afterColon: toOptions.afterColon - }; - } - } - - return toOptions; + toOptions.mode = fromOptions.mode || "strict"; + + // Set value of beforeColon + if (typeof fromOptions.beforeColon !== "undefined") { + toOptions.beforeColon = +fromOptions.beforeColon; + } else { + toOptions.beforeColon = 0; + } + + // Set value of afterColon + if (typeof fromOptions.afterColon !== "undefined") { + toOptions.afterColon = +fromOptions.afterColon; + } else { + toOptions.afterColon = 1; + } + + // Set align if exists + if (typeof fromOptions.align !== "undefined") { + if (typeof fromOptions.align === "object") { + toOptions.align = fromOptions.align; + } else { + // "string" + toOptions.align = { + on: fromOptions.align, + mode: toOptions.mode, + beforeColon: toOptions.beforeColon, + afterColon: toOptions.afterColon, + }; + } + } + + return toOptions; } /** @@ -99,32 +100,44 @@ function initOptionProperty(toOptions, fromOptions) { * @returns {Object} The object with correctly initialized options and values */ function initOptions(toOptions, fromOptions) { - if (typeof fromOptions.align === "object") { - - // Initialize the alignment configuration - toOptions.align = initOptionProperty({}, fromOptions.align); - toOptions.align.on = fromOptions.align.on || "colon"; - toOptions.align.mode = fromOptions.align.mode || "strict"; - - toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); - toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); - - } else { // string or undefined - toOptions.multiLine = initOptionProperty({}, (fromOptions.multiLine || fromOptions)); - toOptions.singleLine = initOptionProperty({}, (fromOptions.singleLine || fromOptions)); - - // If alignment options are defined in multiLine, pull them out into the general align configuration - if (toOptions.multiLine.align) { - toOptions.align = { - on: toOptions.multiLine.align.on, - mode: toOptions.multiLine.align.mode || toOptions.multiLine.mode, - beforeColon: toOptions.multiLine.align.beforeColon, - afterColon: toOptions.multiLine.align.afterColon - }; - } - } - - return toOptions; + if (typeof fromOptions.align === "object") { + // Initialize the alignment configuration + toOptions.align = initOptionProperty({}, fromOptions.align); + toOptions.align.on = fromOptions.align.on || "colon"; + toOptions.align.mode = fromOptions.align.mode || "strict"; + + toOptions.multiLine = initOptionProperty( + {}, + fromOptions.multiLine || fromOptions, + ); + toOptions.singleLine = initOptionProperty( + {}, + fromOptions.singleLine || fromOptions, + ); + } else { + // string or undefined + toOptions.multiLine = initOptionProperty( + {}, + fromOptions.multiLine || fromOptions, + ); + toOptions.singleLine = initOptionProperty( + {}, + fromOptions.singleLine || fromOptions, + ); + + // If alignment options are defined in multiLine, pull them out into the general align configuration + if (toOptions.multiLine.align) { + toOptions.align = { + on: toOptions.multiLine.align.on, + mode: + toOptions.multiLine.align.mode || toOptions.multiLine.mode, + beforeColon: toOptions.multiLine.align.beforeColon, + afterColon: toOptions.multiLine.align.afterColon, + }; + } + } + + return toOptions; } //------------------------------------------------------------------------------ @@ -133,573 +146,677 @@ function initOptions(toOptions, fromOptions) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "key-spacing", - url: "https://eslint.style/rules/js/key-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing between keys and values in object literal properties", - recommended: false, - url: "https://eslint.org/docs/latest/rules/key-spacing" - }, - - fixable: "whitespace", - - schema: [{ - anyOf: [ - { - type: "object", - properties: { - align: { - anyOf: [ - { - enum: ["colon", "value"] - }, - { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - on: { - enum: ["colon", "value"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - }, - mode: { - enum: ["strict", "minimum"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - }, - { - type: "object", - properties: { - singleLine: { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - }, - multiLine: { - type: "object", - properties: { - align: { - anyOf: [ - { - enum: ["colon", "value"] - }, - { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - on: { - enum: ["colon", "value"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - }, - mode: { - enum: ["strict", "minimum"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - } - }, - additionalProperties: false - }, - { - type: "object", - properties: { - singleLine: { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - }, - multiLine: { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - }, - align: { - type: "object", - properties: { - mode: { - enum: ["strict", "minimum"] - }, - on: { - enum: ["colon", "value"] - }, - beforeColon: { - type: "boolean" - }, - afterColon: { - type: "boolean" - } - }, - additionalProperties: false - } - }, - additionalProperties: false - } - ] - }], - messages: { - extraKey: "Extra space after {{computed}}key '{{key}}'.", - extraValue: "Extra space before value for {{computed}}key '{{key}}'.", - missingKey: "Missing space after {{computed}}key '{{key}}'.", - missingValue: "Missing space before value for {{computed}}key '{{key}}'." - } - }, - - create(context) { - - /** - * OPTIONS - * "key-spacing": [2, { - * beforeColon: false, - * afterColon: true, - * align: "colon" // Optional, or "value" - * } - */ - const options = context.options[0] || {}, - ruleOptions = initOptions({}, options), - multiLineOptions = ruleOptions.multiLine, - singleLineOptions = ruleOptions.singleLine, - alignmentOptions = ruleOptions.align || null; - - const sourceCode = context.sourceCode; - - /** - * Determines if the given property is key-value property. - * @param {ASTNode} property Property node to check. - * @returns {boolean} Whether the property is a key-value property. - */ - function isKeyValueProperty(property) { - return !( - (property.method || - property.shorthand || - property.kind !== "init" || property.type !== "Property") // Could be "ExperimentalSpreadProperty" or "SpreadElement" - ); - } - - /** - * Starting from the given node (a property.key node here) looks forward - * until it finds the colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The colon punctuator. - */ - function getNextColon(node) { - return sourceCode.getTokenAfter(node, astUtils.isColonToken); - } - - /** - * Starting from the given node (a property.key node here) looks forward - * until it finds the last token before a colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The last token before a colon punctuator. - */ - function getLastTokenBeforeColon(node) { - const colonToken = getNextColon(node); - - return sourceCode.getTokenBefore(colonToken); - } - - /** - * Starting from the given node (a property.key node here) looks forward - * until it finds the first token after a colon punctuator and returns it. - * @param {ASTNode} node The node to start looking from. - * @returns {ASTNode} The first token after a colon punctuator. - */ - function getFirstTokenAfterColon(node) { - const colonToken = getNextColon(node); - - return sourceCode.getTokenAfter(colonToken); - } - - /** - * Checks whether a property is a member of the property group it follows. - * @param {ASTNode} lastMember The last Property known to be in the group. - * @param {ASTNode} candidate The next Property that might be in the group. - * @returns {boolean} True if the candidate property is part of the group. - */ - function continuesPropertyGroup(lastMember, candidate) { - const groupEndLine = lastMember.loc.start.line, - candidateValueStartLine = (isKeyValueProperty(candidate) ? getFirstTokenAfterColon(candidate.key) : candidate).loc.start.line; - - if (candidateValueStartLine - groupEndLine <= 1) { - return true; - } - - /* - * Check that the first comment is adjacent to the end of the group, the - * last comment is adjacent to the candidate property, and that successive - * comments are adjacent to each other. - */ - const leadingComments = sourceCode.getCommentsBefore(candidate); - - if ( - leadingComments.length && - leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateValueStartLine - last(leadingComments).loc.end.line <= 1 - ) { - for (let i = 1; i < leadingComments.length; i++) { - if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) { - return false; - } - } - return true; - } - - return false; - } - - /** - * Gets an object literal property's key as the identifier name or string value. - * @param {ASTNode} property Property node whose key to retrieve. - * @returns {string} The property's key. - */ - function getKey(property) { - const key = property.key; - - if (property.computed) { - return sourceCode.getText().slice(key.range[0], key.range[1]); - } - return astUtils.getStaticPropertyName(property); - } - - /** - * Reports an appropriately-formatted error if spacing is incorrect on one - * side of the colon. - * @param {ASTNode} property Key-value pair in an object literal. - * @param {string} side Side being verified - either "key" or "value". - * @param {string} whitespace Actual whitespace string. - * @param {int} expected Expected whitespace length. - * @param {string} mode Value of the mode as "strict" or "minimum" - * @returns {void} - */ - function report(property, side, whitespace, expected, mode) { - const diff = whitespace.length - expected; - - if (( - diff && mode === "strict" || - diff < 0 && mode === "minimum" || - diff > 0 && !expected && mode === "minimum") && - !(expected && containsLineTerminator(whitespace)) - ) { - const nextColon = getNextColon(property.key), - tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { includeComments: true }), - tokenAfterColon = sourceCode.getTokenAfter(nextColon, { includeComments: true }), - isKeySide = side === "key", - isExtra = diff > 0, - diffAbs = Math.abs(diff), - spaces = Array(diffAbs + 1).join(" "); - - const locStart = isKeySide ? tokenBeforeColon.loc.end : nextColon.loc.start; - const locEnd = isKeySide ? nextColon.loc.start : tokenAfterColon.loc.start; - const missingLoc = isKeySide ? tokenBeforeColon.loc : tokenAfterColon.loc; - const loc = isExtra ? { start: locStart, end: locEnd } : missingLoc; - - let fix; - - if (isExtra) { - let range; - - // Remove whitespace - if (isKeySide) { - range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs]; - } else { - range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]]; - } - fix = function(fixer) { - return fixer.removeRange(range); - }; - } else { - - // Add whitespace - if (isKeySide) { - fix = function(fixer) { - return fixer.insertTextAfter(tokenBeforeColon, spaces); - }; - } else { - fix = function(fixer) { - return fixer.insertTextBefore(tokenAfterColon, spaces); - }; - } - } - - let messageId; - - if (isExtra) { - messageId = side === "key" ? "extraKey" : "extraValue"; - } else { - messageId = side === "key" ? "missingKey" : "missingValue"; - } - - context.report({ - node: property[side], - loc, - messageId, - data: { - computed: property.computed ? "computed " : "", - key: getKey(property) - }, - fix - }); - } - } - - /** - * Gets the number of characters in a key, including quotes around string - * keys and braces around computed property keys. - * @param {ASTNode} property Property of on object literal. - * @returns {int} Width of the key. - */ - function getKeyWidth(property) { - const startToken = sourceCode.getFirstToken(property); - const endToken = getLastTokenBeforeColon(property.key); - - return getGraphemeCount(sourceCode.getText().slice(startToken.range[0], endToken.range[1])); - } - - /** - * Gets the whitespace around the colon in an object literal property. - * @param {ASTNode} property Property node from an object literal. - * @returns {Object} Whitespace before and after the property's colon. - */ - function getPropertyWhitespace(property) { - const whitespace = /(\s*):(\s*)/u.exec(sourceCode.getText().slice( - property.key.range[1], property.value.range[0] - )); - - if (whitespace) { - return { - beforeColon: whitespace[1], - afterColon: whitespace[2] - }; - } - return null; - } - - /** - * Creates groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. - * @returns {Array} Groups of property AST node lists. - */ - function createGroups(node) { - if (node.properties.length === 1) { - return [node.properties]; - } - - return node.properties.reduce((groups, property) => { - const currentGroup = last(groups), - prev = last(currentGroup); - - if (!prev || continuesPropertyGroup(prev, property)) { - currentGroup.push(property); - } else { - groups.push([property]); - } - - return groups; - }, [ - [] - ]); - } - - /** - * Verifies correct vertical alignment of a group of properties. - * @param {ASTNode[]} properties List of Property AST nodes. - * @returns {void} - */ - function verifyGroupAlignment(properties) { - const length = properties.length, - widths = properties.map(getKeyWidth), // Width of keys, including quotes - align = alignmentOptions.on; // "value" or "colon" - let targetWidth = Math.max(...widths), - beforeColon, afterColon, mode; - - if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration. - beforeColon = alignmentOptions.beforeColon; - afterColon = alignmentOptions.afterColon; - mode = alignmentOptions.mode; - } else { - beforeColon = multiLineOptions.beforeColon; - afterColon = multiLineOptions.afterColon; - mode = alignmentOptions.mode; - } - - // Conditionally include one space before or after colon - targetWidth += (align === "colon" ? beforeColon : afterColon); - - for (let i = 0; i < length; i++) { - const property = properties[i]; - const whitespace = getPropertyWhitespace(property); - - if (whitespace) { // Object literal getters/setters lack a colon - const width = widths[i]; - - if (align === "value") { - report(property, "key", whitespace.beforeColon, beforeColon, mode); - report(property, "value", whitespace.afterColon, targetWidth - width, mode); - } else { // align = "colon" - report(property, "key", whitespace.beforeColon, targetWidth - width, mode); - report(property, "value", whitespace.afterColon, afterColon, mode); - } - } - } - } - - /** - * Verifies spacing of property conforms to specified options. - * @param {ASTNode} node Property node being evaluated. - * @param {Object} lineOptions Configured singleLine or multiLine options - * @returns {void} - */ - function verifySpacing(node, lineOptions) { - const actual = getPropertyWhitespace(node); - - if (actual) { // Object literal getters/setters lack colons - report(node, "key", actual.beforeColon, lineOptions.beforeColon, lineOptions.mode); - report(node, "value", actual.afterColon, lineOptions.afterColon, lineOptions.mode); - } - } - - /** - * Verifies spacing of each property in a list. - * @param {ASTNode[]} properties List of Property AST nodes. - * @param {Object} lineOptions Configured singleLine or multiLine options - * @returns {void} - */ - function verifyListSpacing(properties, lineOptions) { - const length = properties.length; - - for (let i = 0; i < length; i++) { - verifySpacing(properties[i], lineOptions); - } - } - - /** - * Verifies vertical alignment, taking into account groups of properties. - * @param {ASTNode} node ObjectExpression node being evaluated. - * @returns {void} - */ - function verifyAlignment(node) { - createGroups(node).forEach(group => { - const properties = group.filter(isKeyValueProperty); - - if (properties.length > 0 && isSingleLineProperties(properties)) { - verifyListSpacing(properties, multiLineOptions); - } else { - verifyGroupAlignment(properties); - } - }); - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - if (alignmentOptions) { // Verify vertical alignment - - return { - ObjectExpression(node) { - if (isSingleLine(node)) { - verifyListSpacing(node.properties.filter(isKeyValueProperty), singleLineOptions); - } else { - verifyAlignment(node); - } - } - }; - - } - - // Obey beforeColon and afterColon in each property as configured - return { - Property(node) { - verifySpacing(node, isSingleLine(node.parent) ? singleLineOptions : multiLineOptions); - } - }; - - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "key-spacing", + url: "https://eslint.style/rules/js/key-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing between keys and values in object literal properties", + recommended: false, + url: "https://eslint.org/docs/latest/rules/key-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + anyOf: [ + { + type: "object", + properties: { + align: { + anyOf: [ + { + enum: ["colon", "value"], + }, + { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"], + }, + on: { + enum: ["colon", "value"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + mode: { + enum: ["strict", "minimum"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + singleLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + multiLine: { + type: "object", + properties: { + align: { + anyOf: [ + { + enum: ["colon", "value"], + }, + { + type: "object", + properties: { + mode: { + enum: [ + "strict", + "minimum", + ], + }, + on: { + enum: [ + "colon", + "value", + ], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + mode: { + enum: ["strict", "minimum"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + singleLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + multiLine: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + align: { + type: "object", + properties: { + mode: { + enum: ["strict", "minimum"], + }, + on: { + enum: ["colon", "value"], + }, + beforeColon: { + type: "boolean", + }, + afterColon: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + extraKey: "Extra space after {{computed}}key '{{key}}'.", + extraValue: + "Extra space before value for {{computed}}key '{{key}}'.", + missingKey: "Missing space after {{computed}}key '{{key}}'.", + missingValue: + "Missing space before value for {{computed}}key '{{key}}'.", + }, + }, + + create(context) { + /** + * OPTIONS + * "key-spacing": [2, { + * beforeColon: false, + * afterColon: true, + * align: "colon" // Optional, or "value" + * } + */ + const options = context.options[0] || {}, + ruleOptions = initOptions({}, options), + multiLineOptions = ruleOptions.multiLine, + singleLineOptions = ruleOptions.singleLine, + alignmentOptions = ruleOptions.align || null; + + const sourceCode = context.sourceCode; + + /** + * Determines if the given property is key-value property. + * @param {ASTNode} property Property node to check. + * @returns {boolean} Whether the property is a key-value property. + */ + function isKeyValueProperty(property) { + return !( + ( + property.method || + property.shorthand || + property.kind !== "init" || + property.type !== "Property" + ) // Could be "ExperimentalSpreadProperty" or "SpreadElement" + ); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The colon punctuator. + */ + function getNextColon(node) { + return sourceCode.getTokenAfter(node, astUtils.isColonToken); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the last token before a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The last token before a colon punctuator. + */ + function getLastTokenBeforeColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenBefore(colonToken); + } + + /** + * Starting from the given node (a property.key node here) looks forward + * until it finds the first token after a colon punctuator and returns it. + * @param {ASTNode} node The node to start looking from. + * @returns {ASTNode} The first token after a colon punctuator. + */ + function getFirstTokenAfterColon(node) { + const colonToken = getNextColon(node); + + return sourceCode.getTokenAfter(colonToken); + } + + /** + * Checks whether a property is a member of the property group it follows. + * @param {ASTNode} lastMember The last Property known to be in the group. + * @param {ASTNode} candidate The next Property that might be in the group. + * @returns {boolean} True if the candidate property is part of the group. + */ + function continuesPropertyGroup(lastMember, candidate) { + const groupEndLine = lastMember.loc.start.line, + candidateValueStartLine = ( + isKeyValueProperty(candidate) + ? getFirstTokenAfterColon(candidate.key) + : candidate + ).loc.start.line; + + if (candidateValueStartLine - groupEndLine <= 1) { + return true; + } + + /* + * Check that the first comment is adjacent to the end of the group, the + * last comment is adjacent to the candidate property, and that successive + * comments are adjacent to each other. + */ + const leadingComments = sourceCode.getCommentsBefore(candidate); + + if ( + leadingComments.length && + leadingComments[0].loc.start.line - groupEndLine <= 1 && + candidateValueStartLine - last(leadingComments).loc.end.line <= + 1 + ) { + for (let i = 1; i < leadingComments.length; i++) { + if ( + leadingComments[i].loc.start.line - + leadingComments[i - 1].loc.end.line > + 1 + ) { + return false; + } + } + return true; + } + + return false; + } + + /** + * Gets an object literal property's key as the identifier name or string value. + * @param {ASTNode} property Property node whose key to retrieve. + * @returns {string} The property's key. + */ + function getKey(property) { + const key = property.key; + + if (property.computed) { + return sourceCode.getText().slice(key.range[0], key.range[1]); + } + return astUtils.getStaticPropertyName(property); + } + + /** + * Reports an appropriately-formatted error if spacing is incorrect on one + * side of the colon. + * @param {ASTNode} property Key-value pair in an object literal. + * @param {string} side Side being verified - either "key" or "value". + * @param {string} whitespace Actual whitespace string. + * @param {int} expected Expected whitespace length. + * @param {string} mode Value of the mode as "strict" or "minimum" + * @returns {void} + */ + function report(property, side, whitespace, expected, mode) { + const diff = whitespace.length - expected; + + if ( + ((diff && mode === "strict") || + (diff < 0 && mode === "minimum") || + (diff > 0 && !expected && mode === "minimum")) && + !(expected && containsLineTerminator(whitespace)) + ) { + const nextColon = getNextColon(property.key), + tokenBeforeColon = sourceCode.getTokenBefore(nextColon, { + includeComments: true, + }), + tokenAfterColon = sourceCode.getTokenAfter(nextColon, { + includeComments: true, + }), + isKeySide = side === "key", + isExtra = diff > 0, + diffAbs = Math.abs(diff), + spaces = Array(diffAbs + 1).join(" "); + + const locStart = isKeySide + ? tokenBeforeColon.loc.end + : nextColon.loc.start; + const locEnd = isKeySide + ? nextColon.loc.start + : tokenAfterColon.loc.start; + const missingLoc = isKeySide + ? tokenBeforeColon.loc + : tokenAfterColon.loc; + const loc = isExtra + ? { start: locStart, end: locEnd } + : missingLoc; + + let fix; + + if (isExtra) { + let range; + + // Remove whitespace + if (isKeySide) { + range = [ + tokenBeforeColon.range[1], + tokenBeforeColon.range[1] + diffAbs, + ]; + } else { + range = [ + tokenAfterColon.range[0] - diffAbs, + tokenAfterColon.range[0], + ]; + } + fix = function (fixer) { + return fixer.removeRange(range); + }; + } else { + // Add whitespace + if (isKeySide) { + fix = function (fixer) { + return fixer.insertTextAfter( + tokenBeforeColon, + spaces, + ); + }; + } else { + fix = function (fixer) { + return fixer.insertTextBefore( + tokenAfterColon, + spaces, + ); + }; + } + } + + let messageId; + + if (isExtra) { + messageId = side === "key" ? "extraKey" : "extraValue"; + } else { + messageId = side === "key" ? "missingKey" : "missingValue"; + } + + context.report({ + node: property[side], + loc, + messageId, + data: { + computed: property.computed ? "computed " : "", + key: getKey(property), + }, + fix, + }); + } + } + + /** + * Gets the number of characters in a key, including quotes around string + * keys and braces around computed property keys. + * @param {ASTNode} property Property of on object literal. + * @returns {int} Width of the key. + */ + function getKeyWidth(property) { + const startToken = sourceCode.getFirstToken(property); + const endToken = getLastTokenBeforeColon(property.key); + + return getGraphemeCount( + sourceCode + .getText() + .slice(startToken.range[0], endToken.range[1]), + ); + } + + /** + * Gets the whitespace around the colon in an object literal property. + * @param {ASTNode} property Property node from an object literal. + * @returns {Object} Whitespace before and after the property's colon. + */ + function getPropertyWhitespace(property) { + const whitespace = /(\s*):(\s*)/u.exec( + sourceCode + .getText() + .slice(property.key.range[1], property.value.range[0]), + ); + + if (whitespace) { + return { + beforeColon: whitespace[1], + afterColon: whitespace[2], + }; + } + return null; + } + + /** + * Creates groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {Array} Groups of property AST node lists. + */ + function createGroups(node) { + if (node.properties.length === 1) { + return [node.properties]; + } + + return node.properties.reduce( + (groups, property) => { + const currentGroup = last(groups), + prev = last(currentGroup); + + if (!prev || continuesPropertyGroup(prev, property)) { + currentGroup.push(property); + } else { + groups.push([property]); + } + + return groups; + }, + [[]], + ); + } + + /** + * Verifies correct vertical alignment of a group of properties. + * @param {ASTNode[]} properties List of Property AST nodes. + * @returns {void} + */ + function verifyGroupAlignment(properties) { + const length = properties.length, + widths = properties.map(getKeyWidth), // Width of keys, including quotes + align = alignmentOptions.on; // "value" or "colon" + let targetWidth = Math.max(...widths), + beforeColon, + afterColon, + mode; + + if (alignmentOptions && length > 1) { + // When aligning values within a group, use the alignment configuration. + beforeColon = alignmentOptions.beforeColon; + afterColon = alignmentOptions.afterColon; + mode = alignmentOptions.mode; + } else { + beforeColon = multiLineOptions.beforeColon; + afterColon = multiLineOptions.afterColon; + mode = alignmentOptions.mode; + } + + // Conditionally include one space before or after colon + targetWidth += align === "colon" ? beforeColon : afterColon; + + for (let i = 0; i < length; i++) { + const property = properties[i]; + const whitespace = getPropertyWhitespace(property); + + if (whitespace) { + // Object literal getters/setters lack a colon + const width = widths[i]; + + if (align === "value") { + report( + property, + "key", + whitespace.beforeColon, + beforeColon, + mode, + ); + report( + property, + "value", + whitespace.afterColon, + targetWidth - width, + mode, + ); + } else { + // align = "colon" + report( + property, + "key", + whitespace.beforeColon, + targetWidth - width, + mode, + ); + report( + property, + "value", + whitespace.afterColon, + afterColon, + mode, + ); + } + } + } + } + + /** + * Verifies spacing of property conforms to specified options. + * @param {ASTNode} node Property node being evaluated. + * @param {Object} lineOptions Configured singleLine or multiLine options + * @returns {void} + */ + function verifySpacing(node, lineOptions) { + const actual = getPropertyWhitespace(node); + + if (actual) { + // Object literal getters/setters lack colons + report( + node, + "key", + actual.beforeColon, + lineOptions.beforeColon, + lineOptions.mode, + ); + report( + node, + "value", + actual.afterColon, + lineOptions.afterColon, + lineOptions.mode, + ); + } + } + + /** + * Verifies spacing of each property in a list. + * @param {ASTNode[]} properties List of Property AST nodes. + * @param {Object} lineOptions Configured singleLine or multiLine options + * @returns {void} + */ + function verifyListSpacing(properties, lineOptions) { + const length = properties.length; + + for (let i = 0; i < length; i++) { + verifySpacing(properties[i], lineOptions); + } + } + + /** + * Verifies vertical alignment, taking into account groups of properties. + * @param {ASTNode} node ObjectExpression node being evaluated. + * @returns {void} + */ + function verifyAlignment(node) { + createGroups(node).forEach(group => { + const properties = group.filter(isKeyValueProperty); + + if ( + properties.length > 0 && + isSingleLineProperties(properties) + ) { + verifyListSpacing(properties, multiLineOptions); + } else { + verifyGroupAlignment(properties); + } + }); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + if (alignmentOptions) { + // Verify vertical alignment + + return { + ObjectExpression(node) { + if (isSingleLine(node)) { + verifyListSpacing( + node.properties.filter(isKeyValueProperty), + singleLineOptions, + ); + } else { + verifyAlignment(node); + } + }, + }; + } + + // Obey beforeColon and afterColon in each property as configured + return { + Property(node) { + verifySpacing( + node, + isSingleLine(node.parent) + ? singleLineOptions + : multiLineOptions, + ); + }, + }; + }, }; diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index 663e1c1f429f..2e9837bc1c92 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -11,7 +11,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"), - keywords = require("./utils/keywords"); + keywords = require("./utils/keywords"); //------------------------------------------------------------------------------ // Constants @@ -23,18 +23,31 @@ const PREV_TOKEN_M = /^[)\]}>*]$/u; const NEXT_TOKEN_M = /^[{*]$/u; const TEMPLATE_OPEN_PAREN = /\$\{$/u; const TEMPLATE_CLOSE_PAREN = /^\}/u; -const CHECK_TYPE = /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u; -const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of", "set", "yield"]); +const CHECK_TYPE = + /^(?:JSXElement|RegularExpression|String|Template|PrivateIdentifier)$/u; +const KEYS = keywords.concat([ + "as", + "async", + "await", + "from", + "get", + "let", + "of", + "set", + "yield", +]); // check duplications. -(function() { - KEYS.sort(); - for (let i = 1; i < KEYS.length; ++i) { - if (KEYS[i] === KEYS[i - 1]) { - throw new Error(`Duplication was found in the keyword list: ${KEYS[i]}`); - } - } -}()); +(function () { + KEYS.sort(); + for (let i = 1; i < KEYS.length; ++i) { + if (KEYS[i] === KEYS[i - 1]) { + throw new Error( + `Duplication was found in the keyword list: ${KEYS[i]}`, + ); + } + } +})(); //------------------------------------------------------------------------------ // Helpers @@ -46,7 +59,7 @@ const KEYS = keywords.concat(["as", "async", "await", "from", "get", "let", "of" * @returns {boolean} `true` if the token is a "Template" token ends with "${". */ function isOpenParenOfTemplate(token) { - return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); + return token.type === "Template" && TEMPLATE_OPEN_PAREN.test(token.value); } /** @@ -55,7 +68,7 @@ function isOpenParenOfTemplate(token) { * @returns {boolean} `true` if the token is a "Template" token starts with "}". */ function isCloseParenOfTemplate(token) { - return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); + return token.type === "Template" && TEMPLATE_CLOSE_PAREN.test(token.value); } //------------------------------------------------------------------------------ @@ -64,595 +77,625 @@ function isCloseParenOfTemplate(token) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "keyword-spacing", - url: "https://eslint.style/rules/js/keyword-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before and after keywords", - recommended: false, - url: "https://eslint.org/docs/latest/rules/keyword-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - before: { type: "boolean", default: true }, - after: { type: "boolean", default: true }, - overrides: { - type: "object", - properties: KEYS.reduce((retv, key) => { - retv[key] = { - type: "object", - properties: { - before: { type: "boolean" }, - after: { type: "boolean" } - }, - additionalProperties: false - }; - return retv; - }, {}), - additionalProperties: false - } - }, - additionalProperties: false - } - ], - messages: { - expectedBefore: "Expected space(s) before \"{{value}}\".", - expectedAfter: "Expected space(s) after \"{{value}}\".", - unexpectedBefore: "Unexpected space(s) before \"{{value}}\".", - unexpectedAfter: "Unexpected space(s) after \"{{value}}\"." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const tokensToIgnore = new WeakSet(); - - /** - * Reports a given token if there are not space(s) before the token. - * @param {Token} token A token to report. - * @param {RegExp} pattern A pattern of the previous token to check. - * @returns {void} - */ - function expectSpaceBefore(token, pattern) { - const prevToken = sourceCode.getTokenBefore(token); - - if (prevToken && - (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && - !isOpenParenOfTemplate(prevToken) && - !tokensToIgnore.has(prevToken) && - astUtils.isTokenOnSameLine(prevToken, token) && - !sourceCode.isSpaceBetweenTokens(prevToken, token) - ) { - context.report({ - loc: token.loc, - messageId: "expectedBefore", - data: token, - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - } - - /** - * Reports a given token if there are space(s) before the token. - * @param {Token} token A token to report. - * @param {RegExp} pattern A pattern of the previous token to check. - * @returns {void} - */ - function unexpectSpaceBefore(token, pattern) { - const prevToken = sourceCode.getTokenBefore(token); - - if (prevToken && - (CHECK_TYPE.test(prevToken.type) || pattern.test(prevToken.value)) && - !isOpenParenOfTemplate(prevToken) && - !tokensToIgnore.has(prevToken) && - astUtils.isTokenOnSameLine(prevToken, token) && - sourceCode.isSpaceBetweenTokens(prevToken, token) - ) { - context.report({ - loc: { start: prevToken.loc.end, end: token.loc.start }, - messageId: "unexpectedBefore", - data: token, - fix(fixer) { - return fixer.removeRange([prevToken.range[1], token.range[0]]); - } - }); - } - } - - /** - * Reports a given token if there are not space(s) after the token. - * @param {Token} token A token to report. - * @param {RegExp} pattern A pattern of the next token to check. - * @returns {void} - */ - function expectSpaceAfter(token, pattern) { - const nextToken = sourceCode.getTokenAfter(token); - - if (nextToken && - (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && - !isCloseParenOfTemplate(nextToken) && - !tokensToIgnore.has(nextToken) && - astUtils.isTokenOnSameLine(token, nextToken) && - !sourceCode.isSpaceBetweenTokens(token, nextToken) - ) { - context.report({ - loc: token.loc, - messageId: "expectedAfter", - data: token, - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } - } - - /** - * Reports a given token if there are space(s) after the token. - * @param {Token} token A token to report. - * @param {RegExp} pattern A pattern of the next token to check. - * @returns {void} - */ - function unexpectSpaceAfter(token, pattern) { - const nextToken = sourceCode.getTokenAfter(token); - - if (nextToken && - (CHECK_TYPE.test(nextToken.type) || pattern.test(nextToken.value)) && - !isCloseParenOfTemplate(nextToken) && - !tokensToIgnore.has(nextToken) && - astUtils.isTokenOnSameLine(token, nextToken) && - sourceCode.isSpaceBetweenTokens(token, nextToken) - ) { - - context.report({ - loc: { start: token.loc.end, end: nextToken.loc.start }, - messageId: "unexpectedAfter", - data: token, - fix(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } - } - - /** - * Parses the option object and determines check methods for each keyword. - * @param {Object|undefined} options The option object to parse. - * @returns {Object} - Normalized option object. - * Keys are keywords (there are for every keyword). - * Values are instances of `{"before": function, "after": function}`. - */ - function parseOptions(options = {}) { - const before = options.before !== false; - const after = options.after !== false; - const defaultValue = { - before: before ? expectSpaceBefore : unexpectSpaceBefore, - after: after ? expectSpaceAfter : unexpectSpaceAfter - }; - const overrides = (options && options.overrides) || {}; - const retv = Object.create(null); - - for (let i = 0; i < KEYS.length; ++i) { - const key = KEYS[i]; - const override = overrides[key]; - - if (override) { - const thisBefore = ("before" in override) ? override.before : before; - const thisAfter = ("after" in override) ? override.after : after; - - retv[key] = { - before: thisBefore ? expectSpaceBefore : unexpectSpaceBefore, - after: thisAfter ? expectSpaceAfter : unexpectSpaceAfter - }; - } else { - retv[key] = defaultValue; - } - } - - return retv; - } - - const checkMethodMap = parseOptions(context.options[0]); - - /** - * Reports a given token if usage of spacing followed by the token is - * invalid. - * @param {Token} token A token to report. - * @param {RegExp} [pattern] Optional. A pattern of the previous - * token to check. - * @returns {void} - */ - function checkSpacingBefore(token, pattern) { - checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); - } - - /** - * Reports a given token if usage of spacing preceded by the token is - * invalid. - * @param {Token} token A token to report. - * @param {RegExp} [pattern] Optional. A pattern of the next - * token to check. - * @returns {void} - */ - function checkSpacingAfter(token, pattern) { - checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); - } - - /** - * Reports a given token if usage of spacing around the token is invalid. - * @param {Token} token A token to report. - * @returns {void} - */ - function checkSpacingAround(token) { - checkSpacingBefore(token); - checkSpacingAfter(token); - } - - /** - * Reports the first token of a given node if the first token is a keyword - * and usage of spacing around the token is invalid. - * @param {ASTNode|null} node A node to report. - * @returns {void} - */ - function checkSpacingAroundFirstToken(node) { - const firstToken = node && sourceCode.getFirstToken(node); - - if (firstToken && firstToken.type === "Keyword") { - checkSpacingAround(firstToken); - } - } - - /** - * Reports the first token of a given node if the first token is a keyword - * and usage of spacing followed by the token is invalid. - * - * This is used for unary operators (e.g. `typeof`), `function`, and `super`. - * Other rules are handling usage of spacing preceded by those keywords. - * @param {ASTNode|null} node A node to report. - * @returns {void} - */ - function checkSpacingBeforeFirstToken(node) { - const firstToken = node && sourceCode.getFirstToken(node); - - if (firstToken && firstToken.type === "Keyword") { - checkSpacingBefore(firstToken); - } - } - - /** - * Reports the previous token of a given node if the token is a keyword and - * usage of spacing around the token is invalid. - * @param {ASTNode|null} node A node to report. - * @returns {void} - */ - function checkSpacingAroundTokenBefore(node) { - if (node) { - const token = sourceCode.getTokenBefore(node, astUtils.isKeywordToken); - - checkSpacingAround(token); - } - } - - /** - * Reports `async` or `function` keywords of a given node if usage of - * spacing around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForFunction(node) { - const firstToken = node && sourceCode.getFirstToken(node); - - if (firstToken && - ((firstToken.type === "Keyword" && firstToken.value === "function") || - firstToken.value === "async") - ) { - checkSpacingBefore(firstToken); - } - } - - /** - * Reports `class` and `extends` keywords of a given node if usage of - * spacing around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForClass(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.superClass); - } - - /** - * Reports `if` and `else` keywords of a given node if usage of spacing - * around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForIfStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.alternate); - } - - /** - * Reports `try`, `catch`, and `finally` keywords of a given node if usage - * of spacing around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForTryStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundFirstToken(node.handler); - checkSpacingAroundTokenBefore(node.finalizer); - } - - /** - * Reports `do` and `while` keywords of a given node if usage of spacing - * around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForDoWhileStatement(node) { - checkSpacingAroundFirstToken(node); - checkSpacingAroundTokenBefore(node.test); - } - - /** - * Reports `for` and `in` keywords of a given node if usage of spacing - * around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForForInStatement(node) { - checkSpacingAroundFirstToken(node); - - const inToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); - const previousToken = sourceCode.getTokenBefore(inToken); - - if (previousToken.type !== "PrivateIdentifier") { - checkSpacingBefore(inToken); - } - - checkSpacingAfter(inToken); - } - - /** - * Reports `for` and `of` keywords of a given node if usage of spacing - * around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForForOfStatement(node) { - if (node.await) { - checkSpacingBefore(sourceCode.getFirstToken(node, 0)); - checkSpacingAfter(sourceCode.getFirstToken(node, 1)); - } else { - checkSpacingAroundFirstToken(node); - } - - const ofToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); - const previousToken = sourceCode.getTokenBefore(ofToken); - - if (previousToken.type !== "PrivateIdentifier") { - checkSpacingBefore(ofToken); - } - - checkSpacingAfter(ofToken); - } - - /** - * Reports `import`, `export`, `as`, and `from` keywords of a given node if - * usage of spacing around those keywords is invalid. - * - * This rule handles the `*` token in module declarations. - * - * import*as A from "./a"; /*error Expected space(s) after "import". - * error Expected space(s) before "as". - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForModuleDeclaration(node) { - const firstToken = sourceCode.getFirstToken(node); - - checkSpacingBefore(firstToken, PREV_TOKEN_M); - checkSpacingAfter(firstToken, NEXT_TOKEN_M); - - if (node.type === "ExportDefaultDeclaration") { - checkSpacingAround(sourceCode.getTokenAfter(firstToken)); - } - - if (node.type === "ExportAllDeclaration" && node.exported) { - const asToken = sourceCode.getTokenBefore(node.exported); - - checkSpacingBefore(asToken, PREV_TOKEN_M); - checkSpacingAfter(asToken, NEXT_TOKEN_M); - } - - if (node.source) { - const fromToken = sourceCode.getTokenBefore(node.source); - - checkSpacingBefore(fromToken, PREV_TOKEN_M); - checkSpacingAfter(fromToken, NEXT_TOKEN_M); - } - } - - /** - * Reports `as` keyword of a given node if usage of spacing around this - * keyword is invalid. - * @param {ASTNode} node An `ImportSpecifier` node to check. - * @returns {void} - */ - function checkSpacingForImportSpecifier(node) { - if (node.imported.range[0] !== node.local.range[0]) { - const asToken = sourceCode.getTokenBefore(node.local); - - checkSpacingBefore(asToken, PREV_TOKEN_M); - } - } - - /** - * Reports `as` keyword of a given node if usage of spacing around this - * keyword is invalid. - * @param {ASTNode} node An `ExportSpecifier` node to check. - * @returns {void} - */ - function checkSpacingForExportSpecifier(node) { - if (node.local.range[0] !== node.exported.range[0]) { - const asToken = sourceCode.getTokenBefore(node.exported); - - checkSpacingBefore(asToken, PREV_TOKEN_M); - checkSpacingAfter(asToken, NEXT_TOKEN_M); - } - } - - /** - * Reports `as` keyword of a given node if usage of spacing around this - * keyword is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForImportNamespaceSpecifier(node) { - const asToken = sourceCode.getFirstToken(node, 1); - - checkSpacingBefore(asToken, PREV_TOKEN_M); - } - - /** - * Reports `static`, `get`, and `set` keywords of a given node if usage of - * spacing around those keywords is invalid. - * @param {ASTNode} node A node to report. - * @throws {Error} If unable to find token get, set, or async beside method name. - * @returns {void} - */ - function checkSpacingForProperty(node) { - if (node.static) { - checkSpacingAroundFirstToken(node); - } - if (node.kind === "get" || - node.kind === "set" || - ( - (node.method || node.type === "MethodDefinition") && - node.value.async - ) - ) { - const token = sourceCode.getTokenBefore( - node.key, - tok => { - switch (tok.value) { - case "get": - case "set": - case "async": - return true; - default: - return false; - } - } - ); - - if (!token) { - throw new Error("Failed to find token get, set, or async beside method name"); - } - - - checkSpacingAround(token); - } - } - - /** - * Reports `await` keyword of a given node if usage of spacing before - * this keyword is invalid. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function checkSpacingForAwaitExpression(node) { - checkSpacingBefore(sourceCode.getFirstToken(node)); - } - - return { - - // Statements - DebuggerStatement: checkSpacingAroundFirstToken, - WithStatement: checkSpacingAroundFirstToken, - - // Statements - Control flow - BreakStatement: checkSpacingAroundFirstToken, - ContinueStatement: checkSpacingAroundFirstToken, - ReturnStatement: checkSpacingAroundFirstToken, - ThrowStatement: checkSpacingAroundFirstToken, - TryStatement: checkSpacingForTryStatement, - - // Statements - Choice - IfStatement: checkSpacingForIfStatement, - SwitchStatement: checkSpacingAroundFirstToken, - SwitchCase: checkSpacingAroundFirstToken, - - // Statements - Loops - DoWhileStatement: checkSpacingForDoWhileStatement, - ForInStatement: checkSpacingForForInStatement, - ForOfStatement: checkSpacingForForOfStatement, - ForStatement: checkSpacingAroundFirstToken, - WhileStatement: checkSpacingAroundFirstToken, - - // Statements - Declarations - ClassDeclaration: checkSpacingForClass, - ExportNamedDeclaration: checkSpacingForModuleDeclaration, - ExportDefaultDeclaration: checkSpacingForModuleDeclaration, - ExportAllDeclaration: checkSpacingForModuleDeclaration, - FunctionDeclaration: checkSpacingForFunction, - ImportDeclaration: checkSpacingForModuleDeclaration, - VariableDeclaration: checkSpacingAroundFirstToken, - - // Expressions - ArrowFunctionExpression: checkSpacingForFunction, - AwaitExpression: checkSpacingForAwaitExpression, - ClassExpression: checkSpacingForClass, - FunctionExpression: checkSpacingForFunction, - NewExpression: checkSpacingBeforeFirstToken, - Super: checkSpacingBeforeFirstToken, - ThisExpression: checkSpacingBeforeFirstToken, - UnaryExpression: checkSpacingBeforeFirstToken, - YieldExpression: checkSpacingBeforeFirstToken, - - // Others - ImportSpecifier: checkSpacingForImportSpecifier, - ExportSpecifier: checkSpacingForExportSpecifier, - ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, - MethodDefinition: checkSpacingForProperty, - PropertyDefinition: checkSpacingForProperty, - StaticBlock: checkSpacingAroundFirstToken, - Property: checkSpacingForProperty, - - // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b` - "BinaryExpression[operator='>']"(node) { - const operatorToken = sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken); - - tokensToIgnore.add(operatorToken); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "keyword-spacing", + url: "https://eslint.style/rules/js/keyword-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent spacing before and after keywords", + recommended: false, + url: "https://eslint.org/docs/latest/rules/keyword-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { type: "boolean", default: true }, + after: { type: "boolean", default: true }, + overrides: { + type: "object", + properties: KEYS.reduce((retv, key) => { + retv[key] = { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" }, + }, + additionalProperties: false, + }; + return retv; + }, {}), + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + expectedBefore: 'Expected space(s) before "{{value}}".', + expectedAfter: 'Expected space(s) after "{{value}}".', + unexpectedBefore: 'Unexpected space(s) before "{{value}}".', + unexpectedAfter: 'Unexpected space(s) after "{{value}}".', + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const tokensToIgnore = new WeakSet(); + + /** + * Reports a given token if there are not space(s) before the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the previous token to check. + * @returns {void} + */ + function expectSpaceBefore(token, pattern) { + const prevToken = sourceCode.getTokenBefore(token); + + if ( + prevToken && + (CHECK_TYPE.test(prevToken.type) || + pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + !sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: token.loc, + messageId: "expectedBefore", + data: token, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } + } + + /** + * Reports a given token if there are space(s) before the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the previous token to check. + * @returns {void} + */ + function unexpectSpaceBefore(token, pattern) { + const prevToken = sourceCode.getTokenBefore(token); + + if ( + prevToken && + (CHECK_TYPE.test(prevToken.type) || + pattern.test(prevToken.value)) && + !isOpenParenOfTemplate(prevToken) && + !tokensToIgnore.has(prevToken) && + astUtils.isTokenOnSameLine(prevToken, token) && + sourceCode.isSpaceBetweenTokens(prevToken, token) + ) { + context.report({ + loc: { start: prevToken.loc.end, end: token.loc.start }, + messageId: "unexpectedBefore", + data: token, + fix(fixer) { + return fixer.removeRange([ + prevToken.range[1], + token.range[0], + ]); + }, + }); + } + } + + /** + * Reports a given token if there are not space(s) after the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the next token to check. + * @returns {void} + */ + function expectSpaceAfter(token, pattern) { + const nextToken = sourceCode.getTokenAfter(token); + + if ( + nextToken && + (CHECK_TYPE.test(nextToken.type) || + pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + !sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: token.loc, + messageId: "expectedAfter", + data: token, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } + } + + /** + * Reports a given token if there are space(s) after the token. + * @param {Token} token A token to report. + * @param {RegExp} pattern A pattern of the next token to check. + * @returns {void} + */ + function unexpectSpaceAfter(token, pattern) { + const nextToken = sourceCode.getTokenAfter(token); + + if ( + nextToken && + (CHECK_TYPE.test(nextToken.type) || + pattern.test(nextToken.value)) && + !isCloseParenOfTemplate(nextToken) && + !tokensToIgnore.has(nextToken) && + astUtils.isTokenOnSameLine(token, nextToken) && + sourceCode.isSpaceBetweenTokens(token, nextToken) + ) { + context.report({ + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "unexpectedAfter", + data: token, + fix(fixer) { + return fixer.removeRange([ + token.range[1], + nextToken.range[0], + ]); + }, + }); + } + } + + /** + * Parses the option object and determines check methods for each keyword. + * @param {Object|undefined} options The option object to parse. + * @returns {Object} - Normalized option object. + * Keys are keywords (there are for every keyword). + * Values are instances of `{"before": function, "after": function}`. + */ + function parseOptions(options = {}) { + const before = options.before !== false; + const after = options.after !== false; + const defaultValue = { + before: before ? expectSpaceBefore : unexpectSpaceBefore, + after: after ? expectSpaceAfter : unexpectSpaceAfter, + }; + const overrides = (options && options.overrides) || {}; + const retv = Object.create(null); + + for (let i = 0; i < KEYS.length; ++i) { + const key = KEYS[i]; + const override = overrides[key]; + + if (override) { + const thisBefore = + "before" in override ? override.before : before; + const thisAfter = + "after" in override ? override.after : after; + + retv[key] = { + before: thisBefore + ? expectSpaceBefore + : unexpectSpaceBefore, + after: thisAfter + ? expectSpaceAfter + : unexpectSpaceAfter, + }; + } else { + retv[key] = defaultValue; + } + } + + return retv; + } + + const checkMethodMap = parseOptions(context.options[0]); + + /** + * Reports a given token if usage of spacing followed by the token is + * invalid. + * @param {Token} token A token to report. + * @param {RegExp} [pattern] Optional. A pattern of the previous + * token to check. + * @returns {void} + */ + function checkSpacingBefore(token, pattern) { + checkMethodMap[token.value].before(token, pattern || PREV_TOKEN); + } + + /** + * Reports a given token if usage of spacing preceded by the token is + * invalid. + * @param {Token} token A token to report. + * @param {RegExp} [pattern] Optional. A pattern of the next + * token to check. + * @returns {void} + */ + function checkSpacingAfter(token, pattern) { + checkMethodMap[token.value].after(token, pattern || NEXT_TOKEN); + } + + /** + * Reports a given token if usage of spacing around the token is invalid. + * @param {Token} token A token to report. + * @returns {void} + */ + function checkSpacingAround(token) { + checkSpacingBefore(token); + checkSpacingAfter(token); + } + + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing around the token is invalid. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingAroundFirstToken(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingAround(firstToken); + } + } + + /** + * Reports the first token of a given node if the first token is a keyword + * and usage of spacing followed by the token is invalid. + * + * This is used for unary operators (e.g. `typeof`), `function`, and `super`. + * Other rules are handling usage of spacing preceded by those keywords. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingBeforeFirstToken(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if (firstToken && firstToken.type === "Keyword") { + checkSpacingBefore(firstToken); + } + } + + /** + * Reports the previous token of a given node if the token is a keyword and + * usage of spacing around the token is invalid. + * @param {ASTNode|null} node A node to report. + * @returns {void} + */ + function checkSpacingAroundTokenBefore(node) { + if (node) { + const token = sourceCode.getTokenBefore( + node, + astUtils.isKeywordToken, + ); + + checkSpacingAround(token); + } + } + + /** + * Reports `async` or `function` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForFunction(node) { + const firstToken = node && sourceCode.getFirstToken(node); + + if ( + firstToken && + ((firstToken.type === "Keyword" && + firstToken.value === "function") || + firstToken.value === "async") + ) { + checkSpacingBefore(firstToken); + } + } + + /** + * Reports `class` and `extends` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForClass(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.superClass); + } + + /** + * Reports `if` and `else` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForIfStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.alternate); + } + + /** + * Reports `try`, `catch`, and `finally` keywords of a given node if usage + * of spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForTryStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundFirstToken(node.handler); + checkSpacingAroundTokenBefore(node.finalizer); + } + + /** + * Reports `do` and `while` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForDoWhileStatement(node) { + checkSpacingAroundFirstToken(node); + checkSpacingAroundTokenBefore(node.test); + } + + /** + * Reports `for` and `in` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForForInStatement(node) { + checkSpacingAroundFirstToken(node); + + const inToken = sourceCode.getTokenBefore( + node.right, + astUtils.isNotOpeningParenToken, + ); + const previousToken = sourceCode.getTokenBefore(inToken); + + if (previousToken.type !== "PrivateIdentifier") { + checkSpacingBefore(inToken); + } + + checkSpacingAfter(inToken); + } + + /** + * Reports `for` and `of` keywords of a given node if usage of spacing + * around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForForOfStatement(node) { + if (node.await) { + checkSpacingBefore(sourceCode.getFirstToken(node, 0)); + checkSpacingAfter(sourceCode.getFirstToken(node, 1)); + } else { + checkSpacingAroundFirstToken(node); + } + + const ofToken = sourceCode.getTokenBefore( + node.right, + astUtils.isNotOpeningParenToken, + ); + const previousToken = sourceCode.getTokenBefore(ofToken); + + if (previousToken.type !== "PrivateIdentifier") { + checkSpacingBefore(ofToken); + } + + checkSpacingAfter(ofToken); + } + + /** + * Reports `import`, `export`, `as`, and `from` keywords of a given node if + * usage of spacing around those keywords is invalid. + * + * This rule handles the `*` token in module declarations. + * + * import*as A from "./a"; /*error Expected space(s) after "import". + * error Expected space(s) before "as". + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForModuleDeclaration(node) { + const firstToken = sourceCode.getFirstToken(node); + + checkSpacingBefore(firstToken, PREV_TOKEN_M); + checkSpacingAfter(firstToken, NEXT_TOKEN_M); + + if (node.type === "ExportDefaultDeclaration") { + checkSpacingAround(sourceCode.getTokenAfter(firstToken)); + } + + if (node.type === "ExportAllDeclaration" && node.exported) { + const asToken = sourceCode.getTokenBefore(node.exported); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + checkSpacingAfter(asToken, NEXT_TOKEN_M); + } + + if (node.source) { + const fromToken = sourceCode.getTokenBefore(node.source); + + checkSpacingBefore(fromToken, PREV_TOKEN_M); + checkSpacingAfter(fromToken, NEXT_TOKEN_M); + } + } + + /** + * Reports `as` keyword of a given node if usage of spacing around this + * keyword is invalid. + * @param {ASTNode} node An `ImportSpecifier` node to check. + * @returns {void} + */ + function checkSpacingForImportSpecifier(node) { + if (node.imported.range[0] !== node.local.range[0]) { + const asToken = sourceCode.getTokenBefore(node.local); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + } + } + + /** + * Reports `as` keyword of a given node if usage of spacing around this + * keyword is invalid. + * @param {ASTNode} node An `ExportSpecifier` node to check. + * @returns {void} + */ + function checkSpacingForExportSpecifier(node) { + if (node.local.range[0] !== node.exported.range[0]) { + const asToken = sourceCode.getTokenBefore(node.exported); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + checkSpacingAfter(asToken, NEXT_TOKEN_M); + } + } + + /** + * Reports `as` keyword of a given node if usage of spacing around this + * keyword is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForImportNamespaceSpecifier(node) { + const asToken = sourceCode.getFirstToken(node, 1); + + checkSpacingBefore(asToken, PREV_TOKEN_M); + } + + /** + * Reports `static`, `get`, and `set` keywords of a given node if usage of + * spacing around those keywords is invalid. + * @param {ASTNode} node A node to report. + * @throws {Error} If unable to find token get, set, or async beside method name. + * @returns {void} + */ + function checkSpacingForProperty(node) { + if (node.static) { + checkSpacingAroundFirstToken(node); + } + if ( + node.kind === "get" || + node.kind === "set" || + ((node.method || node.type === "MethodDefinition") && + node.value.async) + ) { + const token = sourceCode.getTokenBefore(node.key, tok => { + switch (tok.value) { + case "get": + case "set": + case "async": + return true; + default: + return false; + } + }); + + if (!token) { + throw new Error( + "Failed to find token get, set, or async beside method name", + ); + } + + checkSpacingAround(token); + } + } + + /** + * Reports `await` keyword of a given node if usage of spacing before + * this keyword is invalid. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function checkSpacingForAwaitExpression(node) { + checkSpacingBefore(sourceCode.getFirstToken(node)); + } + + return { + // Statements + DebuggerStatement: checkSpacingAroundFirstToken, + WithStatement: checkSpacingAroundFirstToken, + + // Statements - Control flow + BreakStatement: checkSpacingAroundFirstToken, + ContinueStatement: checkSpacingAroundFirstToken, + ReturnStatement: checkSpacingAroundFirstToken, + ThrowStatement: checkSpacingAroundFirstToken, + TryStatement: checkSpacingForTryStatement, + + // Statements - Choice + IfStatement: checkSpacingForIfStatement, + SwitchStatement: checkSpacingAroundFirstToken, + SwitchCase: checkSpacingAroundFirstToken, + + // Statements - Loops + DoWhileStatement: checkSpacingForDoWhileStatement, + ForInStatement: checkSpacingForForInStatement, + ForOfStatement: checkSpacingForForOfStatement, + ForStatement: checkSpacingAroundFirstToken, + WhileStatement: checkSpacingAroundFirstToken, + + // Statements - Declarations + ClassDeclaration: checkSpacingForClass, + ExportNamedDeclaration: checkSpacingForModuleDeclaration, + ExportDefaultDeclaration: checkSpacingForModuleDeclaration, + ExportAllDeclaration: checkSpacingForModuleDeclaration, + FunctionDeclaration: checkSpacingForFunction, + ImportDeclaration: checkSpacingForModuleDeclaration, + VariableDeclaration: checkSpacingAroundFirstToken, + + // Expressions + ArrowFunctionExpression: checkSpacingForFunction, + AwaitExpression: checkSpacingForAwaitExpression, + ClassExpression: checkSpacingForClass, + FunctionExpression: checkSpacingForFunction, + NewExpression: checkSpacingBeforeFirstToken, + Super: checkSpacingBeforeFirstToken, + ThisExpression: checkSpacingBeforeFirstToken, + UnaryExpression: checkSpacingBeforeFirstToken, + YieldExpression: checkSpacingBeforeFirstToken, + + // Others + ImportSpecifier: checkSpacingForImportSpecifier, + ExportSpecifier: checkSpacingForExportSpecifier, + ImportNamespaceSpecifier: checkSpacingForImportNamespaceSpecifier, + MethodDefinition: checkSpacingForProperty, + PropertyDefinition: checkSpacingForProperty, + StaticBlock: checkSpacingAroundFirstToken, + Property: checkSpacingForProperty, + + // To avoid conflicts with `space-infix-ops`, e.g. `a > this.b` + "BinaryExpression[operator='>']"(node) { + const operatorToken = sourceCode.getTokenBefore( + node.right, + astUtils.isNotOpeningParenToken, + ); + + tokensToIgnore.add(operatorToken); + }, + }; + }, }; diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js index 0e38341714c0..39b3da7e2069 100644 --- a/lib/rules/line-comment-position.js +++ b/lib/rules/line-comment-position.js @@ -13,131 +13,145 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "line-comment-position", - url: "https://eslint.style/rules/js/line-comment-position" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce position of line comments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/line-comment-position" - }, - - schema: [ - { - oneOf: [ - { - enum: ["above", "beside"] - }, - { - type: "object", - properties: { - position: { - enum: ["above", "beside"] - }, - ignorePattern: { - type: "string" - }, - applyDefaultPatterns: { - type: "boolean" - }, - applyDefaultIgnorePatterns: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - } - ], - messages: { - above: "Expected comment to be above code.", - beside: "Expected comment to be beside code." - } - }, - - create(context) { - const options = context.options[0]; - - let above, - ignorePattern, - applyDefaultIgnorePatterns = true; - - if (!options || typeof options === "string") { - above = !options || options === "above"; - - } else { - above = !options.position || options.position === "above"; - ignorePattern = options.ignorePattern; - - if (Object.hasOwn(options, "applyDefaultIgnorePatterns")) { - applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns; - } else { - applyDefaultIgnorePatterns = options.applyDefaultPatterns !== false; - } - } - - const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; - const fallThroughRegExp = /^\s*falls?\s?through/u; - const customIgnoreRegExp = new RegExp(ignorePattern, "u"); - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program() { - const comments = sourceCode.getAllComments(); - - comments.filter(token => token.type === "Line").forEach(node => { - if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) { - return; - } - - if (ignorePattern && customIgnoreRegExp.test(node.value)) { - return; - } - - const previous = sourceCode.getTokenBefore(node, { includeComments: true }); - const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line; - - if (above) { - if (isOnSameLine) { - context.report({ - node, - messageId: "above" - }); - } - } else { - if (!isOnSameLine) { - context.report({ - node, - messageId: "beside" - }); - } - } - }); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "line-comment-position", + url: "https://eslint.style/rules/js/line-comment-position", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce position of line comments", + recommended: false, + url: "https://eslint.org/docs/latest/rules/line-comment-position", + }, + + schema: [ + { + oneOf: [ + { + enum: ["above", "beside"], + }, + { + type: "object", + properties: { + position: { + enum: ["above", "beside"], + }, + ignorePattern: { + type: "string", + }, + applyDefaultPatterns: { + type: "boolean", + }, + applyDefaultIgnorePatterns: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + above: "Expected comment to be above code.", + beside: "Expected comment to be beside code.", + }, + }, + + create(context) { + const options = context.options[0]; + + let above, + ignorePattern, + applyDefaultIgnorePatterns = true; + + if (!options || typeof options === "string") { + above = !options || options === "above"; + } else { + above = !options.position || options.position === "above"; + ignorePattern = options.ignorePattern; + + if (Object.hasOwn(options, "applyDefaultIgnorePatterns")) { + applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns; + } else { + applyDefaultIgnorePatterns = + options.applyDefaultPatterns !== false; + } + } + + const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; + const fallThroughRegExp = /^\s*falls?\s?through/u; + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments + .filter(token => token.type === "Line") + .forEach(node => { + if ( + applyDefaultIgnorePatterns && + (defaultIgnoreRegExp.test(node.value) || + fallThroughRegExp.test(node.value)) + ) { + return; + } + + if ( + ignorePattern && + customIgnoreRegExp.test(node.value) + ) { + return; + } + + const previous = sourceCode.getTokenBefore(node, { + includeComments: true, + }); + const isOnSameLine = + previous && + previous.loc.end.line === node.loc.start.line; + + if (above) { + if (isOnSameLine) { + context.report({ + node, + messageId: "above", + }); + } + } else { + if (!isOnSameLine) { + context.report({ + node, + messageId: "beside", + }); + } + } + }); + }, + }; + }, }; diff --git a/lib/rules/linebreak-style.js b/lib/rules/linebreak-style.js index 0e8155a01451..e6e51dbe749d 100644 --- a/lib/rules/linebreak-style.js +++ b/lib/rules/linebreak-style.js @@ -18,109 +18,110 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "linebreak-style", - url: "https://eslint.style/rules/js/linebreak-style" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent linebreak style", - recommended: false, - url: "https://eslint.org/docs/latest/rules/linebreak-style" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["unix", "windows"] - } - ], - messages: { - expectedLF: "Expected linebreaks to be 'LF' but found 'CRLF'.", - expectedCRLF: "Expected linebreaks to be 'CRLF' but found 'LF'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Builds a fix function that replaces text at the specified range in the source text. - * @param {int[]} range The range to replace - * @param {string} text The text to insert. - * @returns {Function} Fixer function - * @private - */ - function createFix(range, text) { - return function(fixer) { - return fixer.replaceTextRange(range, text); - }; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program: function checkForLinebreakStyle(node) { - const linebreakStyle = context.options[0] || "unix", - expectedLF = linebreakStyle === "unix", - expectedLFChars = expectedLF ? "\n" : "\r\n", - source = sourceCode.getText(), - pattern = astUtils.createGlobalLinebreakMatcher(); - let match; - - let i = 0; - - while ((match = pattern.exec(source)) !== null) { - i++; - if (match[0] === expectedLFChars) { - continue; - } - - const index = match.index; - const range = [index, index + match[0].length]; - - context.report({ - node, - loc: { - start: { - line: i, - column: sourceCode.lines[i - 1].length - }, - end: { - line: i + 1, - column: 0 - } - }, - messageId: expectedLF ? "expectedLF" : "expectedCRLF", - fix: createFix(range, expectedLFChars) - }); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "linebreak-style", + url: "https://eslint.style/rules/js/linebreak-style", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent linebreak style", + recommended: false, + url: "https://eslint.org/docs/latest/rules/linebreak-style", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["unix", "windows"], + }, + ], + messages: { + expectedLF: "Expected linebreaks to be 'LF' but found 'CRLF'.", + expectedCRLF: "Expected linebreaks to be 'CRLF' but found 'LF'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Builds a fix function that replaces text at the specified range in the source text. + * @param {int[]} range The range to replace + * @param {string} text The text to insert. + * @returns {Function} Fixer function + * @private + */ + function createFix(range, text) { + return function (fixer) { + return fixer.replaceTextRange(range, text); + }; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkForLinebreakStyle(node) { + const linebreakStyle = context.options[0] || "unix", + expectedLF = linebreakStyle === "unix", + expectedLFChars = expectedLF ? "\n" : "\r\n", + source = sourceCode.getText(), + pattern = astUtils.createGlobalLinebreakMatcher(); + let match; + + let i = 0; + + while ((match = pattern.exec(source)) !== null) { + i++; + if (match[0] === expectedLFChars) { + continue; + } + + const index = match.index; + const range = [index, index + match[0].length]; + + context.report({ + node, + loc: { + start: { + line: i, + column: sourceCode.lines[i - 1].length, + }, + end: { + line: i + 1, + column: 0, + }, + }, + messageId: expectedLF ? "expectedLF" : "expectedCRLF", + fix: createFix(range, expectedLFChars), + }); + } + }, + }; + }, }; diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js index 2906e9934666..1e5043a7e000 100644 --- a/lib/rules/lines-around-comment.js +++ b/lib/rules/lines-around-comment.js @@ -21,12 +21,15 @@ const astUtils = require("./utils/ast-utils"); * @returns {Array} An array of line numbers. */ function getEmptyLineNums(lines) { - const emptyLines = lines.map((line, i) => ({ - code: line.trim(), - num: i + 1 - })).filter(line => !line.code).map(line => line.num); - - return emptyLines; + const emptyLines = lines + .map((line, i) => ({ + code: line.trim(), + num: i + 1, + })) + .filter(line => !line.code) + .map(line => line.num); + + return emptyLines; } /** @@ -35,15 +38,15 @@ function getEmptyLineNums(lines) { * @returns {Array} An array of line numbers. */ function getCommentLineNums(comments) { - const lines = []; + const lines = []; - comments.forEach(token => { - const start = token.loc.start.line; - const end = token.loc.end.line; + comments.forEach(token => { + const start = token.loc.start.line; + const end = token.loc.end.line; - lines.push(start, end); - }); - return lines; + lines.push(start, end); + }); + return lines; } //------------------------------------------------------------------------------ @@ -52,438 +55,527 @@ function getCommentLineNums(comments) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "lines-around-comment", - url: "https://eslint.style/rules/js/lines-around-comment" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require empty lines around comments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/lines-around-comment" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - beforeBlockComment: { - type: "boolean", - default: true - }, - afterBlockComment: { - type: "boolean", - default: false - }, - beforeLineComment: { - type: "boolean", - default: false - }, - afterLineComment: { - type: "boolean", - default: false - }, - allowBlockStart: { - type: "boolean", - default: false - }, - allowBlockEnd: { - type: "boolean", - default: false - }, - allowClassStart: { - type: "boolean" - }, - allowClassEnd: { - type: "boolean" - }, - allowObjectStart: { - type: "boolean" - }, - allowObjectEnd: { - type: "boolean" - }, - allowArrayStart: { - type: "boolean" - }, - allowArrayEnd: { - type: "boolean" - }, - ignorePattern: { - type: "string" - }, - applyDefaultIgnorePatterns: { - type: "boolean" - }, - afterHashbangComment: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - messages: { - after: "Expected line after comment.", - before: "Expected line before comment." - } - }, - - create(context) { - - const options = Object.assign({}, context.options[0]); - const ignorePattern = options.ignorePattern; - const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; - const customIgnoreRegExp = new RegExp(ignorePattern, "u"); - const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; - - options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; - - const sourceCode = context.sourceCode; - - const lines = sourceCode.lines, - numLines = lines.length + 1, - comments = sourceCode.getAllComments(), - commentLines = getCommentLineNums(comments), - emptyLines = getEmptyLineNums(lines), - commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); - - /** - * Returns whether or not comments are on lines starting with or ending with code - * @param {token} token The comment token to check. - * @returns {boolean} True if the comment is not alone. - */ - function codeAroundComment(token) { - let currentToken = token; - - do { - currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true }); - } while (currentToken && astUtils.isCommentToken(currentToken)); - - if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) { - return true; - } - - currentToken = token; - do { - currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true }); - } while (currentToken && astUtils.isCommentToken(currentToken)); - - if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) { - return true; - } - - return false; - } - - /** - * Returns whether or not comments are inside a node type or not. - * @param {ASTNode} parent The Comment parent node. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is inside nodeType. - */ - function isParentNodeType(parent, nodeType) { - return parent.type === nodeType || - (parent.body && parent.body.type === nodeType) || - (parent.consequent && parent.consequent.type === nodeType); - } - - /** - * Returns the parent node that contains the given token. - * @param {token} token The token to check. - * @returns {ASTNode|null} The parent node that contains the given token. - */ - function getParentNodeOfToken(token) { - const node = sourceCode.getNodeByRangeIndex(token.range[0]); - - /* - * For the purpose of this rule, the comment token is in a `StaticBlock` node only - * if it's inside the braces of that `StaticBlock` node. - * - * Example where this function returns `null`: - * - * static - * // comment - * { - * } - * - * Example where this function returns `StaticBlock` node: - * - * static - * { - * // comment - * } - * - */ - if (node && node.type === "StaticBlock") { - const openingBrace = sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token - - return token.range[0] >= openingBrace.range[0] - ? node - : null; - } - - return node; - } - - /** - * Returns whether or not comments are at the parent start or not. - * @param {token} token The Comment token. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is at parent start. - */ - function isCommentAtParentStart(token, nodeType) { - const parent = getParentNodeOfToken(token); - - if (parent && isParentNodeType(parent, nodeType)) { - let parentStartNodeOrToken = parent; - - if (parent.type === "StaticBlock") { - parentStartNodeOrToken = sourceCode.getFirstToken(parent, { skip: 1 }); // opening brace of the static block - } else if (parent.type === "SwitchStatement") { - parentStartNodeOrToken = sourceCode.getTokenAfter(parent.discriminant, { - filter: astUtils.isOpeningBraceToken - }); // opening brace of the switch statement - } - - return token.loc.start.line - parentStartNodeOrToken.loc.start.line === 1; - } - - return false; - } - - /** - * Returns whether or not comments are at the parent end or not. - * @param {token} token The Comment token. - * @param {string} nodeType The parent type to check against. - * @returns {boolean} True if the comment is at parent end. - */ - function isCommentAtParentEnd(token, nodeType) { - const parent = getParentNodeOfToken(token); - - return !!parent && isParentNodeType(parent, nodeType) && - parent.loc.end.line - token.loc.end.line === 1; - } - - /** - * Returns whether or not comments are at the block start or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at block start. - */ - function isCommentAtBlockStart(token) { - return ( - isCommentAtParentStart(token, "ClassBody") || - isCommentAtParentStart(token, "BlockStatement") || - isCommentAtParentStart(token, "StaticBlock") || - isCommentAtParentStart(token, "SwitchCase") || - isCommentAtParentStart(token, "SwitchStatement") - ); - } - - /** - * Returns whether or not comments are at the block end or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at block end. - */ - function isCommentAtBlockEnd(token) { - return ( - isCommentAtParentEnd(token, "ClassBody") || - isCommentAtParentEnd(token, "BlockStatement") || - isCommentAtParentEnd(token, "StaticBlock") || - isCommentAtParentEnd(token, "SwitchCase") || - isCommentAtParentEnd(token, "SwitchStatement") - ); - } - - /** - * Returns whether or not comments are at the class start or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at class start. - */ - function isCommentAtClassStart(token) { - return isCommentAtParentStart(token, "ClassBody"); - } - - /** - * Returns whether or not comments are at the class end or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at class end. - */ - function isCommentAtClassEnd(token) { - return isCommentAtParentEnd(token, "ClassBody"); - } - - /** - * Returns whether or not comments are at the object start or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at object start. - */ - function isCommentAtObjectStart(token) { - return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern"); - } - - /** - * Returns whether or not comments are at the object end or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at object end. - */ - function isCommentAtObjectEnd(token) { - return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern"); - } - - /** - * Returns whether or not comments are at the array start or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at array start. - */ - function isCommentAtArrayStart(token) { - return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern"); - } - - /** - * Returns whether or not comments are at the array end or not. - * @param {token} token The Comment token. - * @returns {boolean} True if the comment is at array end. - */ - function isCommentAtArrayEnd(token) { - return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern"); - } - - /** - * Checks if a comment token has lines around it (ignores inline comments) - * @param {token} token The Comment token. - * @param {Object} opts Options to determine the newline. - * @param {boolean} opts.after Should have a newline after this line. - * @param {boolean} opts.before Should have a newline before this line. - * @returns {void} - */ - function checkForEmptyLine(token, opts) { - if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) { - return; - } - - if (ignorePattern && customIgnoreRegExp.test(token.value)) { - return; - } - - let after = opts.after, - before = opts.before; - - const prevLineNum = token.loc.start.line - 1, - nextLineNum = token.loc.end.line + 1, - commentIsNotAlone = codeAroundComment(token); - - const blockStartAllowed = options.allowBlockStart && - isCommentAtBlockStart(token) && - !(options.allowClassStart === false && - isCommentAtClassStart(token)), - blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)), - classStartAllowed = options.allowClassStart && isCommentAtClassStart(token), - classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token), - objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token), - objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token), - arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token), - arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token); - - const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed; - const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed; - - // ignore top of the file and bottom of the file - if (prevLineNum < 1) { - before = false; - } - if (nextLineNum >= numLines) { - after = false; - } - - // we ignore all inline comments - if (commentIsNotAlone) { - return; - } - - const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true }); - const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true }); - - // check for newline before - if (!exceptionStartAllowed && before && !commentAndEmptyLines.has(prevLineNum) && - !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) { - const lineStart = token.range[0] - token.loc.start.column; - const range = [lineStart, lineStart]; - - context.report({ - node: token, - messageId: "before", - fix(fixer) { - return fixer.insertTextBeforeRange(range, "\n"); - } - }); - } - - // check for newline after - if (!exceptionEndAllowed && after && !commentAndEmptyLines.has(nextLineNum) && - !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) { - context.report({ - node: token, - messageId: "after", - fix(fixer) { - return fixer.insertTextAfter(token, "\n"); - } - }); - } - - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program() { - comments.forEach(token => { - if (token.type === "Line") { - if (options.beforeLineComment || options.afterLineComment) { - checkForEmptyLine(token, { - after: options.afterLineComment, - before: options.beforeLineComment - }); - } - } else if (token.type === "Block") { - if (options.beforeBlockComment || options.afterBlockComment) { - checkForEmptyLine(token, { - after: options.afterBlockComment, - before: options.beforeBlockComment - }); - } - } else if (token.type === "Shebang") { - if (options.afterHashbangComment) { - checkForEmptyLine(token, { - after: options.afterHashbangComment, - before: false - }); - } - } - }); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "lines-around-comment", + url: "https://eslint.style/rules/js/lines-around-comment", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require empty lines around comments", + recommended: false, + url: "https://eslint.org/docs/latest/rules/lines-around-comment", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + beforeBlockComment: { + type: "boolean", + default: true, + }, + afterBlockComment: { + type: "boolean", + default: false, + }, + beforeLineComment: { + type: "boolean", + default: false, + }, + afterLineComment: { + type: "boolean", + default: false, + }, + allowBlockStart: { + type: "boolean", + default: false, + }, + allowBlockEnd: { + type: "boolean", + default: false, + }, + allowClassStart: { + type: "boolean", + }, + allowClassEnd: { + type: "boolean", + }, + allowObjectStart: { + type: "boolean", + }, + allowObjectEnd: { + type: "boolean", + }, + allowArrayStart: { + type: "boolean", + }, + allowArrayEnd: { + type: "boolean", + }, + ignorePattern: { + type: "string", + }, + applyDefaultIgnorePatterns: { + type: "boolean", + }, + afterHashbangComment: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + after: "Expected line after comment.", + before: "Expected line before comment.", + }, + }, + + create(context) { + const options = Object.assign({}, context.options[0]); + const ignorePattern = options.ignorePattern; + const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); + const applyDefaultIgnorePatterns = + options.applyDefaultIgnorePatterns !== false; + + options.beforeBlockComment = + typeof options.beforeBlockComment !== "undefined" + ? options.beforeBlockComment + : true; + + const sourceCode = context.sourceCode; + + const lines = sourceCode.lines, + numLines = lines.length + 1, + comments = sourceCode.getAllComments(), + commentLines = getCommentLineNums(comments), + emptyLines = getEmptyLineNums(lines), + commentAndEmptyLines = new Set(commentLines.concat(emptyLines)); + + /** + * Returns whether or not comments are on lines starting with or ending with code + * @param {token} token The comment token to check. + * @returns {boolean} True if the comment is not alone. + */ + function codeAroundComment(token) { + let currentToken = token; + + do { + currentToken = sourceCode.getTokenBefore(currentToken, { + includeComments: true, + }); + } while (currentToken && astUtils.isCommentToken(currentToken)); + + if ( + currentToken && + astUtils.isTokenOnSameLine(currentToken, token) + ) { + return true; + } + + currentToken = token; + do { + currentToken = sourceCode.getTokenAfter(currentToken, { + includeComments: true, + }); + } while (currentToken && astUtils.isCommentToken(currentToken)); + + if ( + currentToken && + astUtils.isTokenOnSameLine(token, currentToken) + ) { + return true; + } + + return false; + } + + /** + * Returns whether or not comments are inside a node type or not. + * @param {ASTNode} parent The Comment parent node. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is inside nodeType. + */ + function isParentNodeType(parent, nodeType) { + return ( + parent.type === nodeType || + (parent.body && parent.body.type === nodeType) || + (parent.consequent && parent.consequent.type === nodeType) + ); + } + + /** + * Returns the parent node that contains the given token. + * @param {token} token The token to check. + * @returns {ASTNode|null} The parent node that contains the given token. + */ + function getParentNodeOfToken(token) { + const node = sourceCode.getNodeByRangeIndex(token.range[0]); + + /* + * For the purpose of this rule, the comment token is in a `StaticBlock` node only + * if it's inside the braces of that `StaticBlock` node. + * + * Example where this function returns `null`: + * + * static + * // comment + * { + * } + * + * Example where this function returns `StaticBlock` node: + * + * static + * { + * // comment + * } + * + */ + if (node && node.type === "StaticBlock") { + const openingBrace = sourceCode.getFirstToken(node, { + skip: 1, + }); // skip the `static` token + + return token.range[0] >= openingBrace.range[0] ? node : null; + } + + return node; + } + + /** + * Returns whether or not comments are at the parent start or not. + * @param {token} token The Comment token. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent start. + */ + function isCommentAtParentStart(token, nodeType) { + const parent = getParentNodeOfToken(token); + + if (parent && isParentNodeType(parent, nodeType)) { + let parentStartNodeOrToken = parent; + + if (parent.type === "StaticBlock") { + parentStartNodeOrToken = sourceCode.getFirstToken(parent, { + skip: 1, + }); // opening brace of the static block + } else if (parent.type === "SwitchStatement") { + parentStartNodeOrToken = sourceCode.getTokenAfter( + parent.discriminant, + { + filter: astUtils.isOpeningBraceToken, + }, + ); // opening brace of the switch statement + } + + return ( + token.loc.start.line - + parentStartNodeOrToken.loc.start.line === + 1 + ); + } + + return false; + } + + /** + * Returns whether or not comments are at the parent end or not. + * @param {token} token The Comment token. + * @param {string} nodeType The parent type to check against. + * @returns {boolean} True if the comment is at parent end. + */ + function isCommentAtParentEnd(token, nodeType) { + const parent = getParentNodeOfToken(token); + + return ( + !!parent && + isParentNodeType(parent, nodeType) && + parent.loc.end.line - token.loc.end.line === 1 + ); + } + + /** + * Returns whether or not comments are at the block start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at block start. + */ + function isCommentAtBlockStart(token) { + return ( + isCommentAtParentStart(token, "ClassBody") || + isCommentAtParentStart(token, "BlockStatement") || + isCommentAtParentStart(token, "StaticBlock") || + isCommentAtParentStart(token, "SwitchCase") || + isCommentAtParentStart(token, "SwitchStatement") + ); + } + + /** + * Returns whether or not comments are at the block end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at block end. + */ + function isCommentAtBlockEnd(token) { + return ( + isCommentAtParentEnd(token, "ClassBody") || + isCommentAtParentEnd(token, "BlockStatement") || + isCommentAtParentEnd(token, "StaticBlock") || + isCommentAtParentEnd(token, "SwitchCase") || + isCommentAtParentEnd(token, "SwitchStatement") + ); + } + + /** + * Returns whether or not comments are at the class start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at class start. + */ + function isCommentAtClassStart(token) { + return isCommentAtParentStart(token, "ClassBody"); + } + + /** + * Returns whether or not comments are at the class end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at class end. + */ + function isCommentAtClassEnd(token) { + return isCommentAtParentEnd(token, "ClassBody"); + } + + /** + * Returns whether or not comments are at the object start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at object start. + */ + function isCommentAtObjectStart(token) { + return ( + isCommentAtParentStart(token, "ObjectExpression") || + isCommentAtParentStart(token, "ObjectPattern") + ); + } + + /** + * Returns whether or not comments are at the object end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at object end. + */ + function isCommentAtObjectEnd(token) { + return ( + isCommentAtParentEnd(token, "ObjectExpression") || + isCommentAtParentEnd(token, "ObjectPattern") + ); + } + + /** + * Returns whether or not comments are at the array start or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at array start. + */ + function isCommentAtArrayStart(token) { + return ( + isCommentAtParentStart(token, "ArrayExpression") || + isCommentAtParentStart(token, "ArrayPattern") + ); + } + + /** + * Returns whether or not comments are at the array end or not. + * @param {token} token The Comment token. + * @returns {boolean} True if the comment is at array end. + */ + function isCommentAtArrayEnd(token) { + return ( + isCommentAtParentEnd(token, "ArrayExpression") || + isCommentAtParentEnd(token, "ArrayPattern") + ); + } + + /** + * Checks if a comment token has lines around it (ignores inline comments) + * @param {token} token The Comment token. + * @param {Object} opts Options to determine the newline. + * @param {boolean} opts.after Should have a newline after this line. + * @param {boolean} opts.before Should have a newline before this line. + * @returns {void} + */ + function checkForEmptyLine(token, opts) { + if ( + applyDefaultIgnorePatterns && + defaultIgnoreRegExp.test(token.value) + ) { + return; + } + + if (ignorePattern && customIgnoreRegExp.test(token.value)) { + return; + } + + let after = opts.after, + before = opts.before; + + const prevLineNum = token.loc.start.line - 1, + nextLineNum = token.loc.end.line + 1, + commentIsNotAlone = codeAroundComment(token); + + const blockStartAllowed = + options.allowBlockStart && + isCommentAtBlockStart(token) && + !( + options.allowClassStart === false && + isCommentAtClassStart(token) + ), + blockEndAllowed = + options.allowBlockEnd && + isCommentAtBlockEnd(token) && + !( + options.allowClassEnd === false && + isCommentAtClassEnd(token) + ), + classStartAllowed = + options.allowClassStart && isCommentAtClassStart(token), + classEndAllowed = + options.allowClassEnd && isCommentAtClassEnd(token), + objectStartAllowed = + options.allowObjectStart && isCommentAtObjectStart(token), + objectEndAllowed = + options.allowObjectEnd && isCommentAtObjectEnd(token), + arrayStartAllowed = + options.allowArrayStart && isCommentAtArrayStart(token), + arrayEndAllowed = + options.allowArrayEnd && isCommentAtArrayEnd(token); + + const exceptionStartAllowed = + blockStartAllowed || + classStartAllowed || + objectStartAllowed || + arrayStartAllowed; + const exceptionEndAllowed = + blockEndAllowed || + classEndAllowed || + objectEndAllowed || + arrayEndAllowed; + + // ignore top of the file and bottom of the file + if (prevLineNum < 1) { + before = false; + } + if (nextLineNum >= numLines) { + after = false; + } + + // we ignore all inline comments + if (commentIsNotAlone) { + return; + } + + const previousTokenOrComment = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + const nextTokenOrComment = sourceCode.getTokenAfter(token, { + includeComments: true, + }); + + // check for newline before + if ( + !exceptionStartAllowed && + before && + !commentAndEmptyLines.has(prevLineNum) && + !( + astUtils.isCommentToken(previousTokenOrComment) && + astUtils.isTokenOnSameLine(previousTokenOrComment, token) + ) + ) { + const lineStart = token.range[0] - token.loc.start.column; + const range = [lineStart, lineStart]; + + context.report({ + node: token, + messageId: "before", + fix(fixer) { + return fixer.insertTextBeforeRange(range, "\n"); + }, + }); + } + + // check for newline after + if ( + !exceptionEndAllowed && + after && + !commentAndEmptyLines.has(nextLineNum) && + !( + astUtils.isCommentToken(nextTokenOrComment) && + astUtils.isTokenOnSameLine(token, nextTokenOrComment) + ) + ) { + context.report({ + node: token, + messageId: "after", + fix(fixer) { + return fixer.insertTextAfter(token, "\n"); + }, + }); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + comments.forEach(token => { + if (token.type === "Line") { + if ( + options.beforeLineComment || + options.afterLineComment + ) { + checkForEmptyLine(token, { + after: options.afterLineComment, + before: options.beforeLineComment, + }); + } + } else if (token.type === "Block") { + if ( + options.beforeBlockComment || + options.afterBlockComment + ) { + checkForEmptyLine(token, { + after: options.afterBlockComment, + before: options.beforeBlockComment, + }); + } + } else if (token.type === "Shebang") { + if (options.afterHashbangComment) { + checkForEmptyLine(token, { + after: options.afterHashbangComment, + before: false, + }); + } + } + }); + }, + }; + }, }; diff --git a/lib/rules/lines-around-directive.js b/lib/rules/lines-around-directive.js index 91040c82fa9e..3ed512d9a30d 100644 --- a/lib/rules/lines-around-directive.js +++ b/lib/rules/lines-around-directive.js @@ -14,206 +14,236 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", - - docs: { - description: "Require or disallow newlines around directives", - recommended: false, - url: "https://eslint.org/docs/latest/rules/lines-around-directive" - }, - - schema: [{ - oneOf: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - before: { - enum: ["always", "never"] - }, - after: { - enum: ["always", "never"] - } - }, - additionalProperties: false, - minProperties: 2 - } - ] - }], - - fixable: "whitespace", - messages: { - expected: "Expected newline {{location}} \"{{value}}\" directive.", - unexpected: "Unexpected newline {{location}} \"{{value}}\" directive." - }, - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "padding-line-between-statements", - url: "https://eslint.style/rules/js/padding-line-between-statements" - } - } - ] - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const config = context.options[0] || "always"; - const expectLineBefore = typeof config === "string" ? config : config.before; - const expectLineAfter = typeof config === "string" ? config : config.after; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if node is preceded by a blank newline. - * @param {ASTNode} node Node to check. - * @returns {boolean} Whether or not the passed in node is preceded by a blank newline. - */ - function hasNewlineBefore(node) { - const tokenBefore = sourceCode.getTokenBefore(node, { includeComments: true }); - const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0; - - return node.loc.start.line - tokenLineBefore >= 2; - } - - /** - * Gets the last token of a node that is on the same line as the rest of the node. - * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing - * semicolon on a different line. - * @param {ASTNode} node A directive node - * @returns {Token} The last token of the node on the line - */ - function getLastTokenOnLine(node) { - const lastToken = sourceCode.getLastToken(node); - const secondToLastToken = sourceCode.getTokenBefore(lastToken); - - return astUtils.isSemicolonToken(lastToken) && lastToken.loc.start.line > secondToLastToken.loc.end.line - ? secondToLastToken - : lastToken; - } - - /** - * Check if node is followed by a blank newline. - * @param {ASTNode} node Node to check. - * @returns {boolean} Whether or not the passed in node is followed by a blank newline. - */ - function hasNewlineAfter(node) { - const lastToken = getLastTokenOnLine(node); - const tokenAfter = sourceCode.getTokenAfter(lastToken, { includeComments: true }); - - return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2; - } - - /** - * Report errors for newlines around directives. - * @param {ASTNode} node Node to check. - * @param {string} location Whether the error was found before or after the directive. - * @param {boolean} expected Whether or not a newline was expected or unexpected. - * @returns {void} - */ - function reportError(node, location, expected) { - context.report({ - node, - messageId: expected ? "expected" : "unexpected", - data: { - value: node.expression.value, - location - }, - fix(fixer) { - const lastToken = getLastTokenOnLine(node); - - if (expected) { - return location === "before" ? fixer.insertTextBefore(node, "\n") : fixer.insertTextAfter(lastToken, "\n"); - } - return fixer.removeRange(location === "before" ? [node.range[0] - 1, node.range[0]] : [lastToken.range[1], lastToken.range[1] + 1]); - } - }); - } - - /** - * Check lines around directives in node - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkDirectives(node) { - const directives = astUtils.getDirectivePrologue(node); - - if (!directives.length) { - return; - } - - const firstDirective = directives[0]; - const leadingComments = sourceCode.getCommentsBefore(firstDirective); - - /* - * Only check before the first directive if it is preceded by a comment or if it is at the top of - * the file and expectLineBefore is set to "never". This is to not force a newline at the top of - * the file if there are no comments as well as for compatibility with padded-blocks. - */ - if (leadingComments.length) { - if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) { - reportError(firstDirective, "before", true); - } - - if (expectLineBefore === "never" && hasNewlineBefore(firstDirective)) { - reportError(firstDirective, "before", false); - } - } else if ( - node.type === "Program" && - expectLineBefore === "never" && - !leadingComments.length && - hasNewlineBefore(firstDirective) - ) { - reportError(firstDirective, "before", false); - } - - const lastDirective = directives.at(-1); - const statements = node.type === "Program" ? node.body : node.body.body; - - /* - * Do not check after the last directive if the body only - * contains a directive prologue and isn't followed by a comment to ensure - * this rule behaves well with padded-blocks. - */ - if (lastDirective === statements.at(-1) && !lastDirective.trailingComments) { - return; - } - - if (expectLineAfter === "always" && !hasNewlineAfter(lastDirective)) { - reportError(lastDirective, "after", true); - } - - if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) { - reportError(lastDirective, "after", false); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program: checkDirectives, - FunctionDeclaration: checkDirectives, - FunctionExpression: checkDirectives, - ArrowFunctionExpression: checkDirectives - }; - } + meta: { + type: "layout", + + docs: { + description: "Require or disallow newlines around directives", + recommended: false, + url: "https://eslint.org/docs/latest/rules/lines-around-directive", + }, + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + before: { + enum: ["always", "never"], + }, + after: { + enum: ["always", "never"], + }, + }, + additionalProperties: false, + minProperties: 2, + }, + ], + }, + ], + + fixable: "whitespace", + messages: { + expected: 'Expected newline {{location}} "{{value}}" directive.', + unexpected: + 'Unexpected newline {{location}} "{{value}}" directive.', + }, + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "padding-line-between-statements", + url: "https://eslint.style/rules/js/padding-line-between-statements", + }, + }, + ], + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const config = context.options[0] || "always"; + const expectLineBefore = + typeof config === "string" ? config : config.before; + const expectLineAfter = + typeof config === "string" ? config : config.after; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if node is preceded by a blank newline. + * @param {ASTNode} node Node to check. + * @returns {boolean} Whether or not the passed in node is preceded by a blank newline. + */ + function hasNewlineBefore(node) { + const tokenBefore = sourceCode.getTokenBefore(node, { + includeComments: true, + }); + const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0; + + return node.loc.start.line - tokenLineBefore >= 2; + } + + /** + * Gets the last token of a node that is on the same line as the rest of the node. + * This will usually be the last token of the node, but it will be the second-to-last token if the node has a trailing + * semicolon on a different line. + * @param {ASTNode} node A directive node + * @returns {Token} The last token of the node on the line + */ + function getLastTokenOnLine(node) { + const lastToken = sourceCode.getLastToken(node); + const secondToLastToken = sourceCode.getTokenBefore(lastToken); + + return astUtils.isSemicolonToken(lastToken) && + lastToken.loc.start.line > secondToLastToken.loc.end.line + ? secondToLastToken + : lastToken; + } + + /** + * Check if node is followed by a blank newline. + * @param {ASTNode} node Node to check. + * @returns {boolean} Whether or not the passed in node is followed by a blank newline. + */ + function hasNewlineAfter(node) { + const lastToken = getLastTokenOnLine(node); + const tokenAfter = sourceCode.getTokenAfter(lastToken, { + includeComments: true, + }); + + return tokenAfter.loc.start.line - lastToken.loc.end.line >= 2; + } + + /** + * Report errors for newlines around directives. + * @param {ASTNode} node Node to check. + * @param {string} location Whether the error was found before or after the directive. + * @param {boolean} expected Whether or not a newline was expected or unexpected. + * @returns {void} + */ + function reportError(node, location, expected) { + context.report({ + node, + messageId: expected ? "expected" : "unexpected", + data: { + value: node.expression.value, + location, + }, + fix(fixer) { + const lastToken = getLastTokenOnLine(node); + + if (expected) { + return location === "before" + ? fixer.insertTextBefore(node, "\n") + : fixer.insertTextAfter(lastToken, "\n"); + } + return fixer.removeRange( + location === "before" + ? [node.range[0] - 1, node.range[0]] + : [lastToken.range[1], lastToken.range[1] + 1], + ); + }, + }); + } + + /** + * Check lines around directives in node + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkDirectives(node) { + const directives = astUtils.getDirectivePrologue(node); + + if (!directives.length) { + return; + } + + const firstDirective = directives[0]; + const leadingComments = + sourceCode.getCommentsBefore(firstDirective); + + /* + * Only check before the first directive if it is preceded by a comment or if it is at the top of + * the file and expectLineBefore is set to "never". This is to not force a newline at the top of + * the file if there are no comments as well as for compatibility with padded-blocks. + */ + if (leadingComments.length) { + if ( + expectLineBefore === "always" && + !hasNewlineBefore(firstDirective) + ) { + reportError(firstDirective, "before", true); + } + + if ( + expectLineBefore === "never" && + hasNewlineBefore(firstDirective) + ) { + reportError(firstDirective, "before", false); + } + } else if ( + node.type === "Program" && + expectLineBefore === "never" && + !leadingComments.length && + hasNewlineBefore(firstDirective) + ) { + reportError(firstDirective, "before", false); + } + + const lastDirective = directives.at(-1); + const statements = + node.type === "Program" ? node.body : node.body.body; + + /* + * Do not check after the last directive if the body only + * contains a directive prologue and isn't followed by a comment to ensure + * this rule behaves well with padded-blocks. + */ + if ( + lastDirective === statements.at(-1) && + !lastDirective.trailingComments + ) { + return; + } + + if ( + expectLineAfter === "always" && + !hasNewlineAfter(lastDirective) + ) { + reportError(lastDirective, "after", true); + } + + if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) { + reportError(lastDirective, "after", false); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: checkDirectives, + FunctionDeclaration: checkDirectives, + FunctionExpression: checkDirectives, + ArrowFunctionExpression: checkDirectives, + }; + }, }; diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js index effbe7c4d29a..69c716ced64f 100644 --- a/lib/rules/lines-between-class-members.js +++ b/lib/rules/lines-between-class-members.js @@ -21,9 +21,9 @@ const astUtils = require("./utils/ast-utils"); * @private */ const ClassMemberTypes = { - "*": { test: () => true }, - field: { test: node => node.type === "PropertyDefinition" }, - method: { test: node => node.type === "MethodDefinition" } + "*": { test: () => true }, + field: { test: node => node.type === "PropertyDefinition" }, + method: { test: node => node.type === "MethodDefinition" }, }; //------------------------------------------------------------------------------ @@ -32,256 +32,327 @@ const ClassMemberTypes = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "lines-between-class-members", - url: "https://eslint.style/rules/js/lines-between-class-members" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "lines-between-class-members", + url: "https://eslint.style/rules/js/lines-between-class-members", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Require or disallow an empty line between class members", - recommended: false, - url: "https://eslint.org/docs/latest/rules/lines-between-class-members" - }, + docs: { + description: + "Require or disallow an empty line between class members", + recommended: false, + url: "https://eslint.org/docs/latest/rules/lines-between-class-members", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - anyOf: [ - { - type: "object", - properties: { - enforce: { - type: "array", - items: { - type: "object", - properties: { - blankLine: { enum: ["always", "never"] }, - prev: { enum: ["method", "field", "*"] }, - next: { enum: ["method", "field", "*"] } - }, - additionalProperties: false, - required: ["blankLine", "prev", "next"] - }, - minItems: 1 - } - }, - additionalProperties: false, - required: ["enforce"] - }, - { - enum: ["always", "never"] - } - ] - }, - { - type: "object", - properties: { - exceptAfterSingleLine: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - messages: { - never: "Unexpected blank line between class members.", - always: "Expected blank line between class members." - } - }, + schema: [ + { + anyOf: [ + { + type: "object", + properties: { + enforce: { + type: "array", + items: { + type: "object", + properties: { + blankLine: { + enum: ["always", "never"], + }, + prev: { + enum: ["method", "field", "*"], + }, + next: { + enum: ["method", "field", "*"], + }, + }, + additionalProperties: false, + required: ["blankLine", "prev", "next"], + }, + minItems: 1, + }, + }, + additionalProperties: false, + required: ["enforce"], + }, + { + enum: ["always", "never"], + }, + ], + }, + { + type: "object", + properties: { + exceptAfterSingleLine: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + never: "Unexpected blank line between class members.", + always: "Expected blank line between class members.", + }, + }, - create(context) { + create(context) { + const options = []; - const options = []; + options[0] = context.options[0] || "always"; + options[1] = context.options[1] || { exceptAfterSingleLine: false }; - options[0] = context.options[0] || "always"; - options[1] = context.options[1] || { exceptAfterSingleLine: false }; + const configureList = + typeof options[0] === "object" + ? options[0].enforce + : [{ blankLine: options[0], prev: "*", next: "*" }]; + const sourceCode = context.sourceCode; - const configureList = typeof options[0] === "object" ? options[0].enforce : [{ blankLine: options[0], prev: "*", next: "*" }]; - const sourceCode = context.sourceCode; + /** + * Gets a pair of tokens that should be used to check lines between two class member nodes. + * + * In most cases, this returns the very last token of the current node and + * the very first token of the next node. + * For example: + * + * class C { + * x = 1; // curLast: `;` nextFirst: `in` + * in = 2 + * } + * + * There is only one exception. If the given node ends with a semicolon, and it looks like + * a semicolon-less style's semicolon - one that is not on the same line as the preceding + * token, but is on the line where the next class member starts - this returns the preceding + * token and the semicolon as boundary tokens. + * For example: + * + * class C { + * x = 1 // curLast: `1` nextFirst: `;` + * ;in = 2 + * } + * When determining the desired layout of the code, we should treat this semicolon as + * a part of the next class member node instead of the one it technically belongs to. + * @param {ASTNode} curNode Current class member node. + * @param {ASTNode} nextNode Next class member node. + * @returns {Token} The actual last token of `node`. + * @private + */ + function getBoundaryTokens(curNode, nextNode) { + const lastToken = sourceCode.getLastToken(curNode); + const prevToken = sourceCode.getTokenBefore(lastToken); + const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes - /** - * Gets a pair of tokens that should be used to check lines between two class member nodes. - * - * In most cases, this returns the very last token of the current node and - * the very first token of the next node. - * For example: - * - * class C { - * x = 1; // curLast: `;` nextFirst: `in` - * in = 2 - * } - * - * There is only one exception. If the given node ends with a semicolon, and it looks like - * a semicolon-less style's semicolon - one that is not on the same line as the preceding - * token, but is on the line where the next class member starts - this returns the preceding - * token and the semicolon as boundary tokens. - * For example: - * - * class C { - * x = 1 // curLast: `1` nextFirst: `;` - * ;in = 2 - * } - * When determining the desired layout of the code, we should treat this semicolon as - * a part of the next class member node instead of the one it technically belongs to. - * @param {ASTNode} curNode Current class member node. - * @param {ASTNode} nextNode Next class member node. - * @returns {Token} The actual last token of `node`. - * @private - */ - function getBoundaryTokens(curNode, nextNode) { - const lastToken = sourceCode.getLastToken(curNode); - const prevToken = sourceCode.getTokenBefore(lastToken); - const nextToken = sourceCode.getFirstToken(nextNode); // skip possible lone `;` between nodes + const isSemicolonLessStyle = + astUtils.isSemicolonToken(lastToken) && + !astUtils.isTokenOnSameLine(prevToken, lastToken) && + astUtils.isTokenOnSameLine(lastToken, nextToken); - const isSemicolonLessStyle = ( - astUtils.isSemicolonToken(lastToken) && - !astUtils.isTokenOnSameLine(prevToken, lastToken) && - astUtils.isTokenOnSameLine(lastToken, nextToken) - ); + return isSemicolonLessStyle + ? { curLast: prevToken, nextFirst: lastToken } + : { curLast: lastToken, nextFirst: nextToken }; + } - return isSemicolonLessStyle - ? { curLast: prevToken, nextFirst: lastToken } - : { curLast: lastToken, nextFirst: nextToken }; - } + /** + * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member. + * @param {Token} prevLastToken The last token in the previous member node. + * @param {Token} nextFirstToken The first token in the next member node. + * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. + * @returns {Token} The last token among the consecutive tokens. + */ + function findLastConsecutiveTokenAfter( + prevLastToken, + nextFirstToken, + maxLine, + ) { + const after = sourceCode.getTokenAfter(prevLastToken, { + includeComments: true, + }); - /** - * Return the last token among the consecutive tokens that have no exceed max line difference in between, before the first token in the next member. - * @param {Token} prevLastToken The last token in the previous member node. - * @param {Token} nextFirstToken The first token in the next member node. - * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. - * @returns {Token} The last token among the consecutive tokens. - */ - function findLastConsecutiveTokenAfter(prevLastToken, nextFirstToken, maxLine) { - const after = sourceCode.getTokenAfter(prevLastToken, { includeComments: true }); + if ( + after !== nextFirstToken && + after.loc.start.line - prevLastToken.loc.end.line <= maxLine + ) { + return findLastConsecutiveTokenAfter( + after, + nextFirstToken, + maxLine, + ); + } + return prevLastToken; + } - if (after !== nextFirstToken && after.loc.start.line - prevLastToken.loc.end.line <= maxLine) { - return findLastConsecutiveTokenAfter(after, nextFirstToken, maxLine); - } - return prevLastToken; - } + /** + * Return the first token among the consecutive tokens that have no exceed max line difference in between, after the last token in the previous member. + * @param {Token} nextFirstToken The first token in the next member node. + * @param {Token} prevLastToken The last token in the previous member node. + * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. + * @returns {Token} The first token among the consecutive tokens. + */ + function findFirstConsecutiveTokenBefore( + nextFirstToken, + prevLastToken, + maxLine, + ) { + const before = sourceCode.getTokenBefore(nextFirstToken, { + includeComments: true, + }); - /** - * Return the first token among the consecutive tokens that have no exceed max line difference in between, after the last token in the previous member. - * @param {Token} nextFirstToken The first token in the next member node. - * @param {Token} prevLastToken The last token in the previous member node. - * @param {number} maxLine The maximum number of allowed line difference between consecutive tokens. - * @returns {Token} The first token among the consecutive tokens. - */ - function findFirstConsecutiveTokenBefore(nextFirstToken, prevLastToken, maxLine) { - const before = sourceCode.getTokenBefore(nextFirstToken, { includeComments: true }); + if ( + before !== prevLastToken && + nextFirstToken.loc.start.line - before.loc.end.line <= maxLine + ) { + return findFirstConsecutiveTokenBefore( + before, + prevLastToken, + maxLine, + ); + } + return nextFirstToken; + } - if (before !== prevLastToken && nextFirstToken.loc.start.line - before.loc.end.line <= maxLine) { - return findFirstConsecutiveTokenBefore(before, prevLastToken, maxLine); - } - return nextFirstToken; - } + /** + * Checks if there is a token or comment between two tokens. + * @param {Token} before The token before. + * @param {Token} after The token after. + * @returns {boolean} True if there is a token or comment between two tokens. + */ + function hasTokenOrCommentBetween(before, after) { + return ( + sourceCode.getTokensBetween(before, after, { + includeComments: true, + }).length !== 0 + ); + } - /** - * Checks if there is a token or comment between two tokens. - * @param {Token} before The token before. - * @param {Token} after The token after. - * @returns {boolean} True if there is a token or comment between two tokens. - */ - function hasTokenOrCommentBetween(before, after) { - return sourceCode.getTokensBetween(before, after, { includeComments: true }).length !== 0; - } + /** + * Checks whether the given node matches the given type. + * @param {ASTNode} node The class member node to check. + * @param {string} type The class member type to check. + * @returns {boolean} `true` if the class member node matched the type. + * @private + */ + function match(node, type) { + return ClassMemberTypes[type].test(node); + } - /** - * Checks whether the given node matches the given type. - * @param {ASTNode} node The class member node to check. - * @param {string} type The class member type to check. - * @returns {boolean} `true` if the class member node matched the type. - * @private - */ - function match(node, type) { - return ClassMemberTypes[type].test(node); - } + /** + * Finds the last matched configuration from the configureList. + * @param {ASTNode} prevNode The previous node to match. + * @param {ASTNode} nextNode The current node to match. + * @returns {string|null} Padding type or `null` if no matches were found. + * @private + */ + function getPaddingType(prevNode, nextNode) { + for (let i = configureList.length - 1; i >= 0; --i) { + const configure = configureList[i]; + const matched = + match(prevNode, configure.prev) && + match(nextNode, configure.next); - /** - * Finds the last matched configuration from the configureList. - * @param {ASTNode} prevNode The previous node to match. - * @param {ASTNode} nextNode The current node to match. - * @returns {string|null} Padding type or `null` if no matches were found. - * @private - */ - function getPaddingType(prevNode, nextNode) { - for (let i = configureList.length - 1; i >= 0; --i) { - const configure = configureList[i]; - const matched = - match(prevNode, configure.prev) && - match(nextNode, configure.next); + if (matched) { + return configure.blankLine; + } + } + return null; + } - if (matched) { - return configure.blankLine; - } - } - return null; - } + return { + ClassBody(node) { + const body = node.body; - return { - ClassBody(node) { - const body = node.body; + for (let i = 0; i < body.length - 1; i++) { + const curFirst = sourceCode.getFirstToken(body[i]); + const { curLast, nextFirst } = getBoundaryTokens( + body[i], + body[i + 1], + ); + const isMulti = !astUtils.isTokenOnSameLine( + curFirst, + curLast, + ); + const skip = !isMulti && options[1].exceptAfterSingleLine; + const beforePadding = findLastConsecutiveTokenAfter( + curLast, + nextFirst, + 1, + ); + const afterPadding = findFirstConsecutiveTokenBefore( + nextFirst, + curLast, + 1, + ); + const isPadded = + afterPadding.loc.start.line - + beforePadding.loc.end.line > + 1; + const hasTokenInPadding = hasTokenOrCommentBetween( + beforePadding, + afterPadding, + ); + const curLineLastToken = findLastConsecutiveTokenAfter( + curLast, + nextFirst, + 0, + ); + const paddingType = getPaddingType(body[i], body[i + 1]); - for (let i = 0; i < body.length - 1; i++) { - const curFirst = sourceCode.getFirstToken(body[i]); - const { curLast, nextFirst } = getBoundaryTokens(body[i], body[i + 1]); - const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast); - const skip = !isMulti && options[1].exceptAfterSingleLine; - const beforePadding = findLastConsecutiveTokenAfter(curLast, nextFirst, 1); - const afterPadding = findFirstConsecutiveTokenBefore(nextFirst, curLast, 1); - const isPadded = afterPadding.loc.start.line - beforePadding.loc.end.line > 1; - const hasTokenInPadding = hasTokenOrCommentBetween(beforePadding, afterPadding); - const curLineLastToken = findLastConsecutiveTokenAfter(curLast, nextFirst, 0); - const paddingType = getPaddingType(body[i], body[i + 1]); + if (paddingType === "never" && isPadded) { + context.report({ + node: body[i + 1], + messageId: "never", - if (paddingType === "never" && isPadded) { - context.report({ - node: body[i + 1], - messageId: "never", + fix(fixer) { + if (hasTokenInPadding) { + return null; + } + return fixer.replaceTextRange( + [ + beforePadding.range[1], + afterPadding.range[0], + ], + "\n", + ); + }, + }); + } else if (paddingType === "always" && !skip && !isPadded) { + context.report({ + node: body[i + 1], + messageId: "always", - fix(fixer) { - if (hasTokenInPadding) { - return null; - } - return fixer.replaceTextRange([beforePadding.range[1], afterPadding.range[0]], "\n"); - } - }); - } else if (paddingType === "always" && !skip && !isPadded) { - context.report({ - node: body[i + 1], - messageId: "always", - - fix(fixer) { - if (hasTokenInPadding) { - return null; - } - return fixer.insertTextAfter(curLineLastToken, "\n"); - } - }); - } - - } - } - }; - } + fix(fixer) { + if (hasTokenInPadding) { + return null; + } + return fixer.insertTextAfter( + curLineLastToken, + "\n", + ); + }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/logical-assignment-operators.js b/lib/rules/logical-assignment-operators.js index d2070beeb334..68e3f7bb78c2 100644 --- a/lib/rules/logical-assignment-operators.js +++ b/lib/rules/logical-assignment-operators.js @@ -22,14 +22,16 @@ const baseTypes = new Set(["Identifier", "Super", "ThisExpression"]); * @returns {boolean} True iff "undefined" or "void ..." */ function isUndefined(expression, scope) { - if (expression.type === "Identifier" && expression.name === "undefined") { - return astUtils.isReferenceToGlobalVariable(scope, expression); - } - - return expression.type === "UnaryExpression" && - expression.operator === "void" && - expression.argument.type === "Literal" && - expression.argument.value === 0; + if (expression.type === "Identifier" && expression.name === "undefined") { + return astUtils.isReferenceToGlobalVariable(scope, expression); + } + + return ( + expression.type === "UnaryExpression" && + expression.operator === "void" && + expression.argument.type === "Literal" && + expression.argument.value === 0 + ); } /** @@ -38,8 +40,10 @@ function isUndefined(expression, scope) { * @returns {boolean} True for identifiers and member expressions */ function isReference(expression) { - return (expression.type === "Identifier" && expression.name !== "undefined") || - expression.type === "MemberExpression"; + return ( + (expression.type === "Identifier" && expression.name !== "undefined") || + expression.type === "MemberExpression" + ); } /** @@ -50,15 +54,21 @@ function isReference(expression) { * @returns {boolean} True iff implicit nullish comparison */ function isImplicitNullishComparison(expression, scope) { - if (expression.type !== "BinaryExpression" || expression.operator !== "==") { - return false; - } - - const reference = isReference(expression.left) ? "left" : "right"; - const nullish = reference === "left" ? "right" : "left"; - - return isReference(expression[reference]) && - (astUtils.isNullLiteral(expression[nullish]) || isUndefined(expression[nullish], scope)); + if ( + expression.type !== "BinaryExpression" || + expression.operator !== "==" + ) { + return false; + } + + const reference = isReference(expression.left) ? "left" : "right"; + const nullish = reference === "left" ? "right" : "left"; + + return ( + isReference(expression[reference]) && + (astUtils.isNullLiteral(expression[nullish]) || + isUndefined(expression[nullish], scope)) + ); } /** @@ -67,12 +77,14 @@ function isImplicitNullishComparison(expression, scope) { * @returns {boolean} True iff matches ? === ? || ? === ? */ function isDoubleComparison(expression) { - return expression.type === "LogicalExpression" && - expression.operator === "||" && - expression.left.type === "BinaryExpression" && - expression.left.operator === "===" && - expression.right.type === "BinaryExpression" && - expression.right.operator === "==="; + return ( + expression.type === "LogicalExpression" && + expression.operator === "||" && + expression.left.type === "BinaryExpression" && + expression.left.operator === "===" && + expression.right.type === "BinaryExpression" && + expression.right.operator === "===" + ); } /** @@ -83,17 +95,26 @@ function isDoubleComparison(expression) { * @returns {boolean} True iff explicit nullish comparison */ function isExplicitNullishComparison(expression, scope) { - if (!isDoubleComparison(expression)) { - return false; - } - const leftReference = isReference(expression.left.left) ? "left" : "right"; - const leftNullish = leftReference === "left" ? "right" : "left"; - const rightReference = isReference(expression.right.left) ? "left" : "right"; - const rightNullish = rightReference === "left" ? "right" : "left"; - - return astUtils.isSameReference(expression.left[leftReference], expression.right[rightReference]) && - ((astUtils.isNullLiteral(expression.left[leftNullish]) && isUndefined(expression.right[rightNullish], scope)) || - (isUndefined(expression.left[leftNullish], scope) && astUtils.isNullLiteral(expression.right[rightNullish]))); + if (!isDoubleComparison(expression)) { + return false; + } + const leftReference = isReference(expression.left.left) ? "left" : "right"; + const leftNullish = leftReference === "left" ? "right" : "left"; + const rightReference = isReference(expression.right.left) + ? "left" + : "right"; + const rightNullish = rightReference === "left" ? "right" : "left"; + + return ( + astUtils.isSameReference( + expression.left[leftReference], + expression.right[rightReference], + ) && + ((astUtils.isNullLiteral(expression.left[leftNullish]) && + isUndefined(expression.right[rightNullish], scope)) || + (isUndefined(expression.left[leftNullish], scope) && + astUtils.isNullLiteral(expression.right[rightNullish]))) + ); } /** @@ -103,10 +124,12 @@ function isExplicitNullishComparison(expression, scope) { * @returns {boolean} Whether the expression is a boolean cast */ function isBooleanCast(expression, scope) { - return expression.type === "CallExpression" && - expression.callee.name === "Boolean" && - expression.arguments.length === 1 && - astUtils.isReferenceToGlobalVariable(scope, expression.callee); + return ( + expression.type === "CallExpression" && + expression.callee.name === "Boolean" && + expression.arguments.length === 1 && + astUtils.isReferenceToGlobalVariable(scope, expression.callee) + ); } /** @@ -119,22 +142,39 @@ function isBooleanCast(expression, scope) { * @returns {?{ reference: ASTNode, operator: '??'|'||'|'&&'}} Null if not a known existence */ function getExistence(expression, scope) { - const isNegated = expression.type === "UnaryExpression" && expression.operator === "!"; - const base = isNegated ? expression.argument : expression; - - switch (true) { - case isReference(base): - return { reference: base, operator: isNegated ? "||" : "&&" }; - case base.type === "UnaryExpression" && base.operator === "!" && isReference(base.argument): - return { reference: base.argument, operator: "&&" }; - case isBooleanCast(base, scope) && isReference(base.arguments[0]): - return { reference: base.arguments[0], operator: isNegated ? "||" : "&&" }; - case isImplicitNullishComparison(expression, scope): - return { reference: isReference(expression.left) ? expression.left : expression.right, operator: "??" }; - case isExplicitNullishComparison(expression, scope): - return { reference: isReference(expression.left.left) ? expression.left.left : expression.left.right, operator: "??" }; - default: return null; - } + const isNegated = + expression.type === "UnaryExpression" && expression.operator === "!"; + const base = isNegated ? expression.argument : expression; + + switch (true) { + case isReference(base): + return { reference: base, operator: isNegated ? "||" : "&&" }; + case base.type === "UnaryExpression" && + base.operator === "!" && + isReference(base.argument): + return { reference: base.argument, operator: "&&" }; + case isBooleanCast(base, scope) && isReference(base.arguments[0]): + return { + reference: base.arguments[0], + operator: isNegated ? "||" : "&&", + }; + case isImplicitNullishComparison(expression, scope): + return { + reference: isReference(expression.left) + ? expression.left + : expression.right, + operator: "??", + }; + case isExplicitNullishComparison(expression, scope): + return { + reference: isReference(expression.left.left) + ? expression.left.left + : expression.left.right, + operator: "??", + }; + default: + return null; + } } /** @@ -143,11 +183,13 @@ function getExistence(expression, scope) { * @returns {boolean} True iff passed node is inside a with block */ function isInsideWithBlock(node) { - if (node.type === "Program") { - return false; - } + if (node.type === "Program") { + return false; + } - return node.parent.type === "WithStatement" && node.parent.body === node ? true : isInsideWithBlock(node.parent); + return node.parent.type === "WithStatement" && node.parent.body === node + ? true + : isInsideWithBlock(node.parent); } /** @@ -157,22 +199,22 @@ function isInsideWithBlock(node) { * @returns {Expression} Leftmost operand */ function getLeftmostOperand(sourceCode, node) { - let left = node.left; - - while (left.type === "LogicalExpression" && left.operator === node.operator) { - - if (astUtils.isParenthesised(sourceCode, left)) { - - /* - * It should have associativity, - * but ignore it if use parentheses to make the evaluation order clear. - */ - return left; - } - left = left.left; - } - return left; - + let left = node.left; + + while ( + left.type === "LogicalExpression" && + left.operator === node.operator + ) { + if (astUtils.isParenthesised(sourceCode, left)) { + /* + * It should have associativity, + * but ignore it if use parentheses to make the evaluation order clear. + */ + return left; + } + left = left.left; + } + return left; } //------------------------------------------------------------------------------ @@ -180,326 +222,467 @@ function getLeftmostOperand(sourceCode, node) { //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require or disallow logical assignment operator shorthand", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/logical-assignment-operators" - }, - - schema: { - type: "array", - oneOf: [{ - items: [ - { const: "always" }, - { - type: "object", - properties: { - enforceForIfStatements: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, // 0 for allowing passing no options - maxItems: 2 - }, { - items: [{ const: "never" }], - minItems: 1, - maxItems: 1 - }] - }, - fixable: "code", - hasSuggestions: true, - messages: { - assignment: "Assignment (=) can be replaced with operator assignment ({{operator}}).", - useLogicalOperator: "Convert this assignment to use the operator {{ operator }}.", - logical: "Logical expression can be replaced with an assignment ({{ operator }}).", - convertLogical: "Replace this logical expression with an assignment with the operator {{ operator }}.", - if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.", - convertIf: "Replace this 'if' statement with a logical assignment with operator {{ operator }}.", - unexpected: "Unexpected logical operator assignment ({{operator}}) shorthand.", - separate: "Separate the logical assignment into an assignment with a logical operator." - } - }, - - create(context) { - const mode = context.options[0] === "never" ? "never" : "always"; - const checkIf = mode === "always" && context.options.length > 1 && context.options[1].enforceForIfStatements; - const sourceCode = context.sourceCode; - const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; - - /** - * Returns false if the access could be a getter - * @param {ASTNode} node Assignment expression - * @returns {boolean} True iff the fix is safe - */ - function cannotBeGetter(node) { - return node.type === "Identifier" && - (isStrict || !isInsideWithBlock(node)); - } - - /** - * Check whether only a single property is accessed - * @param {ASTNode} node reference - * @returns {boolean} True iff a single property is accessed - */ - function accessesSingleProperty(node) { - if (!isStrict && isInsideWithBlock(node)) { - return node.type === "Identifier"; - } - - return node.type === "MemberExpression" && - baseTypes.has(node.object.type) && - (!node.computed || (node.property.type !== "MemberExpression" && node.property.type !== "ChainExpression")); - } - - /** - * Adds a fixer or suggestion whether on the fix is safe. - * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest - * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion - * @param {boolean} shouldBeFixed Fix iff the condition is true - * @returns {Object} Descriptor with either an added fix or suggestion - */ - function createConditionalFixer(descriptor, suggestion, shouldBeFixed) { - if (shouldBeFixed) { - return { - ...descriptor, - fix: suggestion.fix - }; - } - - return { - ...descriptor, - suggest: [suggestion] - }; - } - - - /** - * Returns the operator token for assignments and binary expressions - * @param {ASTNode} node AssignmentExpression or BinaryExpression - * @returns {import('eslint').AST.Token} Operator token between the left and right expression - */ - function getOperatorToken(node) { - return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); - } - - if (mode === "never") { - return { - - // foo ||= bar - "AssignmentExpression"(assignment) { - if (!astUtils.isLogicalAssignmentOperator(assignment.operator)) { - return; - } - - const descriptor = { - messageId: "unexpected", - node: assignment, - data: { operator: assignment.operator } - }; - const suggestion = { - messageId: "separate", - *fix(ruleFixer) { - if (sourceCode.getCommentsInside(assignment).length > 0) { - return; - } - - const operatorToken = getOperatorToken(assignment); - - // -> foo = bar - yield ruleFixer.replaceText(operatorToken, "="); - - const assignmentText = sourceCode.getText(assignment.left); - const operator = assignment.operator.slice(0, -1); - - // -> foo = foo || bar - yield ruleFixer.insertTextAfter(operatorToken, ` ${assignmentText} ${operator}`); - - const precedence = astUtils.getPrecedence(assignment.right) <= astUtils.getPrecedence({ type: "LogicalExpression", operator }); - - // ?? and || / && cannot be mixed but have same precedence - const mixed = assignment.operator === "??=" && astUtils.isLogicalExpression(assignment.right); - - if (!astUtils.isParenthesised(sourceCode, assignment.right) && (precedence || mixed)) { - - // -> foo = foo || (bar) - yield ruleFixer.insertTextBefore(assignment.right, "("); - yield ruleFixer.insertTextAfter(assignment.right, ")"); - } - } - }; - - context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); - } - }; - } - - return { - - // foo = foo || bar - "AssignmentExpression[operator='='][right.type='LogicalExpression']"(assignment) { - const leftOperand = getLeftmostOperand(sourceCode, assignment.right); - - if (!astUtils.isSameReference(assignment.left, leftOperand) - ) { - return; - } - - const descriptor = { - messageId: "assignment", - node: assignment, - data: { operator: `${assignment.right.operator}=` } - }; - const suggestion = { - messageId: "useLogicalOperator", - data: { operator: `${assignment.right.operator}=` }, - *fix(ruleFixer) { - if (sourceCode.getCommentsInside(assignment).length > 0) { - return; - } - - // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator - const assignmentOperatorToken = getOperatorToken(assignment); - - // -> foo ||= foo || bar - yield ruleFixer.insertTextBefore(assignmentOperatorToken, assignment.right.operator); - - // -> foo ||= bar - const logicalOperatorToken = getOperatorToken(leftOperand.parent); - const firstRightOperandToken = sourceCode.getTokenAfter(logicalOperatorToken); - - yield ruleFixer.removeRange([leftOperand.parent.range[0], firstRightOperandToken.range[0]]); - } - }; - - context.report(createConditionalFixer(descriptor, suggestion, cannotBeGetter(assignment.left))); - }, - - // foo || (foo = bar) - 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'(logical) { - - // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal - if (isReference(logical.left) && astUtils.isSameReference(logical.left, logical.right.left)) { - const descriptor = { - messageId: "logical", - node: logical, - data: { operator: `${logical.operator}=` } - }; - const suggestion = { - messageId: "convertLogical", - data: { operator: `${logical.operator}=` }, - *fix(ruleFixer) { - if (sourceCode.getCommentsInside(logical).length > 0) { - return; - } - - const parentPrecedence = astUtils.getPrecedence(logical.parent); - const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && ( - parentPrecedence === -1 || - astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence - ); - - if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) { - yield ruleFixer.insertTextBefore(logical, "("); - yield ruleFixer.insertTextAfter(logical, ")"); - } - - // Also removes all opening parenthesis - yield ruleFixer.removeRange([logical.range[0], logical.right.range[0]]); // -> foo = bar) - - // Also removes all ending parenthesis - yield ruleFixer.removeRange([logical.right.range[1], logical.range[1]]); // -> foo = bar - - const operatorToken = getOperatorToken(logical.right); - - yield ruleFixer.insertTextBefore(operatorToken, logical.operator); // -> foo ||= bar - } - }; - const fix = cannotBeGetter(logical.left) || accessesSingleProperty(logical.left); - - context.report(createConditionalFixer(descriptor, suggestion, fix)); - } - }, - - // if (foo) foo = bar - "IfStatement[alternate=null]"(ifNode) { - if (!checkIf) { - return; - } - - const hasBody = ifNode.consequent.type === "BlockStatement"; - - if (hasBody && ifNode.consequent.body.length !== 1) { - return; - } - - const body = hasBody ? ifNode.consequent.body[0] : ifNode.consequent; - const scope = sourceCode.getScope(ifNode); - const existence = getExistence(ifNode.test, scope); - - if ( - body.type === "ExpressionStatement" && - body.expression.type === "AssignmentExpression" && - body.expression.operator === "=" && - existence !== null && - astUtils.isSameReference(existence.reference, body.expression.left) - ) { - const descriptor = { - messageId: "if", - node: ifNode, - data: { operator: `${existence.operator}=` } - }; - const suggestion = { - messageId: "convertIf", - data: { operator: `${existence.operator}=` }, - *fix(ruleFixer) { - if (sourceCode.getCommentsInside(ifNode).length > 0) { - return; - } - - const firstBodyToken = sourceCode.getFirstToken(body); - const prevToken = sourceCode.getTokenBefore(ifNode); - - if ( - prevToken !== null && - prevToken.value !== ";" && - prevToken.value !== "{" && - firstBodyToken.type !== "Identifier" && - firstBodyToken.type !== "Keyword" - ) { - - // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b) - return; - } - - - const operatorToken = getOperatorToken(body.expression); - - yield ruleFixer.insertTextBefore(operatorToken, existence.operator); // -> if (foo) foo ||= bar - - yield ruleFixer.removeRange([ifNode.range[0], body.range[0]]); // -> foo ||= bar - - yield ruleFixer.removeRange([body.range[1], ifNode.range[1]]); // -> foo ||= bar, only present if "if" had a body - - const nextToken = sourceCode.getTokenAfter(body.expression); - - if (hasBody && (nextToken !== null && nextToken.value !== ";")) { - yield ruleFixer.insertTextAfter(ifNode, ";"); - } - } - }; - const shouldBeFixed = cannotBeGetter(existence.reference) || - (ifNode.test.type !== "LogicalExpression" && accessesSingleProperty(existence.reference)); - - context.report(createConditionalFixer(descriptor, suggestion, shouldBeFixed)); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Require or disallow logical assignment operator shorthand", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/logical-assignment-operators", + }, + + schema: { + type: "array", + oneOf: [ + { + items: [ + { const: "always" }, + { + type: "object", + properties: { + enforceForIfStatements: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, // 0 for allowing passing no options + maxItems: 2, + }, + { + items: [{ const: "never" }], + minItems: 1, + maxItems: 1, + }, + ], + }, + fixable: "code", + hasSuggestions: true, + messages: { + assignment: + "Assignment (=) can be replaced with operator assignment ({{operator}}).", + useLogicalOperator: + "Convert this assignment to use the operator {{ operator }}.", + logical: + "Logical expression can be replaced with an assignment ({{ operator }}).", + convertLogical: + "Replace this logical expression with an assignment with the operator {{ operator }}.", + if: "'if' statement can be replaced with a logical operator assignment with operator {{ operator }}.", + convertIf: + "Replace this 'if' statement with a logical assignment with operator {{ operator }}.", + unexpected: + "Unexpected logical operator assignment ({{operator}}) shorthand.", + separate: + "Separate the logical assignment into an assignment with a logical operator.", + }, + }, + + create(context) { + const mode = context.options[0] === "never" ? "never" : "always"; + const checkIf = + mode === "always" && + context.options.length > 1 && + context.options[1].enforceForIfStatements; + const sourceCode = context.sourceCode; + const isStrict = sourceCode.getScope(sourceCode.ast).isStrict; + + /** + * Returns false if the access could be a getter + * @param {ASTNode} node Assignment expression + * @returns {boolean} True iff the fix is safe + */ + function cannotBeGetter(node) { + return ( + node.type === "Identifier" && + (isStrict || !isInsideWithBlock(node)) + ); + } + + /** + * Check whether only a single property is accessed + * @param {ASTNode} node reference + * @returns {boolean} True iff a single property is accessed + */ + function accessesSingleProperty(node) { + if (!isStrict && isInsideWithBlock(node)) { + return node.type === "Identifier"; + } + + return ( + node.type === "MemberExpression" && + baseTypes.has(node.object.type) && + (!node.computed || + (node.property.type !== "MemberExpression" && + node.property.type !== "ChainExpression")) + ); + } + + /** + * Adds a fixer or suggestion whether on the fix is safe. + * @param {{ messageId: string, node: ASTNode }} descriptor Report descriptor without fix or suggest + * @param {{ messageId: string, fix: Function }} suggestion Adds the fix or the whole suggestion as only element in "suggest" to suggestion + * @param {boolean} shouldBeFixed Fix iff the condition is true + * @returns {Object} Descriptor with either an added fix or suggestion + */ + function createConditionalFixer(descriptor, suggestion, shouldBeFixed) { + if (shouldBeFixed) { + return { + ...descriptor, + fix: suggestion.fix, + }; + } + + return { + ...descriptor, + suggest: [suggestion], + }; + } + + /** + * Returns the operator token for assignments and binary expressions + * @param {ASTNode} node AssignmentExpression or BinaryExpression + * @returns {import('eslint').AST.Token} Operator token between the left and right expression + */ + function getOperatorToken(node) { + return sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + } + + if (mode === "never") { + return { + // foo ||= bar + AssignmentExpression(assignment) { + if ( + !astUtils.isLogicalAssignmentOperator( + assignment.operator, + ) + ) { + return; + } + + const descriptor = { + messageId: "unexpected", + node: assignment, + data: { operator: assignment.operator }, + }; + const suggestion = { + messageId: "separate", + *fix(ruleFixer) { + if ( + sourceCode.getCommentsInside(assignment) + .length > 0 + ) { + return; + } + + const operatorToken = getOperatorToken(assignment); + + // -> foo = bar + yield ruleFixer.replaceText(operatorToken, "="); + + const assignmentText = sourceCode.getText( + assignment.left, + ); + const operator = assignment.operator.slice(0, -1); + + // -> foo = foo || bar + yield ruleFixer.insertTextAfter( + operatorToken, + ` ${assignmentText} ${operator}`, + ); + + const precedence = + astUtils.getPrecedence(assignment.right) <= + astUtils.getPrecedence({ + type: "LogicalExpression", + operator, + }); + + // ?? and || / && cannot be mixed but have same precedence + const mixed = + assignment.operator === "??=" && + astUtils.isLogicalExpression(assignment.right); + + if ( + !astUtils.isParenthesised( + sourceCode, + assignment.right, + ) && + (precedence || mixed) + ) { + // -> foo = foo || (bar) + yield ruleFixer.insertTextBefore( + assignment.right, + "(", + ); + yield ruleFixer.insertTextAfter( + assignment.right, + ")", + ); + } + }, + }; + + context.report( + createConditionalFixer( + descriptor, + suggestion, + cannotBeGetter(assignment.left), + ), + ); + }, + }; + } + + return { + // foo = foo || bar + "AssignmentExpression[operator='='][right.type='LogicalExpression']"( + assignment, + ) { + const leftOperand = getLeftmostOperand( + sourceCode, + assignment.right, + ); + + if (!astUtils.isSameReference(assignment.left, leftOperand)) { + return; + } + + const descriptor = { + messageId: "assignment", + node: assignment, + data: { operator: `${assignment.right.operator}=` }, + }; + const suggestion = { + messageId: "useLogicalOperator", + data: { operator: `${assignment.right.operator}=` }, + *fix(ruleFixer) { + if ( + sourceCode.getCommentsInside(assignment).length > 0 + ) { + return; + } + + // No need for parenthesis around the assignment based on precedence as the precedence stays the same even with changed operator + const assignmentOperatorToken = + getOperatorToken(assignment); + + // -> foo ||= foo || bar + yield ruleFixer.insertTextBefore( + assignmentOperatorToken, + assignment.right.operator, + ); + + // -> foo ||= bar + const logicalOperatorToken = getOperatorToken( + leftOperand.parent, + ); + const firstRightOperandToken = + sourceCode.getTokenAfter(logicalOperatorToken); + + yield ruleFixer.removeRange([ + leftOperand.parent.range[0], + firstRightOperandToken.range[0], + ]); + }, + }; + + context.report( + createConditionalFixer( + descriptor, + suggestion, + cannotBeGetter(assignment.left), + ), + ); + }, + + // foo || (foo = bar) + 'LogicalExpression[right.type="AssignmentExpression"][right.operator="="]'( + logical, + ) { + // Right side has to be parenthesized, otherwise would be parsed as (foo || foo) = bar which is illegal + if ( + isReference(logical.left) && + astUtils.isSameReference(logical.left, logical.right.left) + ) { + const descriptor = { + messageId: "logical", + node: logical, + data: { operator: `${logical.operator}=` }, + }; + const suggestion = { + messageId: "convertLogical", + data: { operator: `${logical.operator}=` }, + *fix(ruleFixer) { + if ( + sourceCode.getCommentsInside(logical).length > 0 + ) { + return; + } + + const parentPrecedence = astUtils.getPrecedence( + logical.parent, + ); + const requiresOuterParenthesis = + logical.parent.type !== "ExpressionStatement" && + (parentPrecedence === -1 || + astUtils.getPrecedence({ + type: "AssignmentExpression", + }) < parentPrecedence); + + if ( + !astUtils.isParenthesised( + sourceCode, + logical, + ) && + requiresOuterParenthesis + ) { + yield ruleFixer.insertTextBefore(logical, "("); + yield ruleFixer.insertTextAfter(logical, ")"); + } + + // Also removes all opening parenthesis + yield ruleFixer.removeRange([ + logical.range[0], + logical.right.range[0], + ]); // -> foo = bar) + + // Also removes all ending parenthesis + yield ruleFixer.removeRange([ + logical.right.range[1], + logical.range[1], + ]); // -> foo = bar + + const operatorToken = getOperatorToken( + logical.right, + ); + + yield ruleFixer.insertTextBefore( + operatorToken, + logical.operator, + ); // -> foo ||= bar + }, + }; + const fix = + cannotBeGetter(logical.left) || + accessesSingleProperty(logical.left); + + context.report( + createConditionalFixer(descriptor, suggestion, fix), + ); + } + }, + + // if (foo) foo = bar + "IfStatement[alternate=null]"(ifNode) { + if (!checkIf) { + return; + } + + const hasBody = ifNode.consequent.type === "BlockStatement"; + + if (hasBody && ifNode.consequent.body.length !== 1) { + return; + } + + const body = hasBody + ? ifNode.consequent.body[0] + : ifNode.consequent; + const scope = sourceCode.getScope(ifNode); + const existence = getExistence(ifNode.test, scope); + + if ( + body.type === "ExpressionStatement" && + body.expression.type === "AssignmentExpression" && + body.expression.operator === "=" && + existence !== null && + astUtils.isSameReference( + existence.reference, + body.expression.left, + ) + ) { + const descriptor = { + messageId: "if", + node: ifNode, + data: { operator: `${existence.operator}=` }, + }; + const suggestion = { + messageId: "convertIf", + data: { operator: `${existence.operator}=` }, + *fix(ruleFixer) { + if ( + sourceCode.getCommentsInside(ifNode).length > 0 + ) { + return; + } + + const firstBodyToken = + sourceCode.getFirstToken(body); + const prevToken = sourceCode.getTokenBefore(ifNode); + + if ( + prevToken !== null && + prevToken.value !== ";" && + prevToken.value !== "{" && + firstBodyToken.type !== "Identifier" && + firstBodyToken.type !== "Keyword" + ) { + // Do not fix if the fixed statement could be part of the previous statement (eg. fn() if (a == null) (a) = b --> fn()(a) ??= b) + return; + } + + const operatorToken = getOperatorToken( + body.expression, + ); + + yield ruleFixer.insertTextBefore( + operatorToken, + existence.operator, + ); // -> if (foo) foo ||= bar + + yield ruleFixer.removeRange([ + ifNode.range[0], + body.range[0], + ]); // -> foo ||= bar + + yield ruleFixer.removeRange([ + body.range[1], + ifNode.range[1], + ]); // -> foo ||= bar, only present if "if" had a body + + const nextToken = sourceCode.getTokenAfter( + body.expression, + ); + + if ( + hasBody && + nextToken !== null && + nextToken.value !== ";" + ) { + yield ruleFixer.insertTextAfter(ifNode, ";"); + } + }, + }; + const shouldBeFixed = + cannotBeGetter(existence.reference) || + (ifNode.test.type !== "LogicalExpression" && + accessesSingleProperty(existence.reference)); + + context.report( + createConditionalFixer( + descriptor, + suggestion, + shouldBeFixed, + ), + ); + } + }, + }; + }, }; diff --git a/lib/rules/max-classes-per-file.js b/lib/rules/max-classes-per-file.js index 241e06023e4b..cac396795fef 100644 --- a/lib/rules/max-classes-per-file.js +++ b/lib/rules/max-classes-per-file.js @@ -8,82 +8,83 @@ // Requirements //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Enforce a maximum number of classes per file", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-classes-per-file" - }, + docs: { + description: "Enforce a maximum number of classes per file", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-classes-per-file", + }, - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 1 - }, - { - type: "object", - properties: { - ignoreExpressions: { - type: "boolean" - }, - max: { - type: "integer", - minimum: 1 - } - }, - additionalProperties: false - } - ] - } - ], + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 1, + }, + { + type: "object", + properties: { + ignoreExpressions: { + type: "boolean", + }, + max: { + type: "integer", + minimum: 1, + }, + }, + additionalProperties: false, + }, + ], + }, + ], - messages: { - maximumExceeded: "File has too many classes ({{ classCount }}). Maximum allowed is {{ max }}." - } - }, - create(context) { - const [option = {}] = context.options; - const [ignoreExpressions, max] = typeof option === "number" - ? [false, option || 1] - : [option.ignoreExpressions, option.max || 1]; + messages: { + maximumExceeded: + "File has too many classes ({{ classCount }}). Maximum allowed is {{ max }}.", + }, + }, + create(context) { + const [option = {}] = context.options; + const [ignoreExpressions, max] = + typeof option === "number" + ? [false, option || 1] + : [option.ignoreExpressions, option.max || 1]; - let classCount = 0; + let classCount = 0; - return { - Program() { - classCount = 0; - }, - "Program:exit"(node) { - if (classCount > max) { - context.report({ - node, - messageId: "maximumExceeded", - data: { - classCount, - max - } - }); - } - }, - "ClassDeclaration"() { - classCount++; - }, - "ClassExpression"() { - if (!ignoreExpressions) { - classCount++; - } - } - }; - } + return { + Program() { + classCount = 0; + }, + "Program:exit"(node) { + if (classCount > max) { + context.report({ + node, + messageId: "maximumExceeded", + data: { + classCount, + max, + }, + }); + } + }, + ClassDeclaration() { + classCount++; + }, + ClassExpression() { + if (!ignoreExpressions) { + classCount++; + } + }, + }; + }, }; diff --git a/lib/rules/max-depth.js b/lib/rules/max-depth.js index e92c6b4acdd6..40a83490b3ec 100644 --- a/lib/rules/max-depth.js +++ b/lib/rules/max-depth.js @@ -11,146 +11,149 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce a maximum depth that blocks can be nested", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-depth" - }, - - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - maximum: { - type: "integer", - minimum: 0 - }, - max: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - ], - messages: { - tooDeeply: "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}}." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const functionStack = [], - option = context.options[0]; - let maxDepth = 4; - - if ( - typeof option === "object" && - (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) - ) { - maxDepth = option.maximum || option.max; - } - if (typeof option === "number") { - maxDepth = option; - } - - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push(0); - } - - /** - * When parsing is done then pop out the reference - * @returns {void} - * @private - */ - function endFunction() { - functionStack.pop(); - } - - /** - * Save the block and Evaluate the node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function pushBlock(node) { - const len = ++functionStack[functionStack.length - 1]; - - if (len > maxDepth) { - context.report({ node, messageId: "tooDeeply", data: { depth: len, maxDepth } }); - } - } - - /** - * Pop the saved block - * @returns {void} - * @private - */ - function popBlock() { - functionStack[functionStack.length - 1]--; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - Program: startFunction, - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - StaticBlock: startFunction, - - IfStatement(node) { - if (node.parent.type !== "IfStatement") { - pushBlock(node); - } - }, - SwitchStatement: pushBlock, - TryStatement: pushBlock, - DoWhileStatement: pushBlock, - WhileStatement: pushBlock, - WithStatement: pushBlock, - ForStatement: pushBlock, - ForInStatement: pushBlock, - ForOfStatement: pushBlock, - - "IfStatement:exit": popBlock, - "SwitchStatement:exit": popBlock, - "TryStatement:exit": popBlock, - "DoWhileStatement:exit": popBlock, - "WhileStatement:exit": popBlock, - "WithStatement:exit": popBlock, - "ForStatement:exit": popBlock, - "ForInStatement:exit": popBlock, - "ForOfStatement:exit": popBlock, - - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - "StaticBlock:exit": endFunction, - "Program:exit": endFunction - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Enforce a maximum depth that blocks can be nested", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-depth", + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0, + }, + max: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + tooDeeply: + "Blocks are nested too deeply ({{depth}}). Maximum allowed is {{maxDepth}}.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = [], + option = context.options[0]; + let maxDepth = 4; + + if ( + typeof option === "object" && + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) + ) { + maxDepth = option.maximum || option.max; + } + if (typeof option === "number") { + maxDepth = option; + } + + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } + + /** + * When parsing is done then pop out the reference + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + } + + /** + * Save the block and Evaluate the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function pushBlock(node) { + const len = ++functionStack[functionStack.length - 1]; + + if (len > maxDepth) { + context.report({ + node, + messageId: "tooDeeply", + data: { depth: len, maxDepth }, + }); + } + } + + /** + * Pop the saved block + * @returns {void} + * @private + */ + function popBlock() { + functionStack[functionStack.length - 1]--; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, + + IfStatement(node) { + if (node.parent.type !== "IfStatement") { + pushBlock(node); + } + }, + SwitchStatement: pushBlock, + TryStatement: pushBlock, + DoWhileStatement: pushBlock, + WhileStatement: pushBlock, + WithStatement: pushBlock, + ForStatement: pushBlock, + ForInStatement: pushBlock, + ForOfStatement: pushBlock, + + "IfStatement:exit": popBlock, + "SwitchStatement:exit": popBlock, + "TryStatement:exit": popBlock, + "DoWhileStatement:exit": popBlock, + "WhileStatement:exit": popBlock, + "WithStatement:exit": popBlock, + "ForStatement:exit": popBlock, + "ForInStatement:exit": popBlock, + "ForOfStatement:exit": popBlock, + + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction, + "Program:exit": endFunction, + }; + }, }; diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 80b92b7f98c1..9db798f2a41a 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -11,53 +11,53 @@ //------------------------------------------------------------------------------ const OPTIONS_SCHEMA = { - type: "object", - properties: { - code: { - type: "integer", - minimum: 0 - }, - comments: { - type: "integer", - minimum: 0 - }, - tabWidth: { - type: "integer", - minimum: 0 - }, - ignorePattern: { - type: "string" - }, - ignoreComments: { - type: "boolean" - }, - ignoreStrings: { - type: "boolean" - }, - ignoreUrls: { - type: "boolean" - }, - ignoreTemplateLiterals: { - type: "boolean" - }, - ignoreRegExpLiterals: { - type: "boolean" - }, - ignoreTrailingComments: { - type: "boolean" - } - }, - additionalProperties: false + type: "object", + properties: { + code: { + type: "integer", + minimum: 0, + }, + comments: { + type: "integer", + minimum: 0, + }, + tabWidth: { + type: "integer", + minimum: 0, + }, + ignorePattern: { + type: "string", + }, + ignoreComments: { + type: "boolean", + }, + ignoreStrings: { + type: "boolean", + }, + ignoreUrls: { + type: "boolean", + }, + ignoreTemplateLiterals: { + type: "boolean", + }, + ignoreRegExpLiterals: { + type: "boolean", + }, + ignoreTrailingComments: { + type: "boolean", + }, + }, + additionalProperties: false, }; const OPTIONS_OR_INTEGER_SCHEMA = { - anyOf: [ - OPTIONS_SCHEMA, - { - type: "integer", - minimum: 0 - } - ] + anyOf: [ + OPTIONS_SCHEMA, + { + type: "integer", + minimum: 0, + }, + ], }; //------------------------------------------------------------------------------ @@ -66,393 +66,432 @@ const OPTIONS_OR_INTEGER_SCHEMA = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "max-len", - url: "https://eslint.style/rules/js/max-len" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce a maximum line length", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-len" - }, - - schema: [ - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_OR_INTEGER_SCHEMA, - OPTIONS_SCHEMA - ], - messages: { - max: "This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.", - maxComment: "This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}." - } - }, - - create(context) { - - /* - * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: - * - They're matching an entire string that we know is a URI - * - We're matching part of a string where we think there *might* be a URL - * - We're only concerned about URLs, as picking out any URI would cause - * too many false positives - * - We don't care about matching the entire URL, any small segment is fine - */ - const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u; - - const sourceCode = context.sourceCode; - - /** - * Computes the length of a line that may contain tabs. The width of each - * tab will be the number of spaces to the next tab stop. - * @param {string} line The line. - * @param {int} tabWidth The width of each tab stop in spaces. - * @returns {int} The computed line length. - * @private - */ - function computeLineLength(line, tabWidth) { - let extraCharacterCount = 0; - - line.replace(/\t/gu, (match, offset) => { - const totalOffset = offset + extraCharacterCount, - previousTabStopOffset = tabWidth ? totalOffset % tabWidth : 0, - spaceCount = tabWidth - previousTabStopOffset; - - extraCharacterCount += spaceCount - 1; // -1 for the replaced tab - }); - return Array.from(line).length + extraCharacterCount; - } - - // The options object must be the last option specifiedâ€Ļ - const options = Object.assign({}, context.options.at(-1)); - - // â€Ļbut max code lengthâ€Ļ - if (typeof context.options[0] === "number") { - options.code = context.options[0]; - } - - // â€Ļand tabWidth can be optionally specified directly as integers. - if (typeof context.options[1] === "number") { - options.tabWidth = context.options[1]; - } - - const maxLength = typeof options.code === "number" ? options.code : 80, - tabWidth = typeof options.tabWidth === "number" ? options.tabWidth : 4, - ignoreComments = !!options.ignoreComments, - ignoreStrings = !!options.ignoreStrings, - ignoreTemplateLiterals = !!options.ignoreTemplateLiterals, - ignoreRegExpLiterals = !!options.ignoreRegExpLiterals, - ignoreTrailingComments = !!options.ignoreTrailingComments || !!options.ignoreComments, - ignoreUrls = !!options.ignoreUrls, - maxCommentLength = options.comments; - let ignorePattern = options.ignorePattern || null; - - if (ignorePattern) { - ignorePattern = new RegExp(ignorePattern, "u"); - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tells if a given comment is trailing: it starts on the current line and - * extends to or past the end of the current line. - * @param {string} line The source line we want to check for a trailing comment on - * @param {number} lineNumber The one-indexed line number for line - * @param {ASTNode} comment The comment to inspect - * @returns {boolean} If the comment is trailing on the given line - */ - function isTrailingComment(line, lineNumber, comment) { - return comment && - (comment.loc.start.line === lineNumber && lineNumber <= comment.loc.end.line) && - (comment.loc.end.line > lineNumber || comment.loc.end.column === line.length); - } - - /** - * Tells if a comment encompasses the entire line. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {boolean} If the comment covers the entire line - */ - function isFullLineComment(line, lineNumber, comment) { - const start = comment.loc.start, - end = comment.loc.end, - isFirstTokenOnLine = !line.slice(0, comment.loc.start.column).trim(); - - return comment && - (start.line < lineNumber || (start.line === lineNumber && isFirstTokenOnLine)) && - (end.line > lineNumber || (end.line === lineNumber && end.column === line.length)); - } - - /** - * Check if a node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. - * @param {ASTNode} node A node to check. - * @returns {boolean} True if the node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. - */ - function isJSXEmptyExpressionInSingleLineContainer(node) { - if (!node || !node.parent || node.type !== "JSXEmptyExpression" || node.parent.type !== "JSXExpressionContainer") { - return false; - } - - const parent = node.parent; - - return parent.loc.start.line === parent.loc.end.line; - } - - /** - * Gets the line after the comment and any remaining trailing whitespace is - * stripped. - * @param {string} line The source line with a trailing comment - * @param {ASTNode} comment The comment to remove - * @returns {string} Line without comment and trailing whitespace - */ - function stripTrailingComment(line, comment) { - - // loc.column is zero-indexed - return line.slice(0, comment.loc.start.column).replace(/\s+$/u, ""); - } - - /** - * Ensure that an array exists at [key] on `object`, and add `value` to it. - * @param {Object} object the object to mutate - * @param {string} key the object's key - * @param {any} value the value to add - * @returns {void} - * @private - */ - function ensureArrayAndPush(object, key, value) { - if (!Array.isArray(object[key])) { - object[key] = []; - } - object[key].push(value); - } - - /** - * Retrieves an array containing all strings (" or ') in the source code. - * @returns {ASTNode[]} An array of string nodes. - */ - function getAllStrings() { - return sourceCode.ast.tokens.filter(token => (token.type === "String" || - (token.type === "JSXText" && sourceCode.getNodeByRangeIndex(token.range[0] - 1).type === "JSXAttribute"))); - } - - /** - * Retrieves an array containing all template literals in the source code. - * @returns {ASTNode[]} An array of template literal nodes. - */ - function getAllTemplateLiterals() { - return sourceCode.ast.tokens.filter(token => token.type === "Template"); - } - - - /** - * Retrieves an array containing all RegExp literals in the source code. - * @returns {ASTNode[]} An array of RegExp literal nodes. - */ - function getAllRegExpLiterals() { - return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression"); - } - - /** - * - * reduce an array of AST nodes by line number, both start and end. - * @param {ASTNode[]} arr array of AST nodes - * @returns {Object} accululated AST nodes - */ - function groupArrayByLineNumber(arr) { - const obj = {}; - - for (let i = 0; i < arr.length; i++) { - const node = arr[i]; - - for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) { - ensureArrayAndPush(obj, j, node); - } - } - return obj; - } - - /** - * Returns an array of all comments in the source code. - * If the element in the array is a JSXEmptyExpression contained with a single line JSXExpressionContainer, - * the element is changed with JSXExpressionContainer node. - * @returns {ASTNode[]} An array of comment nodes - */ - function getAllComments() { - const comments = []; - - sourceCode.getAllComments() - .forEach(commentNode => { - const containingNode = sourceCode.getNodeByRangeIndex(commentNode.range[0]); - - if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) { - - // push a unique node only - if (comments.at(-1) !== containingNode.parent) { - comments.push(containingNode.parent); - } - } else { - comments.push(commentNode); - } - }); - - return comments; - } - - /** - * Check the program for max length - * @param {ASTNode} node Node to examine - * @returns {void} - * @private - */ - function checkProgramForMaxLength(node) { - - // split (honors line-ending) - const lines = sourceCode.lines, - - // list of comments to ignore - comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? getAllComments() : []; - - // we iterate over comments in parallel with the lines - let commentsIndex = 0; - - const strings = getAllStrings(); - const stringsByLine = groupArrayByLineNumber(strings); - - const templateLiterals = getAllTemplateLiterals(); - const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals); - - const regExpLiterals = getAllRegExpLiterals(); - const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals); - - lines.forEach((line, i) => { - - // i is zero-indexed, line numbers are one-indexed - const lineNumber = i + 1; - - /* - * if we're checking comment length; we need to know whether this - * line is a comment - */ - let lineIsComment = false; - let textToMeasure; - - /* - * We can short-circuit the comment checks if we're already out of - * comments to check. - */ - if (commentsIndex < comments.length) { - let comment; - - // iterate over comments until we find one past the current line - do { - comment = comments[++commentsIndex]; - } while (comment && comment.loc.start.line <= lineNumber); - - // and step back by one - comment = comments[--commentsIndex]; - - if (isFullLineComment(line, lineNumber, comment)) { - lineIsComment = true; - textToMeasure = line; - } else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) { - textToMeasure = stripTrailingComment(line, comment); - - // ignore multiple trailing comments in the same line - let lastIndex = commentsIndex; - - while (isTrailingComment(textToMeasure, lineNumber, comments[--lastIndex])) { - textToMeasure = stripTrailingComment(textToMeasure, comments[lastIndex]); - } - } else { - textToMeasure = line; - } - } else { - textToMeasure = line; - } - if (ignorePattern && ignorePattern.test(textToMeasure) || - ignoreUrls && URL_REGEXP.test(textToMeasure) || - ignoreStrings && stringsByLine[lineNumber] || - ignoreTemplateLiterals && templateLiteralsByLine[lineNumber] || - ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber] - ) { - - // ignore this line - return; - } - - const lineLength = computeLineLength(textToMeasure, tabWidth); - const commentLengthApplies = lineIsComment && maxCommentLength; - - if (lineIsComment && ignoreComments) { - return; - } - - const loc = { - start: { - line: lineNumber, - column: 0 - }, - end: { - line: lineNumber, - column: textToMeasure.length - } - }; - - if (commentLengthApplies) { - if (lineLength > maxCommentLength) { - context.report({ - node, - loc, - messageId: "maxComment", - data: { - lineLength, - maxCommentLength - } - }); - } - } else if (lineLength > maxLength) { - context.report({ - node, - loc, - messageId: "max", - data: { - lineLength, - maxLength - } - }); - } - }); - } - - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - Program: checkProgramForMaxLength - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "max-len", + url: "https://eslint.style/rules/js/max-len", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce a maximum line length", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-len", + }, + + schema: [ + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_OR_INTEGER_SCHEMA, + OPTIONS_SCHEMA, + ], + messages: { + max: "This line has a length of {{lineLength}}. Maximum allowed is {{maxLength}}.", + maxComment: + "This line has a comment length of {{lineLength}}. Maximum allowed is {{maxCommentLength}}.", + }, + }, + + create(context) { + /* + * Inspired by http://tools.ietf.org/html/rfc3986#appendix-B, however: + * - They're matching an entire string that we know is a URI + * - We're matching part of a string where we think there *might* be a URL + * - We're only concerned about URLs, as picking out any URI would cause + * too many false positives + * - We don't care about matching the entire URL, any small segment is fine + */ + const URL_REGEXP = /[^:/?#]:\/\/[^?#]/u; + + const sourceCode = context.sourceCode; + + /** + * Computes the length of a line that may contain tabs. The width of each + * tab will be the number of spaces to the next tab stop. + * @param {string} line The line. + * @param {int} tabWidth The width of each tab stop in spaces. + * @returns {int} The computed line length. + * @private + */ + function computeLineLength(line, tabWidth) { + let extraCharacterCount = 0; + + line.replace(/\t/gu, (match, offset) => { + const totalOffset = offset + extraCharacterCount, + previousTabStopOffset = tabWidth + ? totalOffset % tabWidth + : 0, + spaceCount = tabWidth - previousTabStopOffset; + + extraCharacterCount += spaceCount - 1; // -1 for the replaced tab + }); + return Array.from(line).length + extraCharacterCount; + } + + // The options object must be the last option specifiedâ€Ļ + const options = Object.assign({}, context.options.at(-1)); + + // â€Ļbut max code lengthâ€Ļ + if (typeof context.options[0] === "number") { + options.code = context.options[0]; + } + + // â€Ļand tabWidth can be optionally specified directly as integers. + if (typeof context.options[1] === "number") { + options.tabWidth = context.options[1]; + } + + const maxLength = typeof options.code === "number" ? options.code : 80, + tabWidth = + typeof options.tabWidth === "number" ? options.tabWidth : 4, + ignoreComments = !!options.ignoreComments, + ignoreStrings = !!options.ignoreStrings, + ignoreTemplateLiterals = !!options.ignoreTemplateLiterals, + ignoreRegExpLiterals = !!options.ignoreRegExpLiterals, + ignoreTrailingComments = + !!options.ignoreTrailingComments || !!options.ignoreComments, + ignoreUrls = !!options.ignoreUrls, + maxCommentLength = options.comments; + let ignorePattern = options.ignorePattern || null; + + if (ignorePattern) { + ignorePattern = new RegExp(ignorePattern, "u"); + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a given comment is trailing: it starts on the current line and + * extends to or past the end of the current line. + * @param {string} line The source line we want to check for a trailing comment on + * @param {number} lineNumber The one-indexed line number for line + * @param {ASTNode} comment The comment to inspect + * @returns {boolean} If the comment is trailing on the given line + */ + function isTrailingComment(line, lineNumber, comment) { + return ( + comment && + comment.loc.start.line === lineNumber && + lineNumber <= comment.loc.end.line && + (comment.loc.end.line > lineNumber || + comment.loc.end.column === line.length) + ); + } + + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + const start = comment.loc.start, + end = comment.loc.end, + isFirstTokenOnLine = !line + .slice(0, comment.loc.start.column) + .trim(); + + return ( + comment && + (start.line < lineNumber || + (start.line === lineNumber && isFirstTokenOnLine)) && + (end.line > lineNumber || + (end.line === lineNumber && end.column === line.length)) + ); + } + + /** + * Check if a node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is a JSXEmptyExpression contained in a single line JSXExpressionContainer. + */ + function isJSXEmptyExpressionInSingleLineContainer(node) { + if ( + !node || + !node.parent || + node.type !== "JSXEmptyExpression" || + node.parent.type !== "JSXExpressionContainer" + ) { + return false; + } + + const parent = node.parent; + + return parent.loc.start.line === parent.loc.end.line; + } + + /** + * Gets the line after the comment and any remaining trailing whitespace is + * stripped. + * @param {string} line The source line with a trailing comment + * @param {ASTNode} comment The comment to remove + * @returns {string} Line without comment and trailing whitespace + */ + function stripTrailingComment(line, comment) { + // loc.column is zero-indexed + return line.slice(0, comment.loc.start.column).replace(/\s+$/u, ""); + } + + /** + * Ensure that an array exists at [key] on `object`, and add `value` to it. + * @param {Object} object the object to mutate + * @param {string} key the object's key + * @param {any} value the value to add + * @returns {void} + * @private + */ + function ensureArrayAndPush(object, key, value) { + if (!Array.isArray(object[key])) { + object[key] = []; + } + object[key].push(value); + } + + /** + * Retrieves an array containing all strings (" or ') in the source code. + * @returns {ASTNode[]} An array of string nodes. + */ + function getAllStrings() { + return sourceCode.ast.tokens.filter( + token => + token.type === "String" || + (token.type === "JSXText" && + sourceCode.getNodeByRangeIndex(token.range[0] - 1) + .type === "JSXAttribute"), + ); + } + + /** + * Retrieves an array containing all template literals in the source code. + * @returns {ASTNode[]} An array of template literal nodes. + */ + function getAllTemplateLiterals() { + return sourceCode.ast.tokens.filter( + token => token.type === "Template", + ); + } + + /** + * Retrieves an array containing all RegExp literals in the source code. + * @returns {ASTNode[]} An array of RegExp literal nodes. + */ + function getAllRegExpLiterals() { + return sourceCode.ast.tokens.filter( + token => token.type === "RegularExpression", + ); + } + + /** + * + * reduce an array of AST nodes by line number, both start and end. + * @param {ASTNode[]} arr array of AST nodes + * @returns {Object} accululated AST nodes + */ + function groupArrayByLineNumber(arr) { + const obj = {}; + + for (let i = 0; i < arr.length; i++) { + const node = arr[i]; + + for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) { + ensureArrayAndPush(obj, j, node); + } + } + return obj; + } + + /** + * Returns an array of all comments in the source code. + * If the element in the array is a JSXEmptyExpression contained with a single line JSXExpressionContainer, + * the element is changed with JSXExpressionContainer node. + * @returns {ASTNode[]} An array of comment nodes + */ + function getAllComments() { + const comments = []; + + sourceCode.getAllComments().forEach(commentNode => { + const containingNode = sourceCode.getNodeByRangeIndex( + commentNode.range[0], + ); + + if (isJSXEmptyExpressionInSingleLineContainer(containingNode)) { + // push a unique node only + if (comments.at(-1) !== containingNode.parent) { + comments.push(containingNode.parent); + } + } else { + comments.push(commentNode); + } + }); + + return comments; + } + + /** + * Check the program for max length + * @param {ASTNode} node Node to examine + * @returns {void} + * @private + */ + function checkProgramForMaxLength(node) { + // split (honors line-ending) + const lines = sourceCode.lines, + // list of comments to ignore + comments = + ignoreComments || maxCommentLength || ignoreTrailingComments + ? getAllComments() + : []; + + // we iterate over comments in parallel with the lines + let commentsIndex = 0; + + const strings = getAllStrings(); + const stringsByLine = groupArrayByLineNumber(strings); + + const templateLiterals = getAllTemplateLiterals(); + const templateLiteralsByLine = + groupArrayByLineNumber(templateLiterals); + + const regExpLiterals = getAllRegExpLiterals(); + const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals); + + lines.forEach((line, i) => { + // i is zero-indexed, line numbers are one-indexed + const lineNumber = i + 1; + + /* + * if we're checking comment length; we need to know whether this + * line is a comment + */ + let lineIsComment = false; + let textToMeasure; + + /* + * We can short-circuit the comment checks if we're already out of + * comments to check. + */ + if (commentsIndex < comments.length) { + let comment; + + // iterate over comments until we find one past the current line + do { + comment = comments[++commentsIndex]; + } while (comment && comment.loc.start.line <= lineNumber); + + // and step back by one + comment = comments[--commentsIndex]; + + if (isFullLineComment(line, lineNumber, comment)) { + lineIsComment = true; + textToMeasure = line; + } else if ( + ignoreTrailingComments && + isTrailingComment(line, lineNumber, comment) + ) { + textToMeasure = stripTrailingComment(line, comment); + + // ignore multiple trailing comments in the same line + let lastIndex = commentsIndex; + + while ( + isTrailingComment( + textToMeasure, + lineNumber, + comments[--lastIndex], + ) + ) { + textToMeasure = stripTrailingComment( + textToMeasure, + comments[lastIndex], + ); + } + } else { + textToMeasure = line; + } + } else { + textToMeasure = line; + } + if ( + (ignorePattern && ignorePattern.test(textToMeasure)) || + (ignoreUrls && URL_REGEXP.test(textToMeasure)) || + (ignoreStrings && stringsByLine[lineNumber]) || + (ignoreTemplateLiterals && + templateLiteralsByLine[lineNumber]) || + (ignoreRegExpLiterals && regExpLiteralsByLine[lineNumber]) + ) { + // ignore this line + return; + } + + const lineLength = computeLineLength(textToMeasure, tabWidth); + const commentLengthApplies = lineIsComment && maxCommentLength; + + if (lineIsComment && ignoreComments) { + return; + } + + const loc = { + start: { + line: lineNumber, + column: 0, + }, + end: { + line: lineNumber, + column: textToMeasure.length, + }, + }; + + if (commentLengthApplies) { + if (lineLength > maxCommentLength) { + context.report({ + node, + loc, + messageId: "maxComment", + data: { + lineLength, + maxCommentLength, + }, + }); + } + } else if (lineLength > maxLength) { + context.report({ + node, + loc, + messageId: "max", + data: { + lineLength, + maxLength, + }, + }); + } + }); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: checkProgramForMaxLength, + }; + }, }; diff --git a/lib/rules/max-lines-per-function.js b/lib/rules/max-lines-per-function.js index f981922a74ac..e3ce0a15d441 100644 --- a/lib/rules/max-lines-per-function.js +++ b/lib/rules/max-lines-per-function.js @@ -16,33 +16,33 @@ const { upperCaseFirst } = require("../shared/string-utils"); //------------------------------------------------------------------------------ const OPTIONS_SCHEMA = { - type: "object", - properties: { - max: { - type: "integer", - minimum: 0 - }, - skipComments: { - type: "boolean" - }, - skipBlankLines: { - type: "boolean" - }, - IIFEs: { - type: "boolean" - } - }, - additionalProperties: false + type: "object", + properties: { + max: { + type: "integer", + minimum: 0, + }, + skipComments: { + type: "boolean", + }, + skipBlankLines: { + type: "boolean", + }, + IIFEs: { + type: "boolean", + }, + }, + additionalProperties: false, }; const OPTIONS_OR_INTEGER_SCHEMA = { - oneOf: [ - OPTIONS_SCHEMA, - { - type: "integer", - minimum: 1 - } - ] + oneOf: [ + OPTIONS_SCHEMA, + { + type: "integer", + minimum: 1, + }, + ], }; /** @@ -51,14 +51,14 @@ const OPTIONS_OR_INTEGER_SCHEMA = { * @returns {Map} A map with numeric keys (source code line numbers) and comment token values. */ function getCommentLineNumbers(comments) { - const map = new Map(); - - comments.forEach(comment => { - for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) { - map.set(i, comment); - } - }); - return map; + const map = new Map(); + + comments.forEach(comment => { + for (let i = comment.loc.start.line; i <= comment.loc.end.line; i++) { + map.set(i, comment); + } + }); + return map; } //------------------------------------------------------------------------------ @@ -67,147 +67,172 @@ function getCommentLineNumbers(comments) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce a maximum number of lines of code in a function", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-lines-per-function" - }, - - schema: [ - OPTIONS_OR_INTEGER_SCHEMA - ], - messages: { - exceed: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const lines = sourceCode.lines; - - const option = context.options[0]; - let maxLines = 50; - let skipComments = false; - let skipBlankLines = false; - let IIFEs = false; - - if (typeof option === "object") { - maxLines = typeof option.max === "number" ? option.max : 50; - skipComments = !!option.skipComments; - skipBlankLines = !!option.skipBlankLines; - IIFEs = !!option.IIFEs; - } else if (typeof option === "number") { - maxLines = option; - } - - const commentLineNumbers = getCommentLineNumbers(sourceCode.getAllComments()); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tells if a comment encompasses the entire line. - * @param {string} line The source line with a trailing comment - * @param {number} lineNumber The one-indexed line number this is on - * @param {ASTNode} comment The comment to remove - * @returns {boolean} If the comment covers the entire line - */ - function isFullLineComment(line, lineNumber, comment) { - const start = comment.loc.start, - end = comment.loc.end, - isFirstTokenOnLine = start.line === lineNumber && !line.slice(0, start.column).trim(), - isLastTokenOnLine = end.line === lineNumber && !line.slice(end.column).trim(); - - return comment && - (start.line < lineNumber || isFirstTokenOnLine) && - (end.line > lineNumber || isLastTokenOnLine); - } - - /** - * Identifies is a node is a FunctionExpression which is part of an IIFE - * @param {ASTNode} node Node to test - * @returns {boolean} True if it's an IIFE - */ - function isIIFE(node) { - return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node; - } - - /** - * Identifies is a node is a FunctionExpression which is embedded within a MethodDefinition or Property - * @param {ASTNode} node Node to test - * @returns {boolean} True if it's a FunctionExpression embedded within a MethodDefinition or Property - */ - function isEmbedded(node) { - if (!node.parent) { - return false; - } - if (node !== node.parent.value) { - return false; - } - if (node.parent.type === "MethodDefinition") { - return true; - } - if (node.parent.type === "Property") { - return node.parent.method === true || node.parent.kind === "get" || node.parent.kind === "set"; - } - return false; - } - - /** - * Count the lines in the function - * @param {ASTNode} funcNode Function AST node - * @returns {void} - * @private - */ - function processFunction(funcNode) { - const node = isEmbedded(funcNode) ? funcNode.parent : funcNode; - - if (!IIFEs && isIIFE(node)) { - return; - } - let lineCount = 0; - - for (let i = node.loc.start.line - 1; i < node.loc.end.line; ++i) { - const line = lines[i]; - - if (skipComments) { - if (commentLineNumbers.has(i + 1) && isFullLineComment(line, i + 1, commentLineNumbers.get(i + 1))) { - continue; - } - } - - if (skipBlankLines) { - if (line.match(/^\s*$/u)) { - continue; - } - } - - lineCount++; - } - - if (lineCount > maxLines) { - const name = upperCaseFirst(astUtils.getFunctionNameWithKind(funcNode)); - - context.report({ - node, - messageId: "exceed", - data: { name, lineCount, maxLines } - }); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - FunctionDeclaration: processFunction, - FunctionExpression: processFunction, - ArrowFunctionExpression: processFunction - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce a maximum number of lines of code in a function", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-lines-per-function", + }, + + schema: [OPTIONS_OR_INTEGER_SCHEMA], + messages: { + exceed: "{{name}} has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const lines = sourceCode.lines; + + const option = context.options[0]; + let maxLines = 50; + let skipComments = false; + let skipBlankLines = false; + let IIFEs = false; + + if (typeof option === "object") { + maxLines = typeof option.max === "number" ? option.max : 50; + skipComments = !!option.skipComments; + skipBlankLines = !!option.skipBlankLines; + IIFEs = !!option.IIFEs; + } else if (typeof option === "number") { + maxLines = option; + } + + const commentLineNumbers = getCommentLineNumbers( + sourceCode.getAllComments(), + ); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tells if a comment encompasses the entire line. + * @param {string} line The source line with a trailing comment + * @param {number} lineNumber The one-indexed line number this is on + * @param {ASTNode} comment The comment to remove + * @returns {boolean} If the comment covers the entire line + */ + function isFullLineComment(line, lineNumber, comment) { + const start = comment.loc.start, + end = comment.loc.end, + isFirstTokenOnLine = + start.line === lineNumber && + !line.slice(0, start.column).trim(), + isLastTokenOnLine = + end.line === lineNumber && !line.slice(end.column).trim(); + + return ( + comment && + (start.line < lineNumber || isFirstTokenOnLine) && + (end.line > lineNumber || isLastTokenOnLine) + ); + } + + /** + * Identifies is a node is a FunctionExpression which is part of an IIFE + * @param {ASTNode} node Node to test + * @returns {boolean} True if it's an IIFE + */ + function isIIFE(node) { + return ( + (node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression") && + node.parent && + node.parent.type === "CallExpression" && + node.parent.callee === node + ); + } + + /** + * Identifies is a node is a FunctionExpression which is embedded within a MethodDefinition or Property + * @param {ASTNode} node Node to test + * @returns {boolean} True if it's a FunctionExpression embedded within a MethodDefinition or Property + */ + function isEmbedded(node) { + if (!node.parent) { + return false; + } + if (node !== node.parent.value) { + return false; + } + if (node.parent.type === "MethodDefinition") { + return true; + } + if (node.parent.type === "Property") { + return ( + node.parent.method === true || + node.parent.kind === "get" || + node.parent.kind === "set" + ); + } + return false; + } + + /** + * Count the lines in the function + * @param {ASTNode} funcNode Function AST node + * @returns {void} + * @private + */ + function processFunction(funcNode) { + const node = isEmbedded(funcNode) ? funcNode.parent : funcNode; + + if (!IIFEs && isIIFE(node)) { + return; + } + let lineCount = 0; + + for (let i = node.loc.start.line - 1; i < node.loc.end.line; ++i) { + const line = lines[i]; + + if (skipComments) { + if ( + commentLineNumbers.has(i + 1) && + isFullLineComment( + line, + i + 1, + commentLineNumbers.get(i + 1), + ) + ) { + continue; + } + } + + if (skipBlankLines) { + if (line.match(/^\s*$/u)) { + continue; + } + } + + lineCount++; + } + + if (lineCount > maxLines) { + const name = upperCaseFirst( + astUtils.getFunctionNameWithKind(funcNode), + ); + + context.report({ + node, + messageId: "exceed", + data: { name, lineCount, maxLines }, + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: processFunction, + FunctionExpression: processFunction, + ArrowFunctionExpression: processFunction, + }; + }, }; diff --git a/lib/rules/max-lines.js b/lib/rules/max-lines.js index d8215794cb05..fc7c23083153 100644 --- a/lib/rules/max-lines.js +++ b/lib/rules/max-lines.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {number[]} The range of numbers */ function range(start, end) { - return [...Array(end - start).keys()].map(x => x + start); + return [...Array(end - start).keys()].map(x => x + start); } //------------------------------------------------------------------------------ @@ -30,164 +30,160 @@ function range(start, end) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce a maximum number of lines per file", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-lines" - }, - - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - max: { - type: "integer", - minimum: 0 - }, - skipComments: { - type: "boolean" - }, - skipBlankLines: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - } - ], - messages: { - exceed: - "File has too many lines ({{actual}}). Maximum allowed is {{max}}." - } - }, - - create(context) { - const option = context.options[0]; - let max = 300; - - if ( - typeof option === "object" && - Object.hasOwn(option, "max") - ) { - max = option.max; - } else if (typeof option === "number") { - max = option; - } - - const skipComments = option && option.skipComments; - const skipBlankLines = option && option.skipBlankLines; - - const sourceCode = context.sourceCode; - - /** - * Returns whether or not a token is a comment node type - * @param {Token} token The token to check - * @returns {boolean} True if the token is a comment node - */ - function isCommentNodeType(token) { - return token && (token.type === "Block" || token.type === "Line"); - } - - /** - * Returns the line numbers of a comment that don't have any code on the same line - * @param {Node} comment The comment node to check - * @returns {number[]} The line numbers - */ - function getLinesWithoutCode(comment) { - let start = comment.loc.start.line; - let end = comment.loc.end.line; - - let token; - - token = comment; - do { - token = sourceCode.getTokenBefore(token, { - includeComments: true - }); - } while (isCommentNodeType(token)); - - if (token && astUtils.isTokenOnSameLine(token, comment)) { - start += 1; - } - - token = comment; - do { - token = sourceCode.getTokenAfter(token, { - includeComments: true - }); - } while (isCommentNodeType(token)); - - if (token && astUtils.isTokenOnSameLine(comment, token)) { - end -= 1; - } - - if (start <= end) { - return range(start, end + 1); - } - return []; - } - - return { - "Program:exit"() { - let lines = sourceCode.lines.map((text, i) => ({ - lineNumber: i + 1, - text - })); - - /* - * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end. - * That isn't a real line, so we shouldn't count it. - */ - if (lines.length > 1 && lines.at(-1).text === "") { - lines.pop(); - } - - if (skipBlankLines) { - lines = lines.filter(l => l.text.trim() !== ""); - } - - if (skipComments) { - const comments = sourceCode.getAllComments(); - - const commentLines = new Set(comments.flatMap(getLinesWithoutCode)); - - lines = lines.filter( - l => !commentLines.has(l.lineNumber) - ); - } - - if (lines.length > max) { - const loc = { - start: { - line: lines[max].lineNumber, - column: 0 - }, - end: { - line: sourceCode.lines.length, - column: sourceCode.lines.at(-1).length - } - }; - - context.report({ - loc, - messageId: "exceed", - data: { - max, - actual: lines.length - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Enforce a maximum number of lines per file", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-lines", + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0, + }, + skipComments: { + type: "boolean", + }, + skipBlankLines: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + exceed: "File has too many lines ({{actual}}). Maximum allowed is {{max}}.", + }, + }, + + create(context) { + const option = context.options[0]; + let max = 300; + + if (typeof option === "object" && Object.hasOwn(option, "max")) { + max = option.max; + } else if (typeof option === "number") { + max = option; + } + + const skipComments = option && option.skipComments; + const skipBlankLines = option && option.skipBlankLines; + + const sourceCode = context.sourceCode; + + /** + * Returns whether or not a token is a comment node type + * @param {Token} token The token to check + * @returns {boolean} True if the token is a comment node + */ + function isCommentNodeType(token) { + return token && (token.type === "Block" || token.type === "Line"); + } + + /** + * Returns the line numbers of a comment that don't have any code on the same line + * @param {Node} comment The comment node to check + * @returns {number[]} The line numbers + */ + function getLinesWithoutCode(comment) { + let start = comment.loc.start.line; + let end = comment.loc.end.line; + + let token; + + token = comment; + do { + token = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + } while (isCommentNodeType(token)); + + if (token && astUtils.isTokenOnSameLine(token, comment)) { + start += 1; + } + + token = comment; + do { + token = sourceCode.getTokenAfter(token, { + includeComments: true, + }); + } while (isCommentNodeType(token)); + + if (token && astUtils.isTokenOnSameLine(comment, token)) { + end -= 1; + } + + if (start <= end) { + return range(start, end + 1); + } + return []; + } + + return { + "Program:exit"() { + let lines = sourceCode.lines.map((text, i) => ({ + lineNumber: i + 1, + text, + })); + + /* + * If file ends with a linebreak, `sourceCode.lines` will have one extra empty line at the end. + * That isn't a real line, so we shouldn't count it. + */ + if (lines.length > 1 && lines.at(-1).text === "") { + lines.pop(); + } + + if (skipBlankLines) { + lines = lines.filter(l => l.text.trim() !== ""); + } + + if (skipComments) { + const comments = sourceCode.getAllComments(); + + const commentLines = new Set( + comments.flatMap(getLinesWithoutCode), + ); + + lines = lines.filter(l => !commentLines.has(l.lineNumber)); + } + + if (lines.length > max) { + const loc = { + start: { + line: lines[max].lineNumber, + column: 0, + }, + end: { + line: sourceCode.lines.length, + column: sourceCode.lines.at(-1).length, + }, + }; + + context.report({ + loc, + messageId: "exceed", + data: { + max, + actual: lines.length, + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/max-nested-callbacks.js b/lib/rules/max-nested-callbacks.js index 448c0bd44333..a3e4d831564b 100644 --- a/lib/rules/max-nested-callbacks.js +++ b/lib/rules/max-nested-callbacks.js @@ -11,107 +11,105 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce a maximum depth that callbacks can be nested", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-nested-callbacks" - }, - - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - maximum: { - type: "integer", - minimum: 0 - }, - max: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - ], - messages: { - exceed: "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Constants - //-------------------------------------------------------------------------- - const option = context.options[0]; - let THRESHOLD = 10; - - if ( - typeof option === "object" && - (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) - ) { - THRESHOLD = option.maximum || option.max; - } else if (typeof option === "number") { - THRESHOLD = option; - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const callbackStack = []; - - /** - * Checks a given function node for too many callbacks. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - const parent = node.parent; - - if (parent.type === "CallExpression") { - callbackStack.push(node); - } - - if (callbackStack.length > THRESHOLD) { - const opts = { num: callbackStack.length, max: THRESHOLD }; - - context.report({ node, messageId: "exceed", data: opts }); - } - } - - /** - * Pops the call stack. - * @returns {void} - * @private - */ - function popStack() { - callbackStack.pop(); - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - ArrowFunctionExpression: checkFunction, - "ArrowFunctionExpression:exit": popStack, - - FunctionExpression: checkFunction, - "FunctionExpression:exit": popStack - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Enforce a maximum depth that callbacks can be nested", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-nested-callbacks", + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0, + }, + max: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + exceed: "Too many nested callbacks ({{num}}). Maximum allowed is {{max}}.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------------- + const option = context.options[0]; + let THRESHOLD = 10; + + if ( + typeof option === "object" && + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) + ) { + THRESHOLD = option.maximum || option.max; + } else if (typeof option === "number") { + THRESHOLD = option; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const callbackStack = []; + + /** + * Checks a given function node for too many callbacks. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + const parent = node.parent; + + if (parent.type === "CallExpression") { + callbackStack.push(node); + } + + if (callbackStack.length > THRESHOLD) { + const opts = { num: callbackStack.length, max: THRESHOLD }; + + context.report({ node, messageId: "exceed", data: opts }); + } + } + + /** + * Pops the call stack. + * @returns {void} + * @private + */ + function popStack() { + callbackStack.pop(); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + ArrowFunctionExpression: checkFunction, + "ArrowFunctionExpression:exit": popStack, + + FunctionExpression: checkFunction, + "FunctionExpression:exit": popStack, + }; + }, }; diff --git a/lib/rules/max-params.js b/lib/rules/max-params.js index eb043ab190b2..fe5d4ef35d9e 100644 --- a/lib/rules/max-params.js +++ b/lib/rules/max-params.js @@ -18,85 +18,87 @@ const { upperCaseFirst } = require("../shared/string-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Enforce a maximum number of parameters in function definitions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-params" - }, + docs: { + description: + "Enforce a maximum number of parameters in function definitions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-params", + }, - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - maximum: { - type: "integer", - minimum: 0 - }, - max: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - } - ] - } - ], - messages: { - exceed: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}." - } - }, + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0, + }, + max: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + exceed: "{{name}} has too many parameters ({{count}}). Maximum allowed is {{max}}.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; - const option = context.options[0]; - let numParams = 3; + create(context) { + const sourceCode = context.sourceCode; + const option = context.options[0]; + let numParams = 3; - if ( - typeof option === "object" && - (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) - ) { - numParams = option.maximum || option.max; - } - if (typeof option === "number") { - numParams = option; - } + if ( + typeof option === "object" && + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) + ) { + numParams = option.maximum || option.max; + } + if (typeof option === "number") { + numParams = option; + } - /** - * Checks a function to see if it has too many parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkFunction(node) { - if (node.params.length > numParams) { - context.report({ - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - node, - messageId: "exceed", - data: { - name: upperCaseFirst(astUtils.getFunctionNameWithKind(node)), - count: node.params.length, - max: numParams - } - }); - } - } + /** + * Checks a function to see if it has too many parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkFunction(node) { + if (node.params.length > numParams) { + context.report({ + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + node, + messageId: "exceed", + data: { + name: upperCaseFirst( + astUtils.getFunctionNameWithKind(node), + ), + count: node.params.length, + max: numParams, + }, + }); + } + } - return { - FunctionDeclaration: checkFunction, - ArrowFunctionExpression: checkFunction, - FunctionExpression: checkFunction - }; - - } + return { + FunctionDeclaration: checkFunction, + ArrowFunctionExpression: checkFunction, + FunctionExpression: checkFunction, + }; + }, }; diff --git a/lib/rules/max-statements-per-line.js b/lib/rules/max-statements-per-line.js index ec3961a8b1b9..d61f8076f486 100644 --- a/lib/rules/max-statements-per-line.js +++ b/lib/rules/max-statements-per-line.js @@ -17,201 +17,208 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "max-statements-per-line", - url: "https://eslint.style/rules/js/max-statements-per-line" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce a maximum number of statements allowed per line", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-statements-per-line" - }, - - schema: [ - { - type: "object", - properties: { - max: { - type: "integer", - minimum: 1, - default: 1 - } - }, - additionalProperties: false - } - ], - messages: { - exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}." - } - }, - - create(context) { - - const sourceCode = context.sourceCode, - options = context.options[0] || {}, - maxStatementsPerLine = typeof options.max !== "undefined" ? options.max : 1; - - let lastStatementLine = 0, - numberOfStatementsOnThisLine = 0, - firstExtraStatement; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const SINGLE_CHILD_ALLOWED = /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u; - - /** - * Reports with the first extra statement, and clears it. - * @returns {void} - */ - function reportFirstExtraStatementAndClear() { - if (firstExtraStatement) { - context.report({ - node: firstExtraStatement, - messageId: "exceed", - data: { - numberOfStatementsOnThisLine, - maxStatementsPerLine, - statements: numberOfStatementsOnThisLine === 1 ? "statement" : "statements" - } - }); - } - firstExtraStatement = null; - } - - /** - * Gets the actual last token of a given node. - * @param {ASTNode} node A node to get. This is a node except EmptyStatement. - * @returns {Token} The actual last token. - */ - function getActualLastToken(node) { - return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); - } - - /** - * Addresses a given node. - * It updates the state of this rule, then reports the node if the node violated this rule. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function enterStatement(node) { - const line = node.loc.start.line; - - /* - * Skip to allow non-block statements if this is direct child of control statements. - * `if (a) foo();` is counted as 1. - * But `if (a) foo(); else foo();` should be counted as 2. - */ - if (SINGLE_CHILD_ALLOWED.test(node.parent.type) && - node.parent.alternate !== node - ) { - return; - } - - // Update state. - if (line === lastStatementLine) { - numberOfStatementsOnThisLine += 1; - } else { - reportFirstExtraStatementAndClear(); - numberOfStatementsOnThisLine = 1; - lastStatementLine = line; - } - - // Reports if the node violated this rule. - if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { - firstExtraStatement = firstExtraStatement || node; - } - } - - /** - * Updates the state of this rule with the end line of leaving node to check with the next statement. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function leaveStatement(node) { - const line = getActualLastToken(node).loc.end.line; - - // Update state. - if (line !== lastStatementLine) { - reportFirstExtraStatementAndClear(); - numberOfStatementsOnThisLine = 1; - lastStatementLine = line; - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - BreakStatement: enterStatement, - ClassDeclaration: enterStatement, - ContinueStatement: enterStatement, - DebuggerStatement: enterStatement, - DoWhileStatement: enterStatement, - ExpressionStatement: enterStatement, - ForInStatement: enterStatement, - ForOfStatement: enterStatement, - ForStatement: enterStatement, - FunctionDeclaration: enterStatement, - IfStatement: enterStatement, - ImportDeclaration: enterStatement, - LabeledStatement: enterStatement, - ReturnStatement: enterStatement, - SwitchStatement: enterStatement, - ThrowStatement: enterStatement, - TryStatement: enterStatement, - VariableDeclaration: enterStatement, - WhileStatement: enterStatement, - WithStatement: enterStatement, - ExportNamedDeclaration: enterStatement, - ExportDefaultDeclaration: enterStatement, - ExportAllDeclaration: enterStatement, - - "BreakStatement:exit": leaveStatement, - "ClassDeclaration:exit": leaveStatement, - "ContinueStatement:exit": leaveStatement, - "DebuggerStatement:exit": leaveStatement, - "DoWhileStatement:exit": leaveStatement, - "ExpressionStatement:exit": leaveStatement, - "ForInStatement:exit": leaveStatement, - "ForOfStatement:exit": leaveStatement, - "ForStatement:exit": leaveStatement, - "FunctionDeclaration:exit": leaveStatement, - "IfStatement:exit": leaveStatement, - "ImportDeclaration:exit": leaveStatement, - "LabeledStatement:exit": leaveStatement, - "ReturnStatement:exit": leaveStatement, - "SwitchStatement:exit": leaveStatement, - "ThrowStatement:exit": leaveStatement, - "TryStatement:exit": leaveStatement, - "VariableDeclaration:exit": leaveStatement, - "WhileStatement:exit": leaveStatement, - "WithStatement:exit": leaveStatement, - "ExportNamedDeclaration:exit": leaveStatement, - "ExportDefaultDeclaration:exit": leaveStatement, - "ExportAllDeclaration:exit": leaveStatement, - "Program:exit": reportFirstExtraStatementAndClear - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "max-statements-per-line", + url: "https://eslint.style/rules/js/max-statements-per-line", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce a maximum number of statements allowed per line", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-statements-per-line", + }, + + schema: [ + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 1, + default: 1, + }, + }, + additionalProperties: false, + }, + ], + messages: { + exceed: "This line has {{numberOfStatementsOnThisLine}} {{statements}}. Maximum allowed is {{maxStatementsPerLine}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode, + options = context.options[0] || {}, + maxStatementsPerLine = + typeof options.max !== "undefined" ? options.max : 1; + + let lastStatementLine = 0, + numberOfStatementsOnThisLine = 0, + firstExtraStatement; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const SINGLE_CHILD_ALLOWED = + /^(?:(?:DoWhile|For|ForIn|ForOf|If|Labeled|While)Statement|Export(?:Default|Named)Declaration)$/u; + + /** + * Reports with the first extra statement, and clears it. + * @returns {void} + */ + function reportFirstExtraStatementAndClear() { + if (firstExtraStatement) { + context.report({ + node: firstExtraStatement, + messageId: "exceed", + data: { + numberOfStatementsOnThisLine, + maxStatementsPerLine, + statements: + numberOfStatementsOnThisLine === 1 + ? "statement" + : "statements", + }, + }); + } + firstExtraStatement = null; + } + + /** + * Gets the actual last token of a given node. + * @param {ASTNode} node A node to get. This is a node except EmptyStatement. + * @returns {Token} The actual last token. + */ + function getActualLastToken(node) { + return sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); + } + + /** + * Addresses a given node. + * It updates the state of this rule, then reports the node if the node violated this rule. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function enterStatement(node) { + const line = node.loc.start.line; + + /* + * Skip to allow non-block statements if this is direct child of control statements. + * `if (a) foo();` is counted as 1. + * But `if (a) foo(); else foo();` should be counted as 2. + */ + if ( + SINGLE_CHILD_ALLOWED.test(node.parent.type) && + node.parent.alternate !== node + ) { + return; + } + + // Update state. + if (line === lastStatementLine) { + numberOfStatementsOnThisLine += 1; + } else { + reportFirstExtraStatementAndClear(); + numberOfStatementsOnThisLine = 1; + lastStatementLine = line; + } + + // Reports if the node violated this rule. + if (numberOfStatementsOnThisLine === maxStatementsPerLine + 1) { + firstExtraStatement = firstExtraStatement || node; + } + } + + /** + * Updates the state of this rule with the end line of leaving node to check with the next statement. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function leaveStatement(node) { + const line = getActualLastToken(node).loc.end.line; + + // Update state. + if (line !== lastStatementLine) { + reportFirstExtraStatementAndClear(); + numberOfStatementsOnThisLine = 1; + lastStatementLine = line; + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + BreakStatement: enterStatement, + ClassDeclaration: enterStatement, + ContinueStatement: enterStatement, + DebuggerStatement: enterStatement, + DoWhileStatement: enterStatement, + ExpressionStatement: enterStatement, + ForInStatement: enterStatement, + ForOfStatement: enterStatement, + ForStatement: enterStatement, + FunctionDeclaration: enterStatement, + IfStatement: enterStatement, + ImportDeclaration: enterStatement, + LabeledStatement: enterStatement, + ReturnStatement: enterStatement, + SwitchStatement: enterStatement, + ThrowStatement: enterStatement, + TryStatement: enterStatement, + VariableDeclaration: enterStatement, + WhileStatement: enterStatement, + WithStatement: enterStatement, + ExportNamedDeclaration: enterStatement, + ExportDefaultDeclaration: enterStatement, + ExportAllDeclaration: enterStatement, + + "BreakStatement:exit": leaveStatement, + "ClassDeclaration:exit": leaveStatement, + "ContinueStatement:exit": leaveStatement, + "DebuggerStatement:exit": leaveStatement, + "DoWhileStatement:exit": leaveStatement, + "ExpressionStatement:exit": leaveStatement, + "ForInStatement:exit": leaveStatement, + "ForOfStatement:exit": leaveStatement, + "ForStatement:exit": leaveStatement, + "FunctionDeclaration:exit": leaveStatement, + "IfStatement:exit": leaveStatement, + "ImportDeclaration:exit": leaveStatement, + "LabeledStatement:exit": leaveStatement, + "ReturnStatement:exit": leaveStatement, + "SwitchStatement:exit": leaveStatement, + "ThrowStatement:exit": leaveStatement, + "TryStatement:exit": leaveStatement, + "VariableDeclaration:exit": leaveStatement, + "WhileStatement:exit": leaveStatement, + "WithStatement:exit": leaveStatement, + "ExportNamedDeclaration:exit": leaveStatement, + "ExportDefaultDeclaration:exit": leaveStatement, + "ExportAllDeclaration:exit": leaveStatement, + "Program:exit": reportFirstExtraStatementAndClear, + }; + }, }; diff --git a/lib/rules/max-statements.js b/lib/rules/max-statements.js index 96d5ea7b8ccb..a29e060c1ed1 100644 --- a/lib/rules/max-statements.js +++ b/lib/rules/max-statements.js @@ -18,167 +18,171 @@ const { upperCaseFirst } = require("../shared/string-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce a maximum number of statements allowed in function blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/max-statements" - }, - - schema: [ - { - oneOf: [ - { - type: "integer", - minimum: 0 - }, - { - type: "object", - properties: { - maximum: { - type: "integer", - minimum: 0 - }, - max: { - type: "integer", - minimum: 0 - } - }, - additionalProperties: false - } - ] - }, - { - type: "object", - properties: { - ignoreTopLevelFunctions: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - messages: { - exceed: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const functionStack = [], - option = context.options[0], - ignoreTopLevelFunctions = context.options[1] && context.options[1].ignoreTopLevelFunctions || false, - topLevelFunctions = []; - let maxStatements = 10; - - if ( - typeof option === "object" && - (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) - ) { - maxStatements = option.maximum || option.max; - } else if (typeof option === "number") { - maxStatements = option; - } - - /** - * Reports a node if it has too many statements - * @param {ASTNode} node node to evaluate - * @param {int} count Number of statements in node - * @param {int} max Maximum number of statements allowed - * @returns {void} - * @private - */ - function reportIfTooManyStatements(node, count, max) { - if (count > max) { - const name = upperCaseFirst(astUtils.getFunctionNameWithKind(node)); - - context.report({ - node, - messageId: "exceed", - data: { name, count, max } - }); - } - } - - /** - * When parsing a new function, store it in our function stack - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push(0); - } - - /** - * Evaluate the node at the end of function - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function endFunction(node) { - const count = functionStack.pop(); - - /* - * This rule does not apply to class static blocks, but we have to track them so - * that statements in them do not count as statements in the enclosing function. - */ - if (node.type === "StaticBlock") { - return; - } - - if (ignoreTopLevelFunctions && functionStack.length === 0) { - topLevelFunctions.push({ node, count }); - } else { - reportIfTooManyStatements(node, count, maxStatements); - } - } - - /** - * Increment the count of the functions - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function countStatements(node) { - functionStack[functionStack.length - 1] += node.body.length; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - StaticBlock: startFunction, - - BlockStatement: countStatements, - - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - "StaticBlock:exit": endFunction, - - "Program:exit"() { - if (topLevelFunctions.length === 1) { - return; - } - - topLevelFunctions.forEach(element => { - const count = element.count; - const node = element.node; - - reportIfTooManyStatements(node, count, maxStatements); - }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce a maximum number of statements allowed in function blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/max-statements", + }, + + schema: [ + { + oneOf: [ + { + type: "integer", + minimum: 0, + }, + { + type: "object", + properties: { + maximum: { + type: "integer", + minimum: 0, + }, + max: { + type: "integer", + minimum: 0, + }, + }, + additionalProperties: false, + }, + ], + }, + { + type: "object", + properties: { + ignoreTopLevelFunctions: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + exceed: "{{name}} has too many statements ({{count}}). Maximum allowed is {{max}}.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = [], + option = context.options[0], + ignoreTopLevelFunctions = + (context.options[1] && + context.options[1].ignoreTopLevelFunctions) || + false, + topLevelFunctions = []; + let maxStatements = 10; + + if ( + typeof option === "object" && + (Object.hasOwn(option, "maximum") || Object.hasOwn(option, "max")) + ) { + maxStatements = option.maximum || option.max; + } else if (typeof option === "number") { + maxStatements = option; + } + + /** + * Reports a node if it has too many statements + * @param {ASTNode} node node to evaluate + * @param {int} count Number of statements in node + * @param {int} max Maximum number of statements allowed + * @returns {void} + * @private + */ + function reportIfTooManyStatements(node, count, max) { + if (count > max) { + const name = upperCaseFirst( + astUtils.getFunctionNameWithKind(node), + ); + + context.report({ + node, + messageId: "exceed", + data: { name, count, max }, + }); + } + } + + /** + * When parsing a new function, store it in our function stack + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push(0); + } + + /** + * Evaluate the node at the end of function + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function endFunction(node) { + const count = functionStack.pop(); + + /* + * This rule does not apply to class static blocks, but we have to track them so + * that statements in them do not count as statements in the enclosing function. + */ + if (node.type === "StaticBlock") { + return; + } + + if (ignoreTopLevelFunctions && functionStack.length === 0) { + topLevelFunctions.push({ node, count }); + } else { + reportIfTooManyStatements(node, count, maxStatements); + } + } + + /** + * Increment the count of the functions + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function countStatements(node) { + functionStack[functionStack.length - 1] += node.body.length; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, + + BlockStatement: countStatements, + + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction, + + "Program:exit"() { + if (topLevelFunctions.length === 1) { + return; + } + + topLevelFunctions.forEach(element => { + const count = element.count; + const node = element.node; + + reportIfTooManyStatements(node, count, maxStatements); + }); + }, + }; + }, }; diff --git a/lib/rules/multiline-comment-style.js b/lib/rules/multiline-comment-style.js index e7a1af6e744e..14ea733d7e61 100644 --- a/lib/rules/multiline-comment-style.js +++ b/lib/rules/multiline-comment-style.js @@ -13,482 +13,640 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "multiline-comment-style", - url: "https://eslint.style/rules/js/multiline-comment-style" - } - } - ] - }, - type: "suggestion", - docs: { - description: "Enforce a particular style for multiline comments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/multiline-comment-style" - }, - - fixable: "whitespace", - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["starred-block", "bare-block"] - } - ], - additionalItems: false - }, - { - type: "array", - items: [ - { - enum: ["separate-lines"] - }, - { - type: "object", - properties: { - checkJSDoc: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - additionalItems: false - } - ] - }, - messages: { - expectedBlock: "Expected a block comment instead of consecutive line comments.", - expectedBareBlock: "Expected a block comment without padding stars.", - startNewline: "Expected a linebreak after '/*'.", - endNewline: "Expected a linebreak before '*/'.", - missingStar: "Expected a '*' at the start of this line.", - alignment: "Expected this line to be aligned with the start of the comment.", - expectedLines: "Expected multiple line comments instead of a block comment." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const option = context.options[0] || "starred-block"; - const params = context.options[1] || {}; - const checkJSDoc = !!params.checkJSDoc; - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Checks if a comment line is starred. - * @param {string} line A string representing a comment line. - * @returns {boolean} Whether or not the comment line is starred. - */ - function isStarredCommentLine(line) { - return /^\s*\*/u.test(line); - } - - /** - * Checks if a comment group is in starred-block form. - * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. - * @returns {boolean} Whether or not the comment group is in starred block form. - */ - function isStarredBlockComment([firstComment]) { - if (firstComment.type !== "Block") { - return false; - } - - const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); - - // The first and last lines can only contain whitespace. - return lines.length > 0 && lines.every((line, i) => (i === 0 || i === lines.length - 1 ? /^\s*$/u : /^\s*\*/u).test(line)); - } - - /** - * Checks if a comment group is in JSDoc form. - * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. - * @returns {boolean} Whether or not the comment group is in JSDoc form. - */ - function isJSDocComment([firstComment]) { - if (firstComment.type !== "Block") { - return false; - } - - const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); - - return /^\*\s*$/u.test(lines[0]) && - lines.slice(1, -1).every(line => /^\s* /u.test(line)) && - /^\s*$/u.test(lines.at(-1)); - } - - /** - * Processes a comment group that is currently in separate-line form, calculating the offset for each line. - * @param {Token[]} commentGroup A group of comments containing multiple line comments. - * @returns {string[]} An array of the processed lines. - */ - function processSeparateLineComments(commentGroup) { - const allLinesHaveLeadingSpace = commentGroup - .map(({ value }) => value) - .filter(line => line.trim().length) - .every(line => line.startsWith(" ")); - - return commentGroup.map(({ value }) => (allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value)); - } - - /** - * Processes a comment group that is currently in starred-block form, calculating the offset for each line. - * @param {Token} comment A single block comment token in starred-block form. - * @returns {string[]} An array of the processed lines. - */ - function processStarredBlockComment(comment) { - const lines = comment.value.split(astUtils.LINEBREAK_MATCHER) - .filter((line, i, linesArr) => !(i === 0 || i === linesArr.length - 1)) - .map(line => line.replace(/^\s*$/u, "")); - const allLinesHaveLeadingSpace = lines - .map(line => line.replace(/\s*\*/u, "")) - .filter(line => line.trim().length) - .every(line => line.startsWith(" ")); - - return lines.map(line => line.replace(allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, "")); - } - - /** - * Processes a comment group that is currently in bare-block form, calculating the offset for each line. - * @param {Token} comment A single block comment token in bare-block form. - * @returns {string[]} An array of the processed lines. - */ - function processBareBlockComment(comment) { - const lines = comment.value.split(astUtils.LINEBREAK_MATCHER).map(line => line.replace(/^\s*$/u, "")); - const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])} `; - let offset = ""; - - /* - * Calculate the offset of the least indented line and use that as the basis for offsetting all the lines. - * The first line should not be checked because it is inline with the opening block comment delimiter. - */ - for (const [i, line] of lines.entries()) { - if (!line.trim().length || i === 0) { - continue; - } - - const [, lineOffset] = line.match(/^(\s*\*?\s*)/u); - - if (lineOffset.length < leadingWhitespace.length) { - const newOffset = leadingWhitespace.slice(lineOffset.length - leadingWhitespace.length); - - if (newOffset.length > offset.length) { - offset = newOffset; - } - } - } - - return lines.map(line => { - const match = line.match(/^(\s*\*?\s*)(.*)/u); - const [, lineOffset, lineContents] = match; - - if (lineOffset.length > leadingWhitespace.length) { - return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`; - } - - if (lineOffset.length < leadingWhitespace.length) { - return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`; - } - - return lineContents; - }); - } - - /** - * Gets a list of comment lines in a group, formatting leading whitespace as necessary. - * @param {Token[]} commentGroup A group of comments containing either multiple line comments or a single block comment. - * @returns {string[]} A list of comment lines. - */ - function getCommentLines(commentGroup) { - const [firstComment] = commentGroup; - - if (firstComment.type === "Line") { - return processSeparateLineComments(commentGroup); - } - - if (isStarredBlockComment(commentGroup)) { - return processStarredBlockComment(firstComment); - } - - return processBareBlockComment(firstComment); - } - - /** - * Gets the initial offset (whitespace) from the beginning of a line to a given comment token. - * @param {Token} comment The token to check. - * @returns {string} The offset from the beginning of a line to the token. - */ - function getInitialOffset(comment) { - return sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0]); - } - - /** - * Converts a comment into starred-block form - * @param {Token} firstComment The first comment of the group being converted - * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment - * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers - */ - function convertToStarredBlock(firstComment, commentLinesList) { - const initialOffset = getInitialOffset(firstComment); - - return `/*\n${commentLinesList.map(line => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`; - } - - /** - * Converts a comment into separate-line form - * @param {Token} firstComment The first comment of the group being converted - * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment - * @returns {string} A representation of the comment value in separate-line form - */ - function convertToSeparateLines(firstComment, commentLinesList) { - return commentLinesList.map(line => `// ${line}`).join(`\n${getInitialOffset(firstComment)}`); - } - - /** - * Converts a comment into bare-block form - * @param {Token} firstComment The first comment of the group being converted - * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment - * @returns {string} A representation of the comment value in bare-block form - */ - function convertToBlock(firstComment, commentLinesList) { - return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)} `)} */`; - } - - /** - * Each method checks a group of comments to see if it's valid according to the given option. - * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single - * block comment or multiple line comments. - * @returns {void} - */ - const commentGroupCheckers = { - "starred-block"(commentGroup) { - const [firstComment] = commentGroup; - const commentLines = getCommentLines(commentGroup); - - if (commentLines.some(value => value.includes("*/"))) { - return; - } - - if (commentGroup.length > 1) { - context.report({ - loc: { - start: firstComment.loc.start, - end: commentGroup.at(-1).loc.end - }, - messageId: "expectedBlock", - fix(fixer) { - const range = [firstComment.range[0], commentGroup.at(-1).range[1]]; - - return commentLines.some(value => value.startsWith("/")) - ? null - : fixer.replaceTextRange(range, convertToStarredBlock(firstComment, commentLines)); - } - }); - } else { - const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); - const expectedLeadingWhitespace = getInitialOffset(firstComment); - const expectedLinePrefix = `${expectedLeadingWhitespace} *`; - - if (!/^\*?\s*$/u.test(lines[0])) { - const start = firstComment.value.startsWith("*") ? firstComment.range[0] + 1 : firstComment.range[0]; - - context.report({ - loc: { - start: firstComment.loc.start, - end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } - }, - messageId: "startNewline", - fix: fixer => fixer.insertTextAfterRange([start, start + 2], `\n${expectedLinePrefix}`) - }); - } - - if (!/^\s*$/u.test(lines.at(-1))) { - context.report({ - loc: { - start: { line: firstComment.loc.end.line, column: firstComment.loc.end.column - 2 }, - end: firstComment.loc.end - }, - messageId: "endNewline", - fix: fixer => fixer.replaceTextRange([firstComment.range[1] - 2, firstComment.range[1]], `\n${expectedLinePrefix}/`) - }); - } - - for (let lineNumber = firstComment.loc.start.line + 1; lineNumber <= firstComment.loc.end.line; lineNumber++) { - const lineText = sourceCode.lines[lineNumber - 1]; - const errorType = isStarredCommentLine(lineText) - ? "alignment" - : "missingStar"; - - if (!lineText.startsWith(expectedLinePrefix)) { - context.report({ - loc: { - start: { line: lineNumber, column: 0 }, - end: { line: lineNumber, column: lineText.length } - }, - messageId: errorType, - fix(fixer) { - const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 }); - - if (errorType === "alignment") { - const [, commentTextPrefix = ""] = lineText.match(/^(\s*\*)/u) || []; - const commentTextStartIndex = lineStartIndex + commentTextPrefix.length; - - return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], expectedLinePrefix); - } - - const [, commentTextPrefix = ""] = lineText.match(/^(\s*)/u) || []; - const commentTextStartIndex = lineStartIndex + commentTextPrefix.length; - let offset; - - for (const [idx, line] of lines.entries()) { - if (!/\S+/u.test(line)) { - continue; - } - - const lineTextToAlignWith = sourceCode.lines[firstComment.loc.start.line - 1 + idx]; - const [, prefix = "", initialOffset = ""] = lineTextToAlignWith.match(/^(\s*(?:\/?\*)?(\s*))/u) || []; - - offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`; - - if (/^\s*\//u.test(lineText) && offset.length === 0) { - offset += " "; - } - break; - } - - return fixer.replaceTextRange([lineStartIndex, commentTextStartIndex], `${expectedLinePrefix}${offset}`); - } - }); - } - } - } - }, - "separate-lines"(commentGroup) { - const [firstComment] = commentGroup; - - const isJSDoc = isJSDocComment(commentGroup); - - if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { - return; - } - - let commentLines = getCommentLines(commentGroup); - - if (isJSDoc) { - commentLines = commentLines.slice(1, commentLines.length - 1); - } - - const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true }); - - if (tokenAfter && firstComment.loc.end.line === tokenAfter.loc.start.line) { - return; - } - - context.report({ - loc: { - start: firstComment.loc.start, - end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } - }, - messageId: "expectedLines", - fix(fixer) { - return fixer.replaceText(firstComment, convertToSeparateLines(firstComment, commentLines)); - } - }); - }, - "bare-block"(commentGroup) { - if (isJSDocComment(commentGroup)) { - return; - } - - const [firstComment] = commentGroup; - const commentLines = getCommentLines(commentGroup); - - // Disallows consecutive line comments in favor of using a block comment. - if (firstComment.type === "Line" && commentLines.length > 1 && - !commentLines.some(value => value.includes("*/"))) { - context.report({ - loc: { - start: firstComment.loc.start, - end: commentGroup.at(-1).loc.end - }, - messageId: "expectedBlock", - fix(fixer) { - return fixer.replaceTextRange( - [firstComment.range[0], commentGroup.at(-1).range[1]], - convertToBlock(firstComment, commentLines) - ); - } - }); - } - - // Prohibits block comments from having a * at the beginning of each line. - if (isStarredBlockComment(commentGroup)) { - context.report({ - loc: { - start: firstComment.loc.start, - end: { line: firstComment.loc.start.line, column: firstComment.loc.start.column + 2 } - }, - messageId: "expectedBareBlock", - fix(fixer) { - return fixer.replaceText(firstComment, convertToBlock(firstComment, commentLines)); - } - }); - } - } - }; - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - Program() { - return sourceCode.getAllComments() - .filter(comment => comment.type !== "Shebang") - .filter(comment => !astUtils.COMMENTS_IGNORE_PATTERN.test(comment.value)) - .filter(comment => { - const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); - - return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line; - }) - .reduce((commentGroups, comment, index, commentList) => { - const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true }); - - if ( - comment.type === "Line" && - index && commentList[index - 1].type === "Line" && - tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 && - tokenBefore === commentList[index - 1] - ) { - commentGroups.at(-1).push(comment); - } else { - commentGroups.push([comment]); - } - - return commentGroups; - }, []) - .filter(commentGroup => !(commentGroup.length === 1 && commentGroup[0].loc.start.line === commentGroup[0].loc.end.line)) - .forEach(commentGroupCheckers[option]); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "multiline-comment-style", + url: "https://eslint.style/rules/js/multiline-comment-style", + }, + }, + ], + }, + type: "suggestion", + docs: { + description: "Enforce a particular style for multiline comments", + recommended: false, + url: "https://eslint.org/docs/latest/rules/multiline-comment-style", + }, + + fixable: "whitespace", + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["starred-block", "bare-block"], + }, + ], + additionalItems: false, + }, + { + type: "array", + items: [ + { + enum: ["separate-lines"], + }, + { + type: "object", + properties: { + checkJSDoc: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + additionalItems: false, + }, + ], + }, + messages: { + expectedBlock: + "Expected a block comment instead of consecutive line comments.", + expectedBareBlock: + "Expected a block comment without padding stars.", + startNewline: "Expected a linebreak after '/*'.", + endNewline: "Expected a linebreak before '*/'.", + missingStar: "Expected a '*' at the start of this line.", + alignment: + "Expected this line to be aligned with the start of the comment.", + expectedLines: + "Expected multiple line comments instead of a block comment.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const option = context.options[0] || "starred-block"; + const params = context.options[1] || {}; + const checkJSDoc = !!params.checkJSDoc; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks if a comment line is starred. + * @param {string} line A string representing a comment line. + * @returns {boolean} Whether or not the comment line is starred. + */ + function isStarredCommentLine(line) { + return /^\s*\*/u.test(line); + } + + /** + * Checks if a comment group is in starred-block form. + * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. + * @returns {boolean} Whether or not the comment group is in starred block form. + */ + function isStarredBlockComment([firstComment]) { + if (firstComment.type !== "Block") { + return false; + } + + const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); + + // The first and last lines can only contain whitespace. + return ( + lines.length > 0 && + lines.every((line, i) => + (i === 0 || i === lines.length - 1 + ? /^\s*$/u + : /^\s*\*/u + ).test(line), + ) + ); + } + + /** + * Checks if a comment group is in JSDoc form. + * @param {Token[]} commentGroup A group of comments, containing either multiple line comments or a single block comment. + * @returns {boolean} Whether or not the comment group is in JSDoc form. + */ + function isJSDocComment([firstComment]) { + if (firstComment.type !== "Block") { + return false; + } + + const lines = firstComment.value.split(astUtils.LINEBREAK_MATCHER); + + return ( + /^\*\s*$/u.test(lines[0]) && + lines.slice(1, -1).every(line => /^\s* /u.test(line)) && + /^\s*$/u.test(lines.at(-1)) + ); + } + + /** + * Processes a comment group that is currently in separate-line form, calculating the offset for each line. + * @param {Token[]} commentGroup A group of comments containing multiple line comments. + * @returns {string[]} An array of the processed lines. + */ + function processSeparateLineComments(commentGroup) { + const allLinesHaveLeadingSpace = commentGroup + .map(({ value }) => value) + .filter(line => line.trim().length) + .every(line => line.startsWith(" ")); + + return commentGroup.map(({ value }) => + allLinesHaveLeadingSpace ? value.replace(/^ /u, "") : value, + ); + } + + /** + * Processes a comment group that is currently in starred-block form, calculating the offset for each line. + * @param {Token} comment A single block comment token in starred-block form. + * @returns {string[]} An array of the processed lines. + */ + function processStarredBlockComment(comment) { + const lines = comment.value + .split(astUtils.LINEBREAK_MATCHER) + .filter( + (line, i, linesArr) => + !(i === 0 || i === linesArr.length - 1), + ) + .map(line => line.replace(/^\s*$/u, "")); + const allLinesHaveLeadingSpace = lines + .map(line => line.replace(/\s*\*/u, "")) + .filter(line => line.trim().length) + .every(line => line.startsWith(" ")); + + return lines.map(line => + line.replace( + allLinesHaveLeadingSpace ? /\s*\* ?/u : /\s*\*/u, + "", + ), + ); + } + + /** + * Processes a comment group that is currently in bare-block form, calculating the offset for each line. + * @param {Token} comment A single block comment token in bare-block form. + * @returns {string[]} An array of the processed lines. + */ + function processBareBlockComment(comment) { + const lines = comment.value + .split(astUtils.LINEBREAK_MATCHER) + .map(line => line.replace(/^\s*$/u, "")); + const leadingWhitespace = `${sourceCode.text.slice(comment.range[0] - comment.loc.start.column, comment.range[0])} `; + let offset = ""; + + /* + * Calculate the offset of the least indented line and use that as the basis for offsetting all the lines. + * The first line should not be checked because it is inline with the opening block comment delimiter. + */ + for (const [i, line] of lines.entries()) { + if (!line.trim().length || i === 0) { + continue; + } + + const [, lineOffset] = line.match(/^(\s*\*?\s*)/u); + + if (lineOffset.length < leadingWhitespace.length) { + const newOffset = leadingWhitespace.slice( + lineOffset.length - leadingWhitespace.length, + ); + + if (newOffset.length > offset.length) { + offset = newOffset; + } + } + } + + return lines.map(line => { + const match = line.match(/^(\s*\*?\s*)(.*)/u); + const [, lineOffset, lineContents] = match; + + if (lineOffset.length > leadingWhitespace.length) { + return `${lineOffset.slice(leadingWhitespace.length - (offset.length + lineOffset.length))}${lineContents}`; + } + + if (lineOffset.length < leadingWhitespace.length) { + return `${lineOffset.slice(leadingWhitespace.length)}${lineContents}`; + } + + return lineContents; + }); + } + + /** + * Gets a list of comment lines in a group, formatting leading whitespace as necessary. + * @param {Token[]} commentGroup A group of comments containing either multiple line comments or a single block comment. + * @returns {string[]} A list of comment lines. + */ + function getCommentLines(commentGroup) { + const [firstComment] = commentGroup; + + if (firstComment.type === "Line") { + return processSeparateLineComments(commentGroup); + } + + if (isStarredBlockComment(commentGroup)) { + return processStarredBlockComment(firstComment); + } + + return processBareBlockComment(firstComment); + } + + /** + * Gets the initial offset (whitespace) from the beginning of a line to a given comment token. + * @param {Token} comment The token to check. + * @returns {string} The offset from the beginning of a line to the token. + */ + function getInitialOffset(comment) { + return sourceCode.text.slice( + comment.range[0] - comment.loc.start.column, + comment.range[0], + ); + } + + /** + * Converts a comment into starred-block form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in starred-block form, excluding start and end markers + */ + function convertToStarredBlock(firstComment, commentLinesList) { + const initialOffset = getInitialOffset(firstComment); + + return `/*\n${commentLinesList.map(line => `${initialOffset} * ${line}`).join("\n")}\n${initialOffset} */`; + } + + /** + * Converts a comment into separate-line form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in separate-line form + */ + function convertToSeparateLines(firstComment, commentLinesList) { + return commentLinesList + .map(line => `// ${line}`) + .join(`\n${getInitialOffset(firstComment)}`); + } + + /** + * Converts a comment into bare-block form + * @param {Token} firstComment The first comment of the group being converted + * @param {string[]} commentLinesList A list of lines to appear in the new starred-block comment + * @returns {string} A representation of the comment value in bare-block form + */ + function convertToBlock(firstComment, commentLinesList) { + return `/* ${commentLinesList.join(`\n${getInitialOffset(firstComment)} `)} */`; + } + + /** + * Each method checks a group of comments to see if it's valid according to the given option. + * @param {Token[]} commentGroup A list of comments that appear together. This will either contain a single + * block comment or multiple line comments. + * @returns {void} + */ + const commentGroupCheckers = { + "starred-block"(commentGroup) { + const [firstComment] = commentGroup; + const commentLines = getCommentLines(commentGroup); + + if (commentLines.some(value => value.includes("*/"))) { + return; + } + + if (commentGroup.length > 1) { + context.report({ + loc: { + start: firstComment.loc.start, + end: commentGroup.at(-1).loc.end, + }, + messageId: "expectedBlock", + fix(fixer) { + const range = [ + firstComment.range[0], + commentGroup.at(-1).range[1], + ]; + + return commentLines.some(value => + value.startsWith("/"), + ) + ? null + : fixer.replaceTextRange( + range, + convertToStarredBlock( + firstComment, + commentLines, + ), + ); + }, + }); + } else { + const lines = firstComment.value.split( + astUtils.LINEBREAK_MATCHER, + ); + const expectedLeadingWhitespace = + getInitialOffset(firstComment); + const expectedLinePrefix = `${expectedLeadingWhitespace} *`; + + if (!/^\*?\s*$/u.test(lines[0])) { + const start = firstComment.value.startsWith("*") + ? firstComment.range[0] + 1 + : firstComment.range[0]; + + context.report({ + loc: { + start: firstComment.loc.start, + end: { + line: firstComment.loc.start.line, + column: firstComment.loc.start.column + 2, + }, + }, + messageId: "startNewline", + fix: fixer => + fixer.insertTextAfterRange( + [start, start + 2], + `\n${expectedLinePrefix}`, + ), + }); + } + + if (!/^\s*$/u.test(lines.at(-1))) { + context.report({ + loc: { + start: { + line: firstComment.loc.end.line, + column: firstComment.loc.end.column - 2, + }, + end: firstComment.loc.end, + }, + messageId: "endNewline", + fix: fixer => + fixer.replaceTextRange( + [ + firstComment.range[1] - 2, + firstComment.range[1], + ], + `\n${expectedLinePrefix}/`, + ), + }); + } + + for ( + let lineNumber = firstComment.loc.start.line + 1; + lineNumber <= firstComment.loc.end.line; + lineNumber++ + ) { + const lineText = sourceCode.lines[lineNumber - 1]; + const errorType = isStarredCommentLine(lineText) + ? "alignment" + : "missingStar"; + + if (!lineText.startsWith(expectedLinePrefix)) { + context.report({ + loc: { + start: { line: lineNumber, column: 0 }, + end: { + line: lineNumber, + column: lineText.length, + }, + }, + messageId: errorType, + fix(fixer) { + const lineStartIndex = + sourceCode.getIndexFromLoc({ + line: lineNumber, + column: 0, + }); + + if (errorType === "alignment") { + const [, commentTextPrefix = ""] = + lineText.match(/^(\s*\*)/u) || []; + const commentTextStartIndex = + lineStartIndex + + commentTextPrefix.length; + + return fixer.replaceTextRange( + [ + lineStartIndex, + commentTextStartIndex, + ], + expectedLinePrefix, + ); + } + + const [, commentTextPrefix = ""] = + lineText.match(/^(\s*)/u) || []; + const commentTextStartIndex = + lineStartIndex + + commentTextPrefix.length; + let offset; + + for (const [idx, line] of lines.entries()) { + if (!/\S+/u.test(line)) { + continue; + } + + const lineTextToAlignWith = + sourceCode.lines[ + firstComment.loc.start.line - + 1 + + idx + ]; + const [ + , + prefix = "", + initialOffset = "", + ] = + lineTextToAlignWith.match( + /^(\s*(?:\/?\*)?(\s*))/u, + ) || []; + + offset = `${commentTextPrefix.slice(prefix.length)}${initialOffset}`; + + if ( + /^\s*\//u.test(lineText) && + offset.length === 0 + ) { + offset += " "; + } + break; + } + + return fixer.replaceTextRange( + [lineStartIndex, commentTextStartIndex], + `${expectedLinePrefix}${offset}`, + ); + }, + }); + } + } + } + }, + "separate-lines"(commentGroup) { + const [firstComment] = commentGroup; + + const isJSDoc = isJSDocComment(commentGroup); + + if (firstComment.type !== "Block" || (!checkJSDoc && isJSDoc)) { + return; + } + + let commentLines = getCommentLines(commentGroup); + + if (isJSDoc) { + commentLines = commentLines.slice( + 1, + commentLines.length - 1, + ); + } + + const tokenAfter = sourceCode.getTokenAfter(firstComment, { + includeComments: true, + }); + + if ( + tokenAfter && + firstComment.loc.end.line === tokenAfter.loc.start.line + ) { + return; + } + + context.report({ + loc: { + start: firstComment.loc.start, + end: { + line: firstComment.loc.start.line, + column: firstComment.loc.start.column + 2, + }, + }, + messageId: "expectedLines", + fix(fixer) { + return fixer.replaceText( + firstComment, + convertToSeparateLines(firstComment, commentLines), + ); + }, + }); + }, + "bare-block"(commentGroup) { + if (isJSDocComment(commentGroup)) { + return; + } + + const [firstComment] = commentGroup; + const commentLines = getCommentLines(commentGroup); + + // Disallows consecutive line comments in favor of using a block comment. + if ( + firstComment.type === "Line" && + commentLines.length > 1 && + !commentLines.some(value => value.includes("*/")) + ) { + context.report({ + loc: { + start: firstComment.loc.start, + end: commentGroup.at(-1).loc.end, + }, + messageId: "expectedBlock", + fix(fixer) { + return fixer.replaceTextRange( + [ + firstComment.range[0], + commentGroup.at(-1).range[1], + ], + convertToBlock(firstComment, commentLines), + ); + }, + }); + } + + // Prohibits block comments from having a * at the beginning of each line. + if (isStarredBlockComment(commentGroup)) { + context.report({ + loc: { + start: firstComment.loc.start, + end: { + line: firstComment.loc.start.line, + column: firstComment.loc.start.column + 2, + }, + }, + messageId: "expectedBareBlock", + fix(fixer) { + return fixer.replaceText( + firstComment, + convertToBlock(firstComment, commentLines), + ); + }, + }); + } + }, + }; + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + Program() { + return sourceCode + .getAllComments() + .filter(comment => comment.type !== "Shebang") + .filter( + comment => + !astUtils.COMMENTS_IGNORE_PATTERN.test( + comment.value, + ), + ) + .filter(comment => { + const tokenBefore = sourceCode.getTokenBefore(comment, { + includeComments: true, + }); + + return ( + !tokenBefore || + tokenBefore.loc.end.line < comment.loc.start.line + ); + }) + .reduce((commentGroups, comment, index, commentList) => { + const tokenBefore = sourceCode.getTokenBefore(comment, { + includeComments: true, + }); + + if ( + comment.type === "Line" && + index && + commentList[index - 1].type === "Line" && + tokenBefore && + tokenBefore.loc.end.line === + comment.loc.start.line - 1 && + tokenBefore === commentList[index - 1] + ) { + commentGroups.at(-1).push(comment); + } else { + commentGroups.push([comment]); + } + + return commentGroups; + }, []) + .filter( + commentGroup => + !( + commentGroup.length === 1 && + commentGroup[0].loc.start.line === + commentGroup[0].loc.end.line + ), + ) + .forEach(commentGroupCheckers[option]); + }, + }; + }, }; diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js index 828b395f4295..405a6f91053e 100644 --- a/lib/rules/multiline-ternary.js +++ b/lib/rules/multiline-ternary.js @@ -14,179 +14,244 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "multiline-ternary", - url: "https://eslint.style/rules/js/multiline-ternary" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce newlines between operands of ternary expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/multiline-ternary" - }, - - schema: [ - { - enum: ["always", "always-multiline", "never"] - } - ], - - messages: { - expectedTestCons: "Expected newline between test and consequent of ternary expression.", - expectedConsAlt: "Expected newline between consequent and alternate of ternary expression.", - unexpectedTestCons: "Unexpected newline between test and consequent of ternary expression.", - unexpectedConsAlt: "Unexpected newline between consequent and alternate of ternary expression." - }, - - fixable: "whitespace" - }, - - create(context) { - const sourceCode = context.sourceCode; - const option = context.options[0]; - const multiline = option !== "never"; - const allowSingleLine = option === "always-multiline"; - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - ConditionalExpression(node) { - const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken); - const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken); - - const firstTokenOfTest = sourceCode.getFirstToken(node); - const lastTokenOfTest = sourceCode.getTokenBefore(questionToken); - const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken); - const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken); - const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken); - - const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent); - const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate); - - const hasComments = !!sourceCode.getCommentsInside(node).length; - - if (!multiline) { - if (!areTestAndConsequentOnSameLine) { - context.report({ - node: node.test, - loc: { - start: firstTokenOfTest.loc.start, - end: lastTokenOfTest.loc.end - }, - messageId: "unexpectedTestCons", - fix(fixer) { - if (hasComments) { - return null; - } - const fixers = []; - const areTestAndQuestionOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, questionToken); - const areQuestionAndConsOnSameLine = astUtils.isTokenOnSameLine(questionToken, firstTokenOfConsequent); - - if (!areTestAndQuestionOnSameLine) { - fixers.push(fixer.removeRange([lastTokenOfTest.range[1], questionToken.range[0]])); - } - if (!areQuestionAndConsOnSameLine) { - fixers.push(fixer.removeRange([questionToken.range[1], firstTokenOfConsequent.range[0]])); - } - - return fixers; - } - }); - } - - if (!areConsequentAndAlternateOnSameLine) { - context.report({ - node: node.consequent, - loc: { - start: firstTokenOfConsequent.loc.start, - end: lastTokenOfConsequent.loc.end - }, - messageId: "unexpectedConsAlt", - fix(fixer) { - if (hasComments) { - return null; - } - const fixers = []; - const areConsAndColonOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, colonToken); - const areColonAndAltOnSameLine = astUtils.isTokenOnSameLine(colonToken, firstTokenOfAlternate); - - if (!areConsAndColonOnSameLine) { - fixers.push(fixer.removeRange([lastTokenOfConsequent.range[1], colonToken.range[0]])); - } - if (!areColonAndAltOnSameLine) { - fixers.push(fixer.removeRange([colonToken.range[1], firstTokenOfAlternate.range[0]])); - } - - return fixers; - } - }); - } - } else { - if (allowSingleLine && node.loc.start.line === node.loc.end.line) { - return; - } - - if (areTestAndConsequentOnSameLine) { - context.report({ - node: node.test, - loc: { - start: firstTokenOfTest.loc.start, - end: lastTokenOfTest.loc.end - }, - messageId: "expectedTestCons", - fix: fixer => (hasComments ? null : ( - fixer.replaceTextRange( - [ - lastTokenOfTest.range[1], - questionToken.range[0] - ], - "\n" - ) - )) - }); - } - - if (areConsequentAndAlternateOnSameLine) { - context.report({ - node: node.consequent, - loc: { - start: firstTokenOfConsequent.loc.start, - end: lastTokenOfConsequent.loc.end - }, - messageId: "expectedConsAlt", - fix: (fixer => (hasComments ? null : ( - fixer.replaceTextRange( - [ - lastTokenOfConsequent.range[1], - colonToken.range[0] - ], - "\n" - ) - ))) - }); - } - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "multiline-ternary", + url: "https://eslint.style/rules/js/multiline-ternary", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce newlines between operands of ternary expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/multiline-ternary", + }, + + schema: [ + { + enum: ["always", "always-multiline", "never"], + }, + ], + + messages: { + expectedTestCons: + "Expected newline between test and consequent of ternary expression.", + expectedConsAlt: + "Expected newline between consequent and alternate of ternary expression.", + unexpectedTestCons: + "Unexpected newline between test and consequent of ternary expression.", + unexpectedConsAlt: + "Unexpected newline between consequent and alternate of ternary expression.", + }, + + fixable: "whitespace", + }, + + create(context) { + const sourceCode = context.sourceCode; + const option = context.options[0]; + const multiline = option !== "never"; + const allowSingleLine = option === "always-multiline"; + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ConditionalExpression(node) { + const questionToken = sourceCode.getTokenAfter( + node.test, + astUtils.isNotClosingParenToken, + ); + const colonToken = sourceCode.getTokenAfter( + node.consequent, + astUtils.isNotClosingParenToken, + ); + + const firstTokenOfTest = sourceCode.getFirstToken(node); + const lastTokenOfTest = + sourceCode.getTokenBefore(questionToken); + const firstTokenOfConsequent = + sourceCode.getTokenAfter(questionToken); + const lastTokenOfConsequent = + sourceCode.getTokenBefore(colonToken); + const firstTokenOfAlternate = + sourceCode.getTokenAfter(colonToken); + + const areTestAndConsequentOnSameLine = + astUtils.isTokenOnSameLine( + lastTokenOfTest, + firstTokenOfConsequent, + ); + const areConsequentAndAlternateOnSameLine = + astUtils.isTokenOnSameLine( + lastTokenOfConsequent, + firstTokenOfAlternate, + ); + + const hasComments = !!sourceCode.getCommentsInside(node).length; + + if (!multiline) { + if (!areTestAndConsequentOnSameLine) { + context.report({ + node: node.test, + loc: { + start: firstTokenOfTest.loc.start, + end: lastTokenOfTest.loc.end, + }, + messageId: "unexpectedTestCons", + fix(fixer) { + if (hasComments) { + return null; + } + const fixers = []; + const areTestAndQuestionOnSameLine = + astUtils.isTokenOnSameLine( + lastTokenOfTest, + questionToken, + ); + const areQuestionAndConsOnSameLine = + astUtils.isTokenOnSameLine( + questionToken, + firstTokenOfConsequent, + ); + + if (!areTestAndQuestionOnSameLine) { + fixers.push( + fixer.removeRange([ + lastTokenOfTest.range[1], + questionToken.range[0], + ]), + ); + } + if (!areQuestionAndConsOnSameLine) { + fixers.push( + fixer.removeRange([ + questionToken.range[1], + firstTokenOfConsequent.range[0], + ]), + ); + } + + return fixers; + }, + }); + } + + if (!areConsequentAndAlternateOnSameLine) { + context.report({ + node: node.consequent, + loc: { + start: firstTokenOfConsequent.loc.start, + end: lastTokenOfConsequent.loc.end, + }, + messageId: "unexpectedConsAlt", + fix(fixer) { + if (hasComments) { + return null; + } + const fixers = []; + const areConsAndColonOnSameLine = + astUtils.isTokenOnSameLine( + lastTokenOfConsequent, + colonToken, + ); + const areColonAndAltOnSameLine = + astUtils.isTokenOnSameLine( + colonToken, + firstTokenOfAlternate, + ); + + if (!areConsAndColonOnSameLine) { + fixers.push( + fixer.removeRange([ + lastTokenOfConsequent.range[1], + colonToken.range[0], + ]), + ); + } + if (!areColonAndAltOnSameLine) { + fixers.push( + fixer.removeRange([ + colonToken.range[1], + firstTokenOfAlternate.range[0], + ]), + ); + } + + return fixers; + }, + }); + } + } else { + if ( + allowSingleLine && + node.loc.start.line === node.loc.end.line + ) { + return; + } + + if (areTestAndConsequentOnSameLine) { + context.report({ + node: node.test, + loc: { + start: firstTokenOfTest.loc.start, + end: lastTokenOfTest.loc.end, + }, + messageId: "expectedTestCons", + fix: fixer => + hasComments + ? null + : fixer.replaceTextRange( + [ + lastTokenOfTest.range[1], + questionToken.range[0], + ], + "\n", + ), + }); + } + + if (areConsequentAndAlternateOnSameLine) { + context.report({ + node: node.consequent, + loc: { + start: firstTokenOfConsequent.loc.start, + end: lastTokenOfConsequent.loc.end, + }, + messageId: "expectedConsAlt", + fix: fixer => + hasComments + ? null + : fixer.replaceTextRange( + [ + lastTokenOfConsequent.range[1], + colonToken.range[0], + ], + "\n", + ), + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js index c57997a593ec..cc0b5403d5ba 100644 --- a/lib/rules/new-cap.js +++ b/lib/rules/new-cap.js @@ -16,17 +16,17 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const CAPS_ALLOWED = [ - "Array", - "Boolean", - "Date", - "Error", - "Function", - "Number", - "Object", - "RegExp", - "String", - "Symbol", - "BigInt" + "Array", + "Boolean", + "Date", + "Error", + "Function", + "Number", + "Object", + "RegExp", + "String", + "Symbol", + "BigInt", ]; /** @@ -36,8 +36,8 @@ const CAPS_ALLOWED = [ * @returns {Object} Returns the updated Object for further reduction. */ function invert(map, key) { - map[key] = true; - return map; + map[key] = true; + return map; } /** @@ -46,9 +46,11 @@ function invert(map, key) { * @returns {Object} Object with cap is new exceptions. */ function calculateCapIsNewExceptions(config) { - const capIsNewExceptions = Array.from(new Set([...config.capIsNewExceptions, ...CAPS_ALLOWED])); + const capIsNewExceptions = Array.from( + new Set([...config.capIsNewExceptions, ...CAPS_ALLOWED]), + ); - return capIsNewExceptions.reduce(invert, {}); + return capIsNewExceptions.reduce(invert, {}); } //------------------------------------------------------------------------------ @@ -57,201 +59,219 @@ function calculateCapIsNewExceptions(config) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require constructor names to begin with a capital letter", - recommended: false, - url: "https://eslint.org/docs/latest/rules/new-cap" - }, - - schema: [ - { - type: "object", - properties: { - newIsCap: { - type: "boolean" - }, - capIsNew: { - type: "boolean" - }, - newIsCapExceptions: { - type: "array", - items: { - type: "string" - } - }, - newIsCapExceptionPattern: { - type: "string" - }, - capIsNewExceptions: { - type: "array", - items: { - type: "string" - } - }, - capIsNewExceptionPattern: { - type: "string" - }, - properties: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - defaultOptions: [{ - capIsNew: true, - capIsNewExceptions: CAPS_ALLOWED, - newIsCap: true, - newIsCapExceptions: [], - properties: true - }], - - messages: { - upper: "A function with a name starting with an uppercase letter should only be used as a constructor.", - lower: "A constructor name should not start with a lowercase letter." - } - }, - - create(context) { - const [config] = context.options; - const skipProperties = !config.properties; - - const newIsCapExceptions = config.newIsCapExceptions.reduce(invert, {}); - const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null; - - const capIsNewExceptions = calculateCapIsNewExceptions(config); - const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null; - - const listeners = {}; - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Get exact callee name from expression - * @param {ASTNode} node CallExpression or NewExpression node - * @returns {string} name - */ - function extractNameFromExpression(node) { - return node.callee.type === "Identifier" - ? node.callee.name - : astUtils.getStaticPropertyName(node.callee) || ""; - } - - /** - * Returns the capitalization state of the string - - * Whether the first character is uppercase, lowercase, or non-alphabetic - * @param {string} str String - * @returns {string} capitalization state: "non-alpha", "lower", or "upper" - */ - function getCap(str) { - const firstChar = str.charAt(0); - - const firstCharLower = firstChar.toLowerCase(); - const firstCharUpper = firstChar.toUpperCase(); - - if (firstCharLower === firstCharUpper) { - - // char has no uppercase variant, so it's non-alphabetic - return "non-alpha"; - } - if (firstChar === firstCharLower) { - return "lower"; - } - return "upper"; - - } - - /** - * Check if capitalization is allowed for a CallExpression - * @param {Object} allowedMap Object mapping calleeName to a Boolean - * @param {ASTNode} node CallExpression node - * @param {string} calleeName Capitalized callee name from a CallExpression - * @param {Object} pattern RegExp object from options pattern - * @returns {boolean} Returns true if the callee may be capitalized - */ - function isCapAllowed(allowedMap, node, calleeName, pattern) { - const sourceText = sourceCode.getText(node.callee); - - if (allowedMap[calleeName] || allowedMap[sourceText]) { - return true; - } - - if (pattern && pattern.test(sourceText)) { - return true; - } - - const callee = astUtils.skipChainExpression(node.callee); - - if (calleeName === "UTC" && callee.type === "MemberExpression") { - - // allow if callee is Date.UTC - return callee.object.type === "Identifier" && - callee.object.name === "Date"; - } - - return skipProperties && callee.type === "MemberExpression"; - } - - /** - * Reports the given messageId for the given node. The location will be the start of the property or the callee. - * @param {ASTNode} node CallExpression or NewExpression node. - * @param {string} messageId The messageId to report. - * @returns {void} - */ - function report(node, messageId) { - let callee = astUtils.skipChainExpression(node.callee); - - if (callee.type === "MemberExpression") { - callee = callee.property; - } - - context.report({ node, loc: callee.loc, messageId }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - if (config.newIsCap) { - listeners.NewExpression = function(node) { - - const constructorName = extractNameFromExpression(node); - - if (constructorName) { - const capitalization = getCap(constructorName); - const isAllowed = capitalization !== "lower" || isCapAllowed(newIsCapExceptions, node, constructorName, newIsCapExceptionPattern); - - if (!isAllowed) { - report(node, "lower"); - } - } - }; - } - - if (config.capIsNew) { - listeners.CallExpression = function(node) { - - const calleeName = extractNameFromExpression(node); - - if (calleeName) { - const capitalization = getCap(calleeName); - const isAllowed = capitalization !== "upper" || isCapAllowed(capIsNewExceptions, node, calleeName, capIsNewExceptionPattern); - - if (!isAllowed) { - report(node, "upper"); - } - } - }; - } - - return listeners; - } + meta: { + type: "suggestion", + + docs: { + description: + "Require constructor names to begin with a capital letter", + recommended: false, + url: "https://eslint.org/docs/latest/rules/new-cap", + }, + + schema: [ + { + type: "object", + properties: { + newIsCap: { + type: "boolean", + }, + capIsNew: { + type: "boolean", + }, + newIsCapExceptions: { + type: "array", + items: { + type: "string", + }, + }, + newIsCapExceptionPattern: { + type: "string", + }, + capIsNewExceptions: { + type: "array", + items: { + type: "string", + }, + }, + capIsNewExceptionPattern: { + type: "string", + }, + properties: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [ + { + capIsNew: true, + capIsNewExceptions: CAPS_ALLOWED, + newIsCap: true, + newIsCapExceptions: [], + properties: true, + }, + ], + + messages: { + upper: "A function with a name starting with an uppercase letter should only be used as a constructor.", + lower: "A constructor name should not start with a lowercase letter.", + }, + }, + + create(context) { + const [config] = context.options; + const skipProperties = !config.properties; + + const newIsCapExceptions = config.newIsCapExceptions.reduce(invert, {}); + const newIsCapExceptionPattern = config.newIsCapExceptionPattern + ? new RegExp(config.newIsCapExceptionPattern, "u") + : null; + + const capIsNewExceptions = calculateCapIsNewExceptions(config); + const capIsNewExceptionPattern = config.capIsNewExceptionPattern + ? new RegExp(config.capIsNewExceptionPattern, "u") + : null; + + const listeners = {}; + + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Get exact callee name from expression + * @param {ASTNode} node CallExpression or NewExpression node + * @returns {string} name + */ + function extractNameFromExpression(node) { + return node.callee.type === "Identifier" + ? node.callee.name + : astUtils.getStaticPropertyName(node.callee) || ""; + } + + /** + * Returns the capitalization state of the string - + * Whether the first character is uppercase, lowercase, or non-alphabetic + * @param {string} str String + * @returns {string} capitalization state: "non-alpha", "lower", or "upper" + */ + function getCap(str) { + const firstChar = str.charAt(0); + + const firstCharLower = firstChar.toLowerCase(); + const firstCharUpper = firstChar.toUpperCase(); + + if (firstCharLower === firstCharUpper) { + // char has no uppercase variant, so it's non-alphabetic + return "non-alpha"; + } + if (firstChar === firstCharLower) { + return "lower"; + } + return "upper"; + } + + /** + * Check if capitalization is allowed for a CallExpression + * @param {Object} allowedMap Object mapping calleeName to a Boolean + * @param {ASTNode} node CallExpression node + * @param {string} calleeName Capitalized callee name from a CallExpression + * @param {Object} pattern RegExp object from options pattern + * @returns {boolean} Returns true if the callee may be capitalized + */ + function isCapAllowed(allowedMap, node, calleeName, pattern) { + const sourceText = sourceCode.getText(node.callee); + + if (allowedMap[calleeName] || allowedMap[sourceText]) { + return true; + } + + if (pattern && pattern.test(sourceText)) { + return true; + } + + const callee = astUtils.skipChainExpression(node.callee); + + if (calleeName === "UTC" && callee.type === "MemberExpression") { + // allow if callee is Date.UTC + return ( + callee.object.type === "Identifier" && + callee.object.name === "Date" + ); + } + + return skipProperties && callee.type === "MemberExpression"; + } + + /** + * Reports the given messageId for the given node. The location will be the start of the property or the callee. + * @param {ASTNode} node CallExpression or NewExpression node. + * @param {string} messageId The messageId to report. + * @returns {void} + */ + function report(node, messageId) { + let callee = astUtils.skipChainExpression(node.callee); + + if (callee.type === "MemberExpression") { + callee = callee.property; + } + + context.report({ node, loc: callee.loc, messageId }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + if (config.newIsCap) { + listeners.NewExpression = function (node) { + const constructorName = extractNameFromExpression(node); + + if (constructorName) { + const capitalization = getCap(constructorName); + const isAllowed = + capitalization !== "lower" || + isCapAllowed( + newIsCapExceptions, + node, + constructorName, + newIsCapExceptionPattern, + ); + + if (!isAllowed) { + report(node, "lower"); + } + } + }; + } + + if (config.capIsNew) { + listeners.CallExpression = function (node) { + const calleeName = extractNameFromExpression(node); + + if (calleeName) { + const capitalization = getCap(calleeName); + const isAllowed = + capitalization !== "upper" || + isCapAllowed( + capIsNewExceptions, + node, + calleeName, + capIsNewExceptionPattern, + ); + + if (!isAllowed) { + report(node, "upper"); + } + } + }; + } + + return listeners; + }, }; diff --git a/lib/rules/new-parens.js b/lib/rules/new-parens.js index 062ecf542e9d..03f5c9cea057 100644 --- a/lib/rules/new-parens.js +++ b/lib/rules/new-parens.js @@ -22,90 +22,99 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "new-parens", - url: "https://eslint.style/rules/js/new-parens" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "new-parens", + url: "https://eslint.style/rules/js/new-parens", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce or disallow parentheses when invoking a constructor with no arguments", - recommended: false, - url: "https://eslint.org/docs/latest/rules/new-parens" - }, + docs: { + description: + "Enforce or disallow parentheses when invoking a constructor with no arguments", + recommended: false, + url: "https://eslint.org/docs/latest/rules/new-parens", + }, - fixable: "code", - schema: [ - { - enum: ["always", "never"] - } - ], - messages: { - missing: "Missing '()' invoking a constructor.", - unnecessary: "Unnecessary '()' invoking a constructor with no arguments." - } - }, + fixable: "code", + schema: [ + { + enum: ["always", "never"], + }, + ], + messages: { + missing: "Missing '()' invoking a constructor.", + unnecessary: + "Unnecessary '()' invoking a constructor with no arguments.", + }, + }, - create(context) { - const options = context.options; - const always = options[0] !== "never"; // Default is always + create(context) { + const options = context.options; + const always = options[0] !== "never"; // Default is always - const sourceCode = context.sourceCode; + const sourceCode = context.sourceCode; - return { - NewExpression(node) { - if (node.arguments.length !== 0) { - return; // if there are arguments, there have to be parens - } + return { + NewExpression(node) { + if (node.arguments.length !== 0) { + return; // if there are arguments, there have to be parens + } - const lastToken = sourceCode.getLastToken(node); - const hasLastParen = lastToken && astUtils.isClosingParenToken(lastToken); + const lastToken = sourceCode.getLastToken(node); + const hasLastParen = + lastToken && astUtils.isClosingParenToken(lastToken); - // `hasParens` is true only if the new expression ends with its own parens, e.g., new new foo() does not end with its own parens - const hasParens = hasLastParen && - astUtils.isOpeningParenToken(sourceCode.getTokenBefore(lastToken)) && - node.callee.range[1] < node.range[1]; + // `hasParens` is true only if the new expression ends with its own parens, e.g., new new foo() does not end with its own parens + const hasParens = + hasLastParen && + astUtils.isOpeningParenToken( + sourceCode.getTokenBefore(lastToken), + ) && + node.callee.range[1] < node.range[1]; - if (always) { - if (!hasParens) { - context.report({ - node, - messageId: "missing", - fix: fixer => fixer.insertTextAfter(node, "()") - }); - } - } else { - if (hasParens) { - context.report({ - node, - messageId: "unnecessary", - fix: fixer => [ - fixer.remove(sourceCode.getTokenBefore(lastToken)), - fixer.remove(lastToken), - fixer.insertTextBefore(node, "("), - fixer.insertTextAfter(node, ")") - ] - }); - } - } - } - }; - } + if (always) { + if (!hasParens) { + context.report({ + node, + messageId: "missing", + fix: fixer => fixer.insertTextAfter(node, "()"), + }); + } + } else { + if (hasParens) { + context.report({ + node, + messageId: "unnecessary", + fix: fixer => [ + fixer.remove( + sourceCode.getTokenBefore(lastToken), + ), + fixer.remove(lastToken), + fixer.insertTextBefore(node, "("), + fixer.insertTextAfter(node, ")"), + ], + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/newline-after-var.js b/lib/rules/newline-after-var.js index 755c5d5ad899..0258d11be9fc 100644 --- a/lib/rules/newline-after-var.js +++ b/lib/rules/newline-after-var.js @@ -18,253 +18,290 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", - - docs: { - description: "Require or disallow an empty line after variable declarations", - recommended: false, - url: "https://eslint.org/docs/latest/rules/newline-after-var" - }, - schema: [ - { - enum: ["never", "always"] - } - ], - fixable: "whitespace", - messages: { - expected: "Expected blank line after variable declarations.", - unexpected: "Unexpected blank line after variable declarations." - }, - - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "padding-line-between-statements", - url: "https://eslint.style/rules/js/padding-line-between-statements" - } - } - ] - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - // Default `mode` to "always". - const mode = context.options[0] === "never" ? "never" : "always"; - - // Cache starting and ending line numbers of comments for faster lookup - const commentEndLine = sourceCode.getAllComments().reduce((result, token) => { - result[token.loc.start.line] = token.loc.end.line; - return result; - }, {}); - - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Gets a token from the given node to compare line to the next statement. - * - * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy. - * - * - The last token is semicolon. - * - The semicolon is on a different line from the previous token of the semicolon. - * - * This behavior would address semicolon-less style code. e.g.: - * - * var foo = 1 - * - * ;(a || b).doSomething() - * @param {ASTNode} node The node to get. - * @returns {Token} The token to compare line to the next statement. - */ - function getLastToken(node) { - const lastToken = sourceCode.getLastToken(node); - - if (lastToken.type === "Punctuator" && lastToken.value === ";") { - const prevToken = sourceCode.getTokenBefore(lastToken); - - if (prevToken.loc.end.line !== lastToken.loc.start.line) { - return prevToken; - } - } - - return lastToken; - } - - /** - * Determine if provided keyword is a variable declaration - * @private - * @param {string} keyword keyword to test - * @returns {boolean} True if `keyword` is a type of var - */ - function isVar(keyword) { - return keyword === "var" || keyword === "let" || keyword === "const"; - } - - /** - * Determine if provided keyword is a variant of for specifiers - * @private - * @param {string} keyword keyword to test - * @returns {boolean} True if `keyword` is a variant of for specifier - */ - function isForTypeSpecifier(keyword) { - return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; - } - - /** - * Determine if provided keyword is an export specifiers - * @private - * @param {string} nodeType nodeType to test - * @returns {boolean} True if `nodeType` is an export specifier - */ - function isExportSpecifier(nodeType) { - return nodeType === "ExportNamedDeclaration" || nodeType === "ExportSpecifier" || - nodeType === "ExportDefaultDeclaration" || nodeType === "ExportAllDeclaration"; - } - - /** - * Determine if provided node is the last of their parent block. - * @private - * @param {ASTNode} node node to test - * @returns {boolean} True if `node` is last of their parent block. - */ - function isLastNode(node) { - const token = sourceCode.getTokenAfter(node); - - return !token || (token.type === "Punctuator" && token.value === "}"); - } - - /** - * Gets the last line of a group of consecutive comments - * @param {number} commentStartLine The starting line of the group - * @returns {number} The number of the last comment line of the group - */ - function getLastCommentLineOfBlock(commentStartLine) { - const currentCommentEnd = commentEndLine[commentStartLine]; - - return commentEndLine[currentCommentEnd + 1] ? getLastCommentLineOfBlock(currentCommentEnd + 1) : currentCommentEnd; - } - - /** - * Determine if a token starts more than one line after a comment ends - * @param {token} token The token being checked - * @param {integer} commentStartLine The line number on which the comment starts - * @returns {boolean} True if `token` does not start immediately after a comment - */ - function hasBlankLineAfterComment(token, commentStartLine) { - return token.loc.start.line > getLastCommentLineOfBlock(commentStartLine) + 1; - } - - /** - * Checks that a blank line exists after a variable declaration when mode is - * set to "always", or checks that there is no blank line when mode is set - * to "never" - * @private - * @param {ASTNode} node `VariableDeclaration` node to test - * @returns {void} - */ - function checkForBlankLine(node) { - - /* - * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will - * sometimes be second-last if there is a semicolon on a different line. - */ - const lastToken = getLastToken(node), - - /* - * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken - * is the last token of the node. - */ - nextToken = lastToken === sourceCode.getLastToken(node) ? sourceCode.getTokenAfter(node) : sourceCode.getLastToken(node), - nextLineNum = lastToken.loc.end.line + 1; - - // Ignore if there is no following statement - if (!nextToken) { - return; - } - - // Ignore if parent of node is a for variant - if (isForTypeSpecifier(node.parent.type)) { - return; - } - - // Ignore if parent of node is an export specifier - if (isExportSpecifier(node.parent.type)) { - return; - } - - /* - * Some coding styles use multiple `var` statements, so do nothing if - * the next token is a `var` statement. - */ - if (nextToken.type === "Keyword" && isVar(nextToken.value)) { - return; - } - - // Ignore if it is last statement in a block - if (isLastNode(node)) { - return; - } - - // Next statement is not a `var`... - const noNextLineToken = nextToken.loc.start.line > nextLineNum; - const hasNextLineComment = (typeof commentEndLine[nextLineNum] !== "undefined"); - - if (mode === "never" && noNextLineToken && !hasNextLineComment) { - context.report({ - node, - messageId: "unexpected", - fix(fixer) { - const linesBetween = sourceCode.getText().slice(lastToken.range[1], nextToken.range[0]).split(astUtils.LINEBREAK_MATCHER); - - return fixer.replaceTextRange([lastToken.range[1], nextToken.range[0]], `${linesBetween.slice(0, -1).join("")}\n${linesBetween.at(-1)}`); - } - }); - } - - // Token on the next line, or comment without blank line - if ( - mode === "always" && ( - !noNextLineToken || - hasNextLineComment && !hasBlankLineAfterComment(nextToken, nextLineNum) - ) - ) { - context.report({ - node, - messageId: "expected", - fix(fixer) { - if ((noNextLineToken ? getLastCommentLineOfBlock(nextLineNum) : lastToken.loc.end.line) === nextToken.loc.start.line) { - return fixer.insertTextBefore(nextToken, "\n\n"); - } - - return fixer.insertTextBeforeRange([nextToken.range[0] - nextToken.loc.start.column, nextToken.range[1]], "\n"); - } - }); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - VariableDeclaration: checkForBlankLine - }; - - } + meta: { + type: "layout", + + docs: { + description: + "Require or disallow an empty line after variable declarations", + recommended: false, + url: "https://eslint.org/docs/latest/rules/newline-after-var", + }, + schema: [ + { + enum: ["never", "always"], + }, + ], + fixable: "whitespace", + messages: { + expected: "Expected blank line after variable declarations.", + unexpected: "Unexpected blank line after variable declarations.", + }, + + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "padding-line-between-statements", + url: "https://eslint.style/rules/js/padding-line-between-statements", + }, + }, + ], + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + // Default `mode` to "always". + const mode = context.options[0] === "never" ? "never" : "always"; + + // Cache starting and ending line numbers of comments for faster lookup + const commentEndLine = sourceCode + .getAllComments() + .reduce((result, token) => { + result[token.loc.start.line] = token.loc.end.line; + return result; + }, {}); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Gets a token from the given node to compare line to the next statement. + * + * In general, the token is the last token of the node. However, the token is the second last token if the following conditions satisfy. + * + * - The last token is semicolon. + * - The semicolon is on a different line from the previous token of the semicolon. + * + * This behavior would address semicolon-less style code. e.g.: + * + * var foo = 1 + * + * ;(a || b).doSomething() + * @param {ASTNode} node The node to get. + * @returns {Token} The token to compare line to the next statement. + */ + function getLastToken(node) { + const lastToken = sourceCode.getLastToken(node); + + if (lastToken.type === "Punctuator" && lastToken.value === ";") { + const prevToken = sourceCode.getTokenBefore(lastToken); + + if (prevToken.loc.end.line !== lastToken.loc.start.line) { + return prevToken; + } + } + + return lastToken; + } + + /** + * Determine if provided keyword is a variable declaration + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a type of var + */ + function isVar(keyword) { + return ( + keyword === "var" || keyword === "let" || keyword === "const" + ); + } + + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return ( + keyword === "ForStatement" || + keyword === "ForInStatement" || + keyword === "ForOfStatement" + ); + } + + /** + * Determine if provided keyword is an export specifiers + * @private + * @param {string} nodeType nodeType to test + * @returns {boolean} True if `nodeType` is an export specifier + */ + function isExportSpecifier(nodeType) { + return ( + nodeType === "ExportNamedDeclaration" || + nodeType === "ExportSpecifier" || + nodeType === "ExportDefaultDeclaration" || + nodeType === "ExportAllDeclaration" + ); + } + + /** + * Determine if provided node is the last of their parent block. + * @private + * @param {ASTNode} node node to test + * @returns {boolean} True if `node` is last of their parent block. + */ + function isLastNode(node) { + const token = sourceCode.getTokenAfter(node); + + return ( + !token || (token.type === "Punctuator" && token.value === "}") + ); + } + + /** + * Gets the last line of a group of consecutive comments + * @param {number} commentStartLine The starting line of the group + * @returns {number} The number of the last comment line of the group + */ + function getLastCommentLineOfBlock(commentStartLine) { + const currentCommentEnd = commentEndLine[commentStartLine]; + + return commentEndLine[currentCommentEnd + 1] + ? getLastCommentLineOfBlock(currentCommentEnd + 1) + : currentCommentEnd; + } + + /** + * Determine if a token starts more than one line after a comment ends + * @param {token} token The token being checked + * @param {integer} commentStartLine The line number on which the comment starts + * @returns {boolean} True if `token` does not start immediately after a comment + */ + function hasBlankLineAfterComment(token, commentStartLine) { + return ( + token.loc.start.line > + getLastCommentLineOfBlock(commentStartLine) + 1 + ); + } + + /** + * Checks that a blank line exists after a variable declaration when mode is + * set to "always", or checks that there is no blank line when mode is set + * to "never" + * @private + * @param {ASTNode} node `VariableDeclaration` node to test + * @returns {void} + */ + function checkForBlankLine(node) { + /* + * lastToken is the last token on the node's line. It will usually also be the last token of the node, but it will + * sometimes be second-last if there is a semicolon on a different line. + */ + const lastToken = getLastToken(node), + /* + * If lastToken is the last token of the node, nextToken should be the token after the node. Otherwise, nextToken + * is the last token of the node. + */ + nextToken = + lastToken === sourceCode.getLastToken(node) + ? sourceCode.getTokenAfter(node) + : sourceCode.getLastToken(node), + nextLineNum = lastToken.loc.end.line + 1; + + // Ignore if there is no following statement + if (!nextToken) { + return; + } + + // Ignore if parent of node is a for variant + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + // Ignore if parent of node is an export specifier + if (isExportSpecifier(node.parent.type)) { + return; + } + + /* + * Some coding styles use multiple `var` statements, so do nothing if + * the next token is a `var` statement. + */ + if (nextToken.type === "Keyword" && isVar(nextToken.value)) { + return; + } + + // Ignore if it is last statement in a block + if (isLastNode(node)) { + return; + } + + // Next statement is not a `var`... + const noNextLineToken = nextToken.loc.start.line > nextLineNum; + const hasNextLineComment = + typeof commentEndLine[nextLineNum] !== "undefined"; + + if (mode === "never" && noNextLineToken && !hasNextLineComment) { + context.report({ + node, + messageId: "unexpected", + fix(fixer) { + const linesBetween = sourceCode + .getText() + .slice(lastToken.range[1], nextToken.range[0]) + .split(astUtils.LINEBREAK_MATCHER); + + return fixer.replaceTextRange( + [lastToken.range[1], nextToken.range[0]], + `${linesBetween.slice(0, -1).join("")}\n${linesBetween.at(-1)}`, + ); + }, + }); + } + + // Token on the next line, or comment without blank line + if ( + mode === "always" && + (!noNextLineToken || + (hasNextLineComment && + !hasBlankLineAfterComment(nextToken, nextLineNum))) + ) { + context.report({ + node, + messageId: "expected", + fix(fixer) { + if ( + (noNextLineToken + ? getLastCommentLineOfBlock(nextLineNum) + : lastToken.loc.end.line) === + nextToken.loc.start.line + ) { + return fixer.insertTextBefore(nextToken, "\n\n"); + } + + return fixer.insertTextBeforeRange( + [ + nextToken.range[0] - nextToken.loc.start.column, + nextToken.range[1], + ], + "\n", + ); + }, + }); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForBlankLine, + }; + }, }; diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js index 6aa74c180ea8..34c5f253e0cb 100644 --- a/lib/rules/newline-before-return.js +++ b/lib/rules/newline-before-return.js @@ -11,225 +11,232 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", - - docs: { - description: "Require an empty line before `return` statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/newline-before-return" - }, - - fixable: "whitespace", - schema: [], - messages: { - expected: "Expected newline before return statement." - }, - - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "padding-line-between-statements", - url: "https://eslint.style/rules/js/padding-line-between-statements" - } - } - ] - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tests whether node is preceded by supplied tokens - * @param {ASTNode} node node to check - * @param {Array} testTokens array of tokens to test against - * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens - * @private - */ - function isPrecededByTokens(node, testTokens) { - const tokenBefore = sourceCode.getTokenBefore(node); - - return testTokens.includes(tokenBefore.value); - } - - /** - * Checks whether node is the first node after statement or in block - * @param {ASTNode} node node to check - * @returns {boolean} Whether or not the node is the first node after statement or in block - * @private - */ - function isFirstNode(node) { - const parentType = node.parent.type; - - if (node.parent.body) { - return Array.isArray(node.parent.body) - ? node.parent.body[0] === node - : node.parent.body === node; - } - - if (parentType === "IfStatement") { - return isPrecededByTokens(node, ["else", ")"]); - } - if (parentType === "DoWhileStatement") { - return isPrecededByTokens(node, ["do"]); - } - if (parentType === "SwitchCase") { - return isPrecededByTokens(node, [":"]); - } - return isPrecededByTokens(node, [")"]); - - } - - /** - * Returns the number of lines of comments that precede the node - * @param {ASTNode} node node to check for overlapping comments - * @param {number} lineNumTokenBefore line number of previous token, to check for overlapping comments - * @returns {number} Number of lines of comments that precede the node - * @private - */ - function calcCommentLines(node, lineNumTokenBefore) { - const comments = sourceCode.getCommentsBefore(node); - let numLinesComments = 0; - - if (!comments.length) { - return numLinesComments; - } - - comments.forEach(comment => { - numLinesComments++; - - if (comment.type === "Block") { - numLinesComments += comment.loc.end.line - comment.loc.start.line; - } - - // avoid counting lines with inline comments twice - if (comment.loc.start.line === lineNumTokenBefore) { - numLinesComments--; - } - - if (comment.loc.end.line === node.loc.start.line) { - numLinesComments--; - } - }); - - return numLinesComments; - } - - /** - * Returns the line number of the token before the node that is passed in as an argument - * @param {ASTNode} node The node to use as the start of the calculation - * @returns {number} Line number of the token before `node` - * @private - */ - function getLineNumberOfTokenBefore(node) { - const tokenBefore = sourceCode.getTokenBefore(node); - let lineNumTokenBefore; - - /** - * Global return (at the beginning of a script) is a special case. - * If there is no token before `return`, then we expect no line - * break before the return. Comments are allowed to occupy lines - * before the global return, just no blank lines. - * Setting lineNumTokenBefore to zero in that case results in the - * desired behavior. - */ - if (tokenBefore) { - lineNumTokenBefore = tokenBefore.loc.end.line; - } else { - lineNumTokenBefore = 0; // global return at beginning of script - } - - return lineNumTokenBefore; - } - - /** - * Checks whether node is preceded by a newline - * @param {ASTNode} node node to check - * @returns {boolean} Whether or not the node is preceded by a newline - * @private - */ - function hasNewlineBefore(node) { - const lineNumNode = node.loc.start.line; - const lineNumTokenBefore = getLineNumberOfTokenBefore(node); - const commentLines = calcCommentLines(node, lineNumTokenBefore); - - return (lineNumNode - lineNumTokenBefore - commentLines) > 1; - } - - /** - * Checks whether it is safe to apply a fix to a given return statement. - * - * The fix is not considered safe if the given return statement has leading comments, - * as we cannot safely determine if the newline should be added before or after the comments. - * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211 - * @param {ASTNode} node The return statement node to check. - * @returns {boolean} `true` if it can fix the node. - * @private - */ - function canFix(node) { - const leadingComments = sourceCode.getCommentsBefore(node); - const lastLeadingComment = leadingComments.at(-1); - const tokenBefore = sourceCode.getTokenBefore(node); - - if (leadingComments.length === 0) { - return true; - } - - /* - * if the last leading comment ends in the same line as the previous token and - * does not share a line with the `return` node, we can consider it safe to fix. - * Example: - * function a() { - * var b; //comment - * return; - * } - */ - if (lastLeadingComment.loc.end.line === tokenBefore.loc.end.line && - lastLeadingComment.loc.end.line !== node.loc.start.line) { - return true; - } - - return false; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - ReturnStatement(node) { - if (!isFirstNode(node) && !hasNewlineBefore(node)) { - context.report({ - node, - messageId: "expected", - fix(fixer) { - if (canFix(node)) { - const tokenBefore = sourceCode.getTokenBefore(node); - const newlines = node.loc.start.line === tokenBefore.loc.end.line ? "\n\n" : "\n"; - - return fixer.insertTextBefore(node, newlines); - } - return null; - } - }); - } - } - }; - } + meta: { + type: "layout", + + docs: { + description: "Require an empty line before `return` statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/newline-before-return", + }, + + fixable: "whitespace", + schema: [], + messages: { + expected: "Expected newline before return statement.", + }, + + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "padding-line-between-statements", + url: "https://eslint.style/rules/js/padding-line-between-statements", + }, + }, + ], + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tests whether node is preceded by supplied tokens + * @param {ASTNode} node node to check + * @param {Array} testTokens array of tokens to test against + * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens + * @private + */ + function isPrecededByTokens(node, testTokens) { + const tokenBefore = sourceCode.getTokenBefore(node); + + return testTokens.includes(tokenBefore.value); + } + + /** + * Checks whether node is the first node after statement or in block + * @param {ASTNode} node node to check + * @returns {boolean} Whether or not the node is the first node after statement or in block + * @private + */ + function isFirstNode(node) { + const parentType = node.parent.type; + + if (node.parent.body) { + return Array.isArray(node.parent.body) + ? node.parent.body[0] === node + : node.parent.body === node; + } + + if (parentType === "IfStatement") { + return isPrecededByTokens(node, ["else", ")"]); + } + if (parentType === "DoWhileStatement") { + return isPrecededByTokens(node, ["do"]); + } + if (parentType === "SwitchCase") { + return isPrecededByTokens(node, [":"]); + } + return isPrecededByTokens(node, [")"]); + } + + /** + * Returns the number of lines of comments that precede the node + * @param {ASTNode} node node to check for overlapping comments + * @param {number} lineNumTokenBefore line number of previous token, to check for overlapping comments + * @returns {number} Number of lines of comments that precede the node + * @private + */ + function calcCommentLines(node, lineNumTokenBefore) { + const comments = sourceCode.getCommentsBefore(node); + let numLinesComments = 0; + + if (!comments.length) { + return numLinesComments; + } + + comments.forEach(comment => { + numLinesComments++; + + if (comment.type === "Block") { + numLinesComments += + comment.loc.end.line - comment.loc.start.line; + } + + // avoid counting lines with inline comments twice + if (comment.loc.start.line === lineNumTokenBefore) { + numLinesComments--; + } + + if (comment.loc.end.line === node.loc.start.line) { + numLinesComments--; + } + }); + + return numLinesComments; + } + + /** + * Returns the line number of the token before the node that is passed in as an argument + * @param {ASTNode} node The node to use as the start of the calculation + * @returns {number} Line number of the token before `node` + * @private + */ + function getLineNumberOfTokenBefore(node) { + const tokenBefore = sourceCode.getTokenBefore(node); + let lineNumTokenBefore; + + /** + * Global return (at the beginning of a script) is a special case. + * If there is no token before `return`, then we expect no line + * break before the return. Comments are allowed to occupy lines + * before the global return, just no blank lines. + * Setting lineNumTokenBefore to zero in that case results in the + * desired behavior. + */ + if (tokenBefore) { + lineNumTokenBefore = tokenBefore.loc.end.line; + } else { + lineNumTokenBefore = 0; // global return at beginning of script + } + + return lineNumTokenBefore; + } + + /** + * Checks whether node is preceded by a newline + * @param {ASTNode} node node to check + * @returns {boolean} Whether or not the node is preceded by a newline + * @private + */ + function hasNewlineBefore(node) { + const lineNumNode = node.loc.start.line; + const lineNumTokenBefore = getLineNumberOfTokenBefore(node); + const commentLines = calcCommentLines(node, lineNumTokenBefore); + + return lineNumNode - lineNumTokenBefore - commentLines > 1; + } + + /** + * Checks whether it is safe to apply a fix to a given return statement. + * + * The fix is not considered safe if the given return statement has leading comments, + * as we cannot safely determine if the newline should be added before or after the comments. + * For more information, see: https://github.com/eslint/eslint/issues/5958#issuecomment-222767211 + * @param {ASTNode} node The return statement node to check. + * @returns {boolean} `true` if it can fix the node. + * @private + */ + function canFix(node) { + const leadingComments = sourceCode.getCommentsBefore(node); + const lastLeadingComment = leadingComments.at(-1); + const tokenBefore = sourceCode.getTokenBefore(node); + + if (leadingComments.length === 0) { + return true; + } + + /* + * if the last leading comment ends in the same line as the previous token and + * does not share a line with the `return` node, we can consider it safe to fix. + * Example: + * function a() { + * var b; //comment + * return; + * } + */ + if ( + lastLeadingComment.loc.end.line === tokenBefore.loc.end.line && + lastLeadingComment.loc.end.line !== node.loc.start.line + ) { + return true; + } + + return false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ReturnStatement(node) { + if (!isFirstNode(node) && !hasNewlineBefore(node)) { + context.report({ + node, + messageId: "expected", + fix(fixer) { + if (canFix(node)) { + const tokenBefore = + sourceCode.getTokenBefore(node); + const newlines = + node.loc.start.line === + tokenBefore.loc.end.line + ? "\n\n" + : "\n"; + + return fixer.insertTextBefore(node, newlines); + } + return null; + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/newline-per-chained-call.js b/lib/rules/newline-per-chained-call.js index 7da017e2010d..c662aec1e08e 100644 --- a/lib/rules/newline-per-chained-call.js +++ b/lib/rules/newline-per-chained-call.js @@ -15,130 +15,145 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "newline-per-chained-call", - url: "https://eslint.style/rules/js/newline-per-chained-call" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require a newline after each call in a method chain", - recommended: false, - url: "https://eslint.org/docs/latest/rules/newline-per-chained-call" - }, - - fixable: "whitespace", - - schema: [{ - type: "object", - properties: { - ignoreChainWithDepth: { - type: "integer", - minimum: 1, - maximum: 10, - default: 2 - } - }, - additionalProperties: false - }], - messages: { - expected: "Expected line break before `{{callee}}`." - } - }, - - create(context) { - - const options = context.options[0] || {}, - ignoreChainWithDepth = options.ignoreChainWithDepth || 2; - - const sourceCode = context.sourceCode; - - /** - * Get the prefix of a given MemberExpression node. - * If the MemberExpression node is a computed value it returns a - * left bracket. If not it returns a period. - * @param {ASTNode} node A MemberExpression node to get - * @returns {string} The prefix of the node. - */ - function getPrefix(node) { - if (node.computed) { - if (node.optional) { - return "?.["; - } - return "["; - } - if (node.optional) { - return "?."; - } - return "."; - } - - /** - * Gets the property text of a given MemberExpression node. - * If the text is multiline, this returns only the first line. - * @param {ASTNode} node A MemberExpression node to get. - * @returns {string} The property text of the node. - */ - function getPropertyText(node) { - const prefix = getPrefix(node); - const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER); - const suffix = node.computed && lines.length === 1 ? "]" : ""; - - return prefix + lines[0] + suffix; - } - - return { - "CallExpression:exit"(node) { - const callee = astUtils.skipChainExpression(node.callee); - - if (callee.type !== "MemberExpression") { - return; - } - - let parent = astUtils.skipChainExpression(callee.object); - let depth = 1; - - while (parent && parent.callee) { - depth += 1; - parent = astUtils.skipChainExpression(astUtils.skipChainExpression(parent.callee).object); - } - - if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) { - const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken); - - context.report({ - node: callee.property, - loc: { - start: firstTokenAfterObject.loc.start, - end: callee.loc.end - }, - messageId: "expected", - data: { - callee: getPropertyText(callee) - }, - fix(fixer) { - return fixer.insertTextBefore(firstTokenAfterObject, "\n"); - } - }); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "newline-per-chained-call", + url: "https://eslint.style/rules/js/newline-per-chained-call", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require a newline after each call in a method chain", + recommended: false, + url: "https://eslint.org/docs/latest/rules/newline-per-chained-call", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + ignoreChainWithDepth: { + type: "integer", + minimum: 1, + maximum: 10, + default: 2, + }, + }, + additionalProperties: false, + }, + ], + messages: { + expected: "Expected line break before `{{callee}}`.", + }, + }, + + create(context) { + const options = context.options[0] || {}, + ignoreChainWithDepth = options.ignoreChainWithDepth || 2; + + const sourceCode = context.sourceCode; + + /** + * Get the prefix of a given MemberExpression node. + * If the MemberExpression node is a computed value it returns a + * left bracket. If not it returns a period. + * @param {ASTNode} node A MemberExpression node to get + * @returns {string} The prefix of the node. + */ + function getPrefix(node) { + if (node.computed) { + if (node.optional) { + return "?.["; + } + return "["; + } + if (node.optional) { + return "?."; + } + return "."; + } + + /** + * Gets the property text of a given MemberExpression node. + * If the text is multiline, this returns only the first line. + * @param {ASTNode} node A MemberExpression node to get. + * @returns {string} The property text of the node. + */ + function getPropertyText(node) { + const prefix = getPrefix(node); + const lines = sourceCode + .getText(node.property) + .split(astUtils.LINEBREAK_MATCHER); + const suffix = node.computed && lines.length === 1 ? "]" : ""; + + return prefix + lines[0] + suffix; + } + + return { + "CallExpression:exit"(node) { + const callee = astUtils.skipChainExpression(node.callee); + + if (callee.type !== "MemberExpression") { + return; + } + + let parent = astUtils.skipChainExpression(callee.object); + let depth = 1; + + while (parent && parent.callee) { + depth += 1; + parent = astUtils.skipChainExpression( + astUtils.skipChainExpression(parent.callee).object, + ); + } + + if ( + depth > ignoreChainWithDepth && + astUtils.isTokenOnSameLine(callee.object, callee.property) + ) { + const firstTokenAfterObject = sourceCode.getTokenAfter( + callee.object, + astUtils.isNotClosingParenToken, + ); + + context.report({ + node: callee.property, + loc: { + start: firstTokenAfterObject.loc.start, + end: callee.loc.end, + }, + messageId: "expected", + data: { + callee: getPropertyText(callee), + }, + fix(fixer) { + return fixer.insertTextBefore( + firstTokenAfterObject, + "\n", + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-alert.js b/lib/rules/no-alert.js index cc8728565015..97c42f6b6b21 100644 --- a/lib/rules/no-alert.js +++ b/lib/rules/no-alert.js @@ -9,9 +9,9 @@ //------------------------------------------------------------------------------ const { - getStaticPropertyName: getPropertyName, - getVariableByName, - skipChainExpression + getStaticPropertyName: getPropertyName, + getVariableByName, + skipChainExpression, } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -24,7 +24,7 @@ const { * @returns {boolean} Whether or not the name is prohibited. */ function isProhibitedIdentifier(name) { - return /^(alert|confirm|prompt)$/u.test(name); + return /^(alert|confirm|prompt)$/u.test(name); } /** @@ -34,13 +34,16 @@ function isProhibitedIdentifier(name) { * @returns {Reference|null} Returns the found reference or null if none were found. */ function findReference(scope, node) { - const references = scope.references.filter(reference => reference.identifier.range[0] === node.range[0] && - reference.identifier.range[1] === node.range[1]); - - if (references.length === 1) { - return references[0]; - } - return null; + const references = scope.references.filter( + reference => + reference.identifier.range[0] === node.range[0] && + reference.identifier.range[1] === node.range[1], + ); + + if (references.length === 1) { + return references[0]; + } + return null; } /** @@ -50,9 +53,11 @@ function findReference(scope, node) { * @returns {boolean} Whether or not the name is shadowed. */ function isShadowed(scope, node) { - const reference = findReference(scope, node); + const reference = findReference(scope, node); - return reference && reference.resolved && reference.resolved.defs.length > 0; + return ( + reference && reference.resolved && reference.resolved.defs.length > 0 + ); } /** @@ -62,20 +67,19 @@ function isShadowed(scope, node) { * @returns {boolean} Whether or not the node is a reference to the global object. */ function isGlobalThisReferenceOrGlobalWindow(scope, node) { - if (scope.type === "global" && node.type === "ThisExpression") { - return true; - } - if ( - node.type === "Identifier" && - ( - node.name === "window" || - (node.name === "globalThis" && getVariableByName(scope, "globalThis")) - ) - ) { - return !isShadowed(scope, node); - } - - return false; + if (scope.type === "global" && node.type === "ThisExpression") { + return true; + } + if ( + node.type === "Identifier" && + (node.name === "window" || + (node.name === "globalThis" && + getVariableByName(scope, "globalThis"))) + ) { + return !isShadowed(scope, node); + } + + return false; } //------------------------------------------------------------------------------ @@ -84,55 +88,62 @@ function isGlobalThisReferenceOrGlobalWindow(scope, node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of `alert`, `confirm`, and `prompt`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-alert" - }, - - schema: [], - - messages: { - unexpected: "Unexpected {{name}}." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - const callee = skipChainExpression(node.callee), - currentScope = sourceCode.getScope(node); - - // without window. - if (callee.type === "Identifier") { - const name = callee.name; - - if (!isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name)) { - context.report({ - node, - messageId: "unexpected", - data: { name } - }); - } - - } else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object)) { - const name = getPropertyName(callee); - - if (isProhibitedIdentifier(name)) { - context.report({ - node, - messageId: "unexpected", - data: { name } - }); - } - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow the use of `alert`, `confirm`, and `prompt`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-alert", + }, + + schema: [], + + messages: { + unexpected: "Unexpected {{name}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + CallExpression(node) { + const callee = skipChainExpression(node.callee), + currentScope = sourceCode.getScope(node); + + // without window. + if (callee.type === "Identifier") { + const name = callee.name; + + if ( + !isShadowed(currentScope, callee) && + isProhibitedIdentifier(callee.name) + ) { + context.report({ + node, + messageId: "unexpected", + data: { name }, + }); + } + } else if ( + callee.type === "MemberExpression" && + isGlobalThisReferenceOrGlobalWindow( + currentScope, + callee.object, + ) + ) { + const name = getPropertyName(callee); + + if (isProhibitedIdentifier(name)) { + context.report({ + node, + messageId: "unexpected", + data: { name }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-array-constructor.js b/lib/rules/no-array-constructor.js index f56b6876674c..c3a8963bfb85 100644 --- a/lib/rules/no-array-constructor.js +++ b/lib/rules/no-array-constructor.js @@ -10,11 +10,11 @@ //------------------------------------------------------------------------------ const { - getVariableByName, - isClosingParenToken, - isOpeningParenToken, - isStartOfExpressionStatement, - needsPrecedingSemicolon + getVariableByName, + isClosingParenToken, + isOpeningParenToken, + isStartOfExpressionStatement, + needsPrecedingSemicolon, } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -23,111 +23,120 @@ const { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `Array` constructors", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-array-constructor" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - preferLiteral: "The array literal notation [] is preferable.", - useLiteral: "Replace with an array literal.", - useLiteralAfterSemicolon: "Replace with an array literal, add preceding semicolon." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Gets the text between the calling parentheses of a CallExpression or NewExpression. - * @param {ASTNode} node A CallExpression or NewExpression node. - * @returns {string} The text between the calling parentheses, or an empty string if there are none. - */ - function getArgumentsText(node) { - const lastToken = sourceCode.getLastToken(node); - - if (!isClosingParenToken(lastToken)) { - return ""; - } - - let firstToken = node.callee; - - do { - firstToken = sourceCode.getTokenAfter(firstToken); - if (!firstToken || firstToken === lastToken) { - return ""; - } - } while (!isOpeningParenToken(firstToken)); - - return sourceCode.text.slice(firstToken.range[1], lastToken.range[0]); - } - - /** - * Disallow construction of dense arrays using the Array constructor - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function check(node) { - if ( - node.callee.type !== "Identifier" || - node.callee.name !== "Array" || - node.arguments.length === 1 && - node.arguments[0].type !== "SpreadElement") { - return; - } - - const variable = getVariableByName(sourceCode.getScope(node), "Array"); - - /* - * Check if `Array` is a predefined global variable: predefined globals have no declarations, - * meaning that the `identifiers` list of the variable object is empty. - */ - if (variable && variable.identifiers.length === 0) { - const argsText = getArgumentsText(node); - let fixText; - let messageId; - - /* - * Check if the suggested change should include a preceding semicolon or not. - * Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically - * before an expression like `Array()` or `new Array()`, but not when the expression - * is changed into an array literal like `[]`. - */ - if (isStartOfExpressionStatement(node) && needsPrecedingSemicolon(sourceCode, node)) { - fixText = `;[${argsText}]`; - messageId = "useLiteralAfterSemicolon"; - } else { - fixText = `[${argsText}]`; - messageId = "useLiteral"; - } - - context.report({ - node, - messageId: "preferLiteral", - suggest: [ - { - messageId, - fix: fixer => fixer.replaceText(node, fixText) - } - ] - }); - } - } - - return { - CallExpression: check, - NewExpression: check - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow `Array` constructors", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-array-constructor", + }, + + hasSuggestions: true, + + schema: [], + + messages: { + preferLiteral: "The array literal notation [] is preferable.", + useLiteral: "Replace with an array literal.", + useLiteralAfterSemicolon: + "Replace with an array literal, add preceding semicolon.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Gets the text between the calling parentheses of a CallExpression or NewExpression. + * @param {ASTNode} node A CallExpression or NewExpression node. + * @returns {string} The text between the calling parentheses, or an empty string if there are none. + */ + function getArgumentsText(node) { + const lastToken = sourceCode.getLastToken(node); + + if (!isClosingParenToken(lastToken)) { + return ""; + } + + let firstToken = node.callee; + + do { + firstToken = sourceCode.getTokenAfter(firstToken); + if (!firstToken || firstToken === lastToken) { + return ""; + } + } while (!isOpeningParenToken(firstToken)); + + return sourceCode.text.slice( + firstToken.range[1], + lastToken.range[0], + ); + } + + /** + * Disallow construction of dense arrays using the Array constructor + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function check(node) { + if ( + node.callee.type !== "Identifier" || + node.callee.name !== "Array" || + (node.arguments.length === 1 && + node.arguments[0].type !== "SpreadElement") + ) { + return; + } + + const variable = getVariableByName( + sourceCode.getScope(node), + "Array", + ); + + /* + * Check if `Array` is a predefined global variable: predefined globals have no declarations, + * meaning that the `identifiers` list of the variable object is empty. + */ + if (variable && variable.identifiers.length === 0) { + const argsText = getArgumentsText(node); + let fixText; + let messageId; + + /* + * Check if the suggested change should include a preceding semicolon or not. + * Due to JavaScript's ASI rules, a missing semicolon may be inserted automatically + * before an expression like `Array()` or `new Array()`, but not when the expression + * is changed into an array literal like `[]`. + */ + if ( + isStartOfExpressionStatement(node) && + needsPrecedingSemicolon(sourceCode, node) + ) { + fixText = `;[${argsText}]`; + messageId = "useLiteralAfterSemicolon"; + } else { + fixText = `[${argsText}]`; + messageId = "useLiteral"; + } + + context.report({ + node, + messageId: "preferLiteral", + suggest: [ + { + messageId, + fix: fixer => fixer.replaceText(node, fixText), + }, + ], + }); + } + } + + return { + CallExpression: check, + NewExpression: check, + }; + }, }; diff --git a/lib/rules/no-async-promise-executor.js b/lib/rules/no-async-promise-executor.js index ea6c851147cb..062205446afd 100644 --- a/lib/rules/no-async-promise-executor.js +++ b/lib/rules/no-async-promise-executor.js @@ -10,30 +10,36 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - docs: { - description: "Disallow using an async function as a Promise executor", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-async-promise-executor" - }, + docs: { + description: + "Disallow using an async function as a Promise executor", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-async-promise-executor", + }, - fixable: null, - schema: [], - messages: { - async: "Promise executor functions should not be async." - } - }, + fixable: null, + schema: [], + messages: { + async: "Promise executor functions should not be async.", + }, + }, - create(context) { - return { - "NewExpression[callee.name='Promise'][arguments.0.async=true]"(node) { - context.report({ - node: context.sourceCode.getFirstToken(node.arguments[0], token => token.value === "async"), - messageId: "async" - }); - } - }; - } + create(context) { + return { + "NewExpression[callee.name='Promise'][arguments.0.async=true]"( + node, + ) { + context.report({ + node: context.sourceCode.getFirstToken( + node.arguments[0], + token => token.value === "async", + ), + messageId: "async", + }); + }, + }; + }, }; diff --git a/lib/rules/no-await-in-loop.js b/lib/rules/no-await-in-loop.js index 20230defa626..1b9ae877a29d 100644 --- a/lib/rules/no-await-in-loop.js +++ b/lib/rules/no-await-in-loop.js @@ -10,19 +10,18 @@ * @returns {boolean} `true` if it should stop traversing. */ function isBoundary(node) { - const t = node.type; + const t = node.type; - return ( - t === "FunctionDeclaration" || - t === "FunctionExpression" || - t === "ArrowFunctionExpression" || - - /* - * Don't report the await expressions on for-await-of loop since it's - * asynchronous iteration intentionally. - */ - (t === "ForOfStatement" && node.await === true) - ); + return ( + t === "FunctionDeclaration" || + t === "FunctionExpression" || + t === "ArrowFunctionExpression" || + /* + * Don't report the await expressions on for-await-of loop since it's + * asynchronous iteration intentionally. + */ + (t === "ForOfStatement" && node.await === true) + ); } /** @@ -32,75 +31,74 @@ function isBoundary(node) { * @returns {boolean} `true` if the node is in loop. */ function isLooped(node, parent) { - switch (parent.type) { - case "ForStatement": - return ( - node === parent.test || - node === parent.update || - node === parent.body - ); + switch (parent.type) { + case "ForStatement": + return ( + node === parent.test || + node === parent.update || + node === parent.body + ); - case "ForOfStatement": - case "ForInStatement": - return node === parent.body; + case "ForOfStatement": + case "ForInStatement": + return node === parent.body; - case "WhileStatement": - case "DoWhileStatement": - return node === parent.test || node === parent.body; + case "WhileStatement": + case "DoWhileStatement": + return node === parent.test || node === parent.body; - default: - return false; - } + default: + return false; + } } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow `await` inside of loops", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-await-in-loop" - }, + meta: { + type: "problem", - schema: [], + docs: { + description: "Disallow `await` inside of loops", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-await-in-loop", + }, - messages: { - unexpectedAwait: "Unexpected `await` inside a loop." - } - }, - create(context) { + schema: [], - /** - * Validate an await expression. - * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate. - * @returns {void} - */ - function validate(awaitNode) { - if (awaitNode.type === "ForOfStatement" && !awaitNode.await) { - return; - } + messages: { + unexpectedAwait: "Unexpected `await` inside a loop.", + }, + }, + create(context) { + /** + * Validate an await expression. + * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate. + * @returns {void} + */ + function validate(awaitNode) { + if (awaitNode.type === "ForOfStatement" && !awaitNode.await) { + return; + } - let node = awaitNode; - let parent = node.parent; + let node = awaitNode; + let parent = node.parent; - while (parent && !isBoundary(parent)) { - if (isLooped(node, parent)) { - context.report({ - node: awaitNode, - messageId: "unexpectedAwait" - }); - return; - } - node = parent; - parent = parent.parent; - } - } + while (parent && !isBoundary(parent)) { + if (isLooped(node, parent)) { + context.report({ + node: awaitNode, + messageId: "unexpectedAwait", + }); + return; + } + node = parent; + parent = parent.parent; + } + } - return { - AwaitExpression: validate, - ForOfStatement: validate - }; - } + return { + AwaitExpression: validate, + ForOfStatement: validate, + }; + }, }; diff --git a/lib/rules/no-bitwise.js b/lib/rules/no-bitwise.js index 794326692eb0..5f853373114e 100644 --- a/lib/rules/no-bitwise.js +++ b/lib/rules/no-bitwise.js @@ -11,9 +11,19 @@ * */ const BITWISE_OPERATORS = [ - "^", "|", "&", "<<", ">>", ">>>", - "^=", "|=", "&=", "<<=", ">>=", ">>>=", - "~" + "^", + "|", + "&", + "<<", + ">>", + ">>>", + "^=", + "|=", + "&=", + "<<=", + ">>=", + ">>>=", + "~", ]; //------------------------------------------------------------------------------ @@ -22,100 +32,114 @@ const BITWISE_OPERATORS = [ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - allow: [], - int32Hint: false - }], - - docs: { - description: "Disallow bitwise operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-bitwise" - }, - - schema: [ - { - type: "object", - properties: { - allow: { - type: "array", - items: { - enum: BITWISE_OPERATORS - }, - uniqueItems: true - }, - int32Hint: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "Unexpected use of '{{operator}}'." - } - }, - - create(context) { - const [{ allow: allowed, int32Hint }] = context.options; - - /** - * Reports an unexpected use of a bitwise operator. - * @param {ASTNode} node Node which contains the bitwise operator. - * @returns {void} - */ - function report(node) { - context.report({ node, messageId: "unexpected", data: { operator: node.operator } }); - } - - /** - * Checks if the given node has a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function hasBitwiseOperator(node) { - return BITWISE_OPERATORS.includes(node.operator); - } - - /** - * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. - * @param {ASTNode} node The node to check. - * @returns {boolean} Whether or not the node has a bitwise operator. - */ - function allowedOperator(node) { - return allowed.includes(node.operator); - } - - /** - * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is used in integer typecasting. - */ - function isInt32Hint(node) { - return int32Hint && node.operator === "|" && node.right && - node.right.type === "Literal" && node.right.value === 0; - } - - /** - * Report if the given node contains a bitwise operator. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNodeForBitwiseOperator(node) { - if (hasBitwiseOperator(node) && !allowedOperator(node) && !isInt32Hint(node)) { - report(node); - } - } - - return { - AssignmentExpression: checkNodeForBitwiseOperator, - BinaryExpression: checkNodeForBitwiseOperator, - UnaryExpression: checkNodeForBitwiseOperator - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + allow: [], + int32Hint: false, + }, + ], + + docs: { + description: "Disallow bitwise operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-bitwise", + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + enum: BITWISE_OPERATORS, + }, + uniqueItems: true, + }, + int32Hint: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: "Unexpected use of '{{operator}}'.", + }, + }, + + create(context) { + const [{ allow: allowed, int32Hint }] = context.options; + + /** + * Reports an unexpected use of a bitwise operator. + * @param {ASTNode} node Node which contains the bitwise operator. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "unexpected", + data: { operator: node.operator }, + }); + } + + /** + * Checks if the given node has a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function hasBitwiseOperator(node) { + return BITWISE_OPERATORS.includes(node.operator); + } + + /** + * Checks if exceptions were provided, e.g. `{ allow: ['~', '|'] }`. + * @param {ASTNode} node The node to check. + * @returns {boolean} Whether or not the node has a bitwise operator. + */ + function allowedOperator(node) { + return allowed.includes(node.operator); + } + + /** + * Checks if the given bitwise operator is used for integer typecasting, i.e. "|0" + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is used in integer typecasting. + */ + function isInt32Hint(node) { + return ( + int32Hint && + node.operator === "|" && + node.right && + node.right.type === "Literal" && + node.right.value === 0 + ); + } + + /** + * Report if the given node contains a bitwise operator. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNodeForBitwiseOperator(node) { + if ( + hasBitwiseOperator(node) && + !allowedOperator(node) && + !isInt32Hint(node) + ) { + report(node); + } + } + + return { + AssignmentExpression: checkNodeForBitwiseOperator, + BinaryExpression: checkNodeForBitwiseOperator, + UnaryExpression: checkNodeForBitwiseOperator, + }; + }, }; diff --git a/lib/rules/no-buffer-constructor.js b/lib/rules/no-buffer-constructor.js index ba602925cffb..dc966fe9e34e 100644 --- a/lib/rules/no-buffer-constructor.js +++ b/lib/rules/no-buffer-constructor.js @@ -11,56 +11,64 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - } - } - ] - }, + meta: { + 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", + type: "problem", - docs: { - description: "Disallow use of the `Buffer()` constructor", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-buffer-constructor" - }, + docs: { + description: "Disallow use of the `Buffer()` constructor", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-buffer-constructor", + }, - schema: [], + schema: [], - messages: { - deprecated: "{{expr}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead." - } - }, + messages: { + deprecated: + "{{expr}} is deprecated. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead.", + }, + }, - create(context) { + create(context) { + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - "CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"(node) { - context.report({ - node, - messageId: "deprecated", - data: { expr: node.type === "CallExpression" ? "Buffer()" : "new Buffer()" } - }); - } - }; - } + return { + "CallExpression[callee.name='Buffer'], NewExpression[callee.name='Buffer']"( + node, + ) { + context.report({ + node, + messageId: "deprecated", + data: { + expr: + node.type === "CallExpression" + ? "Buffer()" + : "new Buffer()", + }, + }); + }, + }; + }, }; diff --git a/lib/rules/no-caller.js b/lib/rules/no-caller.js index 3e61a8e1c2db..4e8530993265 100644 --- a/lib/rules/no-caller.js +++ b/lib/rules/no-caller.js @@ -11,36 +11,42 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of `arguments.caller` or `arguments.callee`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-caller" - }, - - schema: [], - - messages: { - unexpected: "Avoid arguments.{{prop}}." - } - }, - - create(context) { - - return { - - MemberExpression(node) { - const objectName = node.object.name, - propertyName = node.property.name; - - if (objectName === "arguments" && !node.computed && propertyName && propertyName.match(/^calle[er]$/u)) { - context.report({ node, messageId: "unexpected", data: { prop: propertyName } }); - } - - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow the use of `arguments.caller` or `arguments.callee`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-caller", + }, + + schema: [], + + messages: { + unexpected: "Avoid arguments.{{prop}}.", + }, + }, + + create(context) { + return { + MemberExpression(node) { + const objectName = node.object.name, + propertyName = node.property.name; + + if ( + objectName === "arguments" && + !node.computed && + propertyName && + propertyName.match(/^calle[er]$/u) + ) { + context.report({ + node, + messageId: "unexpected", + data: { prop: propertyName }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-case-declarations.js b/lib/rules/no-case-declarations.js index 55f82e241f04..248c587b98f1 100644 --- a/lib/rules/no-case-declarations.js +++ b/lib/rules/no-case-declarations.js @@ -10,67 +10,71 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow lexical declarations in case clauses", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-case-declarations" - }, + docs: { + description: "Disallow lexical declarations in case clauses", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-case-declarations", + }, - hasSuggestions: true, + hasSuggestions: true, - schema: [], + schema: [], - messages: { - addBrackets: "Add {} brackets around the case block.", - unexpected: "Unexpected lexical declaration in case block." - } - }, + messages: { + addBrackets: "Add {} brackets around the case block.", + unexpected: "Unexpected lexical declaration in case block.", + }, + }, - create(context) { + create(context) { + /** + * Checks whether or not a node is a lexical declaration. + * @param {ASTNode} node A direct child statement of a switch case. + * @returns {boolean} Whether or not the node is a lexical declaration. + */ + function isLexicalDeclaration(node) { + switch (node.type) { + case "FunctionDeclaration": + case "ClassDeclaration": + return true; + case "VariableDeclaration": + return node.kind !== "var"; + default: + return false; + } + } - /** - * Checks whether or not a node is a lexical declaration. - * @param {ASTNode} node A direct child statement of a switch case. - * @returns {boolean} Whether or not the node is a lexical declaration. - */ - function isLexicalDeclaration(node) { - switch (node.type) { - case "FunctionDeclaration": - case "ClassDeclaration": - return true; - case "VariableDeclaration": - return node.kind !== "var"; - default: - return false; - } - } + return { + SwitchCase(node) { + for (let i = 0; i < node.consequent.length; i++) { + const statement = node.consequent[i]; - return { - SwitchCase(node) { - for (let i = 0; i < node.consequent.length; i++) { - const statement = node.consequent[i]; - - if (isLexicalDeclaration(statement)) { - context.report({ - node: statement, - messageId: "unexpected", - suggest: [ - { - messageId: "addBrackets", - fix: fixer => [ - fixer.insertTextBefore(node.consequent[0], "{ "), - fixer.insertTextAfter(node.consequent.at(-1), " }") - ] - } - ] - }); - } - } - } - }; - - } + if (isLexicalDeclaration(statement)) { + context.report({ + node: statement, + messageId: "unexpected", + suggest: [ + { + messageId: "addBrackets", + fix: fixer => [ + fixer.insertTextBefore( + node.consequent[0], + "{ ", + ), + fixer.insertTextAfter( + node.consequent.at(-1), + " }", + ), + ], + }, + ], + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-catch-shadow.js b/lib/rules/no-catch-shadow.js index 407b92e5a67d..d00cf875fe14 100644 --- a/lib/rules/no-catch-shadow.js +++ b/lib/rules/no-catch-shadow.js @@ -18,76 +18,79 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `catch` clause parameters from shadowing variables in the outer scope", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-catch-shadow" - }, - - 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" - } - } - ] - }, - schema: [], - - messages: { - mutable: "Value of '{{name}}' may be overwritten in IE 8 and earlier." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the parameters are been shadowed - * @param {Object} scope current scope - * @param {string} name parameter name - * @returns {boolean} True is its been shadowed - */ - function paramIsShadowing(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - "CatchClause[param!=null]"(node) { - let scope = sourceCode.getScope(node); - - /* - * When ecmaVersion >= 6, CatchClause creates its own scope - * so start from one upper scope to exclude the current node - */ - if (scope.block === node) { - scope = scope.upper; - } - - if (paramIsShadowing(scope, node.param.name)) { - context.report({ node, messageId: "mutable", data: { name: node.param.name } }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow `catch` clause parameters from shadowing variables in the outer scope", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-catch-shadow", + }, + + 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", + }, + }, + ], + }, + schema: [], + + messages: { + mutable: + "Value of '{{name}}' may be overwritten in IE 8 and earlier.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the parameters are been shadowed + * @param {Object} scope current scope + * @param {string} name parameter name + * @returns {boolean} True is its been shadowed + */ + function paramIsShadowing(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "CatchClause[param!=null]"(node) { + let scope = sourceCode.getScope(node); + + /* + * When ecmaVersion >= 6, CatchClause creates its own scope + * so start from one upper scope to exclude the current node + */ + if (scope.block === node) { + scope = scope.upper; + } + + if (paramIsShadowing(scope, node.param.name)) { + context.report({ + node, + messageId: "mutable", + data: { name: node.param.name }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-class-assign.js b/lib/rules/no-class-assign.js index 49f3b844e7ef..6bbf64eaf40c 100644 --- a/lib/rules/no-class-assign.js +++ b/lib/rules/no-class-assign.js @@ -13,51 +13,54 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow reassigning class members", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-class-assign" - }, - - schema: [], - - messages: { - class: "'{{name}}' is a class." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, messageId: "class", data: { name: reference.identifier.name } }); - - }); - } - - /** - * Finds and reports references that are non initializer and writable. - * @param {ASTNode} node A ClassDeclaration/ClassExpression node to check. - * @returns {void} - */ - function checkForClass(node) { - sourceCode.getDeclaredVariables(node).forEach(checkVariable); - } - - return { - ClassDeclaration: checkForClass, - ClassExpression: checkForClass - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow reassigning class members", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-class-assign", + }, + + schema: [], + + messages: { + class: "'{{name}}' is a class.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils + .getModifyingReferences(variable.references) + .forEach(reference => { + context.report({ + node: reference.identifier, + messageId: "class", + data: { name: reference.identifier.name }, + }); + }); + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {ASTNode} node A ClassDeclaration/ClassExpression node to check. + * @returns {void} + */ + function checkForClass(node) { + sourceCode.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + ClassDeclaration: checkForClass, + ClassExpression: checkForClass, + }; + }, }; diff --git a/lib/rules/no-compare-neg-zero.js b/lib/rules/no-compare-neg-zero.js index 42e4a374eb33..64e985825d4d 100644 --- a/lib/rules/no-compare-neg-zero.js +++ b/lib/rules/no-compare-neg-zero.js @@ -10,51 +10,65 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow comparing against `-0`", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-compare-neg-zero" - }, - - fixable: null, - schema: [], - - messages: { - unexpected: "Do not use the '{{operator}}' operator to compare against -0." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks a given node is -0 - * @param {ASTNode} node A node to check. - * @returns {boolean} `true` if the node is -0. - */ - function isNegZero(node) { - return node.type === "UnaryExpression" && node.operator === "-" && node.argument.type === "Literal" && node.argument.value === 0; - } - const OPERATORS_TO_CHECK = new Set([">", ">=", "<", "<=", "==", "===", "!=", "!=="]); - - return { - BinaryExpression(node) { - if (OPERATORS_TO_CHECK.has(node.operator)) { - if (isNegZero(node.left) || isNegZero(node.right)) { - context.report({ - node, - messageId: "unexpected", - data: { operator: node.operator } - }); - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow comparing against `-0`", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-compare-neg-zero", + }, + + fixable: null, + schema: [], + + messages: { + unexpected: + "Do not use the '{{operator}}' operator to compare against -0.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks a given node is -0 + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is -0. + */ + function isNegZero(node) { + return ( + node.type === "UnaryExpression" && + node.operator === "-" && + node.argument.type === "Literal" && + node.argument.value === 0 + ); + } + const OPERATORS_TO_CHECK = new Set([ + ">", + ">=", + "<", + "<=", + "==", + "===", + "!=", + "!==", + ]); + + return { + BinaryExpression(node) { + if (OPERATORS_TO_CHECK.has(node.operator)) { + if (isNegZero(node.left) || isNegZero(node.right)) { + context.report({ + node, + messageId: "unexpected", + data: { operator: node.operator }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-cond-assign.js b/lib/rules/no-cond-assign.js index 6d4135d08ecb..242be2917f17 100644 --- a/lib/rules/no-cond-assign.js +++ b/lib/rules/no-cond-assign.js @@ -15,13 +15,19 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const TEST_CONDITION_PARENT_TYPES = new Set(["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ConditionalExpression"]); +const TEST_CONDITION_PARENT_TYPES = new Set([ + "IfStatement", + "WhileStatement", + "DoWhileStatement", + "ForStatement", + "ConditionalExpression", +]); const NODE_DESCRIPTIONS = { - DoWhileStatement: "a 'do...while' statement", - ForStatement: "a 'for' statement", - IfStatement: "an 'if' statement", - WhileStatement: "a 'while' statement" + DoWhileStatement: "a 'do...while' statement", + ForStatement: "a 'for' statement", + IfStatement: "an 'if' statement", + WhileStatement: "a 'while' statement", }; //------------------------------------------------------------------------------ @@ -30,130 +36,140 @@ const NODE_DESCRIPTIONS = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: ["except-parens"], - - docs: { - description: "Disallow assignment operators in conditional expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-cond-assign" - }, - - schema: [ - { - enum: ["except-parens", "always"] - } - ], - - messages: { - unexpected: "Unexpected assignment within {{type}}.", - - // must match JSHint's error message - missing: "Expected a conditional expression and instead saw an assignment." - } - }, - - create(context) { - const [prohibitAssign] = context.options; - const sourceCode = context.sourceCode; - - /** - * Check whether an AST node is the test expression for a conditional statement. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. - */ - function isConditionalTestExpression(node) { - return node.parent && - TEST_CONDITION_PARENT_TYPES.has(node.parent.type) && - node === node.parent.test; - } - - /** - * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. - * @param {!Object} node The node to use at the start of the search. - * @returns {?Object} The closest ancestor node that represents a conditional statement. - */ - function findConditionalAncestor(node) { - let currentAncestor = node; - - do { - if (isConditionalTestExpression(currentAncestor)) { - return currentAncestor.parent; - } - } while ((currentAncestor = currentAncestor.parent) && !astUtils.isFunction(currentAncestor)); - - return null; - } - - /** - * Check whether the code represented by an AST node is enclosed in two sets of parentheses. - * @param {!Object} node The node to test. - * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. - */ - function isParenthesisedTwice(node) { - const previousToken = sourceCode.getTokenBefore(node, 1), - nextToken = sourceCode.getTokenAfter(node, 1); - - return astUtils.isParenthesised(sourceCode, node) && - previousToken && astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && - astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; - } - - /** - * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. - * @param {!Object} node The node for the conditional statement. - * @returns {void} - */ - function testForAssign(node) { - if (node.test && - (node.test.type === "AssignmentExpression") && - (node.type === "ForStatement" - ? !astUtils.isParenthesised(sourceCode, node.test) - : !isParenthesisedTwice(node.test) - ) - ) { - - context.report({ - node: node.test, - messageId: "missing" - }); - } - } - - /** - * Check whether an assignment expression is descended from a conditional statement's test expression. - * @param {!Object} node The node for the assignment expression. - * @returns {void} - */ - function testForConditionalAncestor(node) { - const ancestor = findConditionalAncestor(node); - - if (ancestor) { - context.report({ - node, - messageId: "unexpected", - data: { - type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type - } - }); - } - } - - if (prohibitAssign === "always") { - return { - AssignmentExpression: testForConditionalAncestor - }; - } - - return { - DoWhileStatement: testForAssign, - ForStatement: testForAssign, - IfStatement: testForAssign, - WhileStatement: testForAssign, - ConditionalExpression: testForAssign - }; - - } + meta: { + type: "problem", + + defaultOptions: ["except-parens"], + + docs: { + description: + "Disallow assignment operators in conditional expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-cond-assign", + }, + + schema: [ + { + enum: ["except-parens", "always"], + }, + ], + + messages: { + unexpected: "Unexpected assignment within {{type}}.", + + // must match JSHint's error message + missing: + "Expected a conditional expression and instead saw an assignment.", + }, + }, + + create(context) { + const [prohibitAssign] = context.options; + const sourceCode = context.sourceCode; + + /** + * Check whether an AST node is the test expression for a conditional statement. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the node is the text expression for a conditional statement; otherwise, `false`. + */ + function isConditionalTestExpression(node) { + return ( + node.parent && + TEST_CONDITION_PARENT_TYPES.has(node.parent.type) && + node === node.parent.test + ); + } + + /** + * Given an AST node, perform a bottom-up search for the first ancestor that represents a conditional statement. + * @param {!Object} node The node to use at the start of the search. + * @returns {?Object} The closest ancestor node that represents a conditional statement. + */ + function findConditionalAncestor(node) { + let currentAncestor = node; + + do { + if (isConditionalTestExpression(currentAncestor)) { + return currentAncestor.parent; + } + } while ( + (currentAncestor = currentAncestor.parent) && + !astUtils.isFunction(currentAncestor) + ); + + return null; + } + + /** + * Check whether the code represented by an AST node is enclosed in two sets of parentheses. + * @param {!Object} node The node to test. + * @returns {boolean} `true` if the code is enclosed in two sets of parentheses; otherwise, `false`. + */ + function isParenthesisedTwice(node) { + const previousToken = sourceCode.getTokenBefore(node, 1), + nextToken = sourceCode.getTokenAfter(node, 1); + + return ( + astUtils.isParenthesised(sourceCode, node) && + previousToken && + astUtils.isOpeningParenToken(previousToken) && + previousToken.range[1] <= node.range[0] && + astUtils.isClosingParenToken(nextToken) && + nextToken.range[0] >= node.range[1] + ); + } + + /** + * Check a conditional statement's test expression for top-level assignments that are not enclosed in parentheses. + * @param {!Object} node The node for the conditional statement. + * @returns {void} + */ + function testForAssign(node) { + if ( + node.test && + node.test.type === "AssignmentExpression" && + (node.type === "ForStatement" + ? !astUtils.isParenthesised(sourceCode, node.test) + : !isParenthesisedTwice(node.test)) + ) { + context.report({ + node: node.test, + messageId: "missing", + }); + } + } + + /** + * Check whether an assignment expression is descended from a conditional statement's test expression. + * @param {!Object} node The node for the assignment expression. + * @returns {void} + */ + function testForConditionalAncestor(node) { + const ancestor = findConditionalAncestor(node); + + if (ancestor) { + context.report({ + node, + messageId: "unexpected", + data: { + type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type, + }, + }); + } + } + + if (prohibitAssign === "always") { + return { + AssignmentExpression: testForConditionalAncestor, + }; + } + + return { + DoWhileStatement: testForAssign, + ForStatement: testForAssign, + IfStatement: testForAssign, + WhileStatement: testForAssign, + ConditionalExpression: testForAssign, + }; + }, }; diff --git a/lib/rules/no-confusing-arrow.js b/lib/rules/no-confusing-arrow.js index 8d9a4d84153a..371d5645b9d5 100644 --- a/lib/rules/no-confusing-arrow.js +++ b/lib/rules/no-confusing-arrow.js @@ -19,7 +19,7 @@ const astUtils = require("./utils/ast-utils.js"); * @returns {boolean} `true` if the node is a conditional expression. */ function isConditional(node) { - return node && node.type === "ConditionalExpression"; + return node && node.type === "ConditionalExpression"; } //------------------------------------------------------------------------------ @@ -28,83 +28,100 @@ function isConditional(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-confusing-arrow", - url: "https://eslint.style/rules/js/no-confusing-arrow" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Disallow arrow functions where they could be confused with comparisons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-confusing-arrow" - }, - - fixable: "code", - - schema: [{ - type: "object", - properties: { - allowParens: { type: "boolean", default: true }, - onlyOneSimpleParam: { type: "boolean", default: false } - }, - additionalProperties: false - }], - - messages: { - confusing: "Arrow function used ambiguously with a conditional expression." - } - }, - - create(context) { - const config = context.options[0] || {}; - const allowParens = config.allowParens || (config.allowParens === void 0); - const onlyOneSimpleParam = config.onlyOneSimpleParam; - const sourceCode = context.sourceCode; - - - /** - * Reports if an arrow function contains an ambiguous conditional. - * @param {ASTNode} node A node to check and report. - * @returns {void} - */ - function checkArrowFunc(node) { - const body = node.body; - - if (isConditional(body) && - !(allowParens && astUtils.isParenthesised(sourceCode, body)) && - !(onlyOneSimpleParam && !(node.params.length === 1 && node.params[0].type === "Identifier"))) { - context.report({ - node, - messageId: "confusing", - fix(fixer) { - - // if `allowParens` is not set to true don't bother wrapping in parens - return allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`); - } - }); - } - } - - return { - ArrowFunctionExpression: checkArrowFunc - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-confusing-arrow", + url: "https://eslint.style/rules/js/no-confusing-arrow", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: + "Disallow arrow functions where they could be confused with comparisons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-confusing-arrow", + }, + + fixable: "code", + + schema: [ + { + type: "object", + properties: { + allowParens: { type: "boolean", default: true }, + onlyOneSimpleParam: { type: "boolean", default: false }, + }, + additionalProperties: false, + }, + ], + + messages: { + confusing: + "Arrow function used ambiguously with a conditional expression.", + }, + }, + + create(context) { + const config = context.options[0] || {}; + const allowParens = config.allowParens || config.allowParens === void 0; + const onlyOneSimpleParam = config.onlyOneSimpleParam; + const sourceCode = context.sourceCode; + + /** + * Reports if an arrow function contains an ambiguous conditional. + * @param {ASTNode} node A node to check and report. + * @returns {void} + */ + function checkArrowFunc(node) { + const body = node.body; + + if ( + isConditional(body) && + !(allowParens && astUtils.isParenthesised(sourceCode, body)) && + !( + onlyOneSimpleParam && + !( + node.params.length === 1 && + node.params[0].type === "Identifier" + ) + ) + ) { + context.report({ + node, + messageId: "confusing", + fix(fixer) { + // if `allowParens` is not set to true don't bother wrapping in parens + return ( + allowParens && + fixer.replaceText( + node.body, + `(${sourceCode.getText(node.body)})`, + ) + ); + }, + }); + } + } + + return { + ArrowFunctionExpression: checkArrowFunc, + }; + }, }; diff --git a/lib/rules/no-console.js b/lib/rules/no-console.js index c5cd15c0d923..630df0310b92 100644 --- a/lib/rules/no-console.js +++ b/lib/rules/no-console.js @@ -17,202 +17,205 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{}], - - docs: { - description: "Disallow the use of `console`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-console" - }, - - schema: [ - { - type: "object", - properties: { - allow: { - type: "array", - items: { - type: "string" - }, - minItems: 1, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - hasSuggestions: true, - - messages: { - unexpected: "Unexpected console statement.", - limited: "Unexpected console statement. Only these console methods are allowed: {{ allowed }}.", - removeConsole: "Remove the console.{{ propertyName }}().", - removeMethodCall: "Remove the console method call." - } - }, - - create(context) { - const [{ allow: allowed = [] }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Checks whether the given reference is 'console' or not. - * @param {eslint-scope.Reference} reference The reference to check. - * @returns {boolean} `true` if the reference is 'console'. - */ - function isConsole(reference) { - const id = reference.identifier; - - return id && id.name === "console"; - } - - /** - * Checks whether the property name of the given MemberExpression node - * is allowed by options or not. - * @param {ASTNode} node The MemberExpression node to check. - * @returns {boolean} `true` if the property name of the node is allowed. - */ - function isAllowed(node) { - const propertyName = astUtils.getStaticPropertyName(node); - - return propertyName && allowed.includes(propertyName); - } - - /** - * Checks whether the given reference is a member access which is not - * allowed by options or not. - * @param {eslint-scope.Reference} reference The reference to check. - * @returns {boolean} `true` if the reference is a member access which - * is not allowed by options. - */ - function isMemberAccessExceptAllowed(reference) { - const node = reference.identifier; - const parent = node.parent; - - return ( - parent.type === "MemberExpression" && - parent.object === node && - !isAllowed(parent) - ); - } - - /** - * Checks if removing the ExpressionStatement node will cause ASI to - * break. - * eg. - * foo() - * console.log(); - * [1, 2, 3].forEach(a => doSomething(a)) - * - * Removing the console.log(); statement should leave two statements, but - * here the two statements will become one because [ causes continuation after - * foo(). - * @param {ASTNode} node The ExpressionStatement node to check. - * @returns {boolean} `true` if ASI will break after removing the ExpressionStatement - * node. - */ - function maybeAsiHazard(node) { - const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{ - const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-` - - const tokenBefore = sourceCode.getTokenBefore(node); - const tokenAfter = sourceCode.getTokenAfter(node); - - return ( - Boolean(tokenAfter) && - UNSAFE_CHARS_AFTER.test(tokenAfter.value) && - tokenAfter.value !== "++" && - tokenAfter.value !== "--" && - Boolean(tokenBefore) && - !SAFE_TOKENS_BEFORE.test(tokenBefore.value) - ); - } - - /** - * Checks if the MemberExpression node's parent.parent.parent is a - * Program, BlockStatement, StaticBlock, or SwitchCase node. This check - * is necessary to avoid providing a suggestion that might cause a syntax error. - * - * eg. if (a) console.log(b), removing console.log() here will lead to a - * syntax error. - * if (a) { console.log(b) }, removing console.log() here is acceptable. - * - * Additionally, it checks if the callee of the CallExpression node is - * the node itself. - * - * eg. foo(console.log), cannot provide a suggestion here. - * @param {ASTNode} node The MemberExpression node to check. - * @returns {boolean} `true` if a suggestion can be provided for a node. - */ - function canProvideSuggestions(node) { - return ( - node.parent.type === "CallExpression" && - node.parent.callee === node && - node.parent.parent.type === "ExpressionStatement" && - astUtils.STATEMENT_LIST_PARENTS.has(node.parent.parent.parent.type) && - !maybeAsiHazard(node.parent.parent) - ); - } - - /** - * Reports the given reference as a violation. - * @param {eslint-scope.Reference} reference The reference to report. - * @returns {void} - */ - function report(reference) { - const node = reference.identifier.parent; - - const suggest = []; - - if (canProvideSuggestions(node)) { - const suggestion = { - fix(fixer) { - return fixer.remove(node.parent.parent); - } - }; - - if (node.computed) { - suggestion.messageId = "removeMethodCall"; - } else { - suggestion.messageId = "removeConsole"; - suggestion.data = { propertyName: node.property.name }; - } - suggest.push(suggestion); - } - context.report({ - node, - loc: node.loc, - messageId: allowed.length ? "limited" : "unexpected", - data: { allowed: allowed.join(", ") }, - suggest - }); - } - - return { - "Program:exit"(node) { - const scope = sourceCode.getScope(node); - const consoleVar = astUtils.getVariableByName(scope, "console"); - const shadowed = consoleVar && consoleVar.defs.length > 0; - - /* - * 'scope.through' includes all references to undefined - * variables. If the variable 'console' is not defined, it uses - * 'scope.through'. - */ - const references = consoleVar - ? consoleVar.references - : scope.through.filter(isConsole); - - if (!shadowed) { - references - .filter(isMemberAccessExceptAllowed) - .forEach(report); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{}], + + docs: { + description: "Disallow the use of `console`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-console", + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string", + }, + minItems: 1, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + hasSuggestions: true, + + messages: { + unexpected: "Unexpected console statement.", + limited: + "Unexpected console statement. Only these console methods are allowed: {{ allowed }}.", + removeConsole: "Remove the console.{{ propertyName }}().", + removeMethodCall: "Remove the console method call.", + }, + }, + + create(context) { + const [{ allow: allowed = [] }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Checks whether the given reference is 'console' or not. + * @param {eslint-scope.Reference} reference The reference to check. + * @returns {boolean} `true` if the reference is 'console'. + */ + function isConsole(reference) { + const id = reference.identifier; + + return id && id.name === "console"; + } + + /** + * Checks whether the property name of the given MemberExpression node + * is allowed by options or not. + * @param {ASTNode} node The MemberExpression node to check. + * @returns {boolean} `true` if the property name of the node is allowed. + */ + function isAllowed(node) { + const propertyName = astUtils.getStaticPropertyName(node); + + return propertyName && allowed.includes(propertyName); + } + + /** + * Checks whether the given reference is a member access which is not + * allowed by options or not. + * @param {eslint-scope.Reference} reference The reference to check. + * @returns {boolean} `true` if the reference is a member access which + * is not allowed by options. + */ + function isMemberAccessExceptAllowed(reference) { + const node = reference.identifier; + const parent = node.parent; + + return ( + parent.type === "MemberExpression" && + parent.object === node && + !isAllowed(parent) + ); + } + + /** + * Checks if removing the ExpressionStatement node will cause ASI to + * break. + * eg. + * foo() + * console.log(); + * [1, 2, 3].forEach(a => doSomething(a)) + * + * Removing the console.log(); statement should leave two statements, but + * here the two statements will become one because [ causes continuation after + * foo(). + * @param {ASTNode} node The ExpressionStatement node to check. + * @returns {boolean} `true` if ASI will break after removing the ExpressionStatement + * node. + */ + function maybeAsiHazard(node) { + const SAFE_TOKENS_BEFORE = /^[:;{]$/u; // One of :;{ + const UNSAFE_CHARS_AFTER = /^[-[(/+`]/u; // One of [(/+-` + + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + + return ( + Boolean(tokenAfter) && + UNSAFE_CHARS_AFTER.test(tokenAfter.value) && + tokenAfter.value !== "++" && + tokenAfter.value !== "--" && + Boolean(tokenBefore) && + !SAFE_TOKENS_BEFORE.test(tokenBefore.value) + ); + } + + /** + * Checks if the MemberExpression node's parent.parent.parent is a + * Program, BlockStatement, StaticBlock, or SwitchCase node. This check + * is necessary to avoid providing a suggestion that might cause a syntax error. + * + * eg. if (a) console.log(b), removing console.log() here will lead to a + * syntax error. + * if (a) { console.log(b) }, removing console.log() here is acceptable. + * + * Additionally, it checks if the callee of the CallExpression node is + * the node itself. + * + * eg. foo(console.log), cannot provide a suggestion here. + * @param {ASTNode} node The MemberExpression node to check. + * @returns {boolean} `true` if a suggestion can be provided for a node. + */ + function canProvideSuggestions(node) { + return ( + node.parent.type === "CallExpression" && + node.parent.callee === node && + node.parent.parent.type === "ExpressionStatement" && + astUtils.STATEMENT_LIST_PARENTS.has( + node.parent.parent.parent.type, + ) && + !maybeAsiHazard(node.parent.parent) + ); + } + + /** + * Reports the given reference as a violation. + * @param {eslint-scope.Reference} reference The reference to report. + * @returns {void} + */ + function report(reference) { + const node = reference.identifier.parent; + + const suggest = []; + + if (canProvideSuggestions(node)) { + const suggestion = { + fix(fixer) { + return fixer.remove(node.parent.parent); + }, + }; + + if (node.computed) { + suggestion.messageId = "removeMethodCall"; + } else { + suggestion.messageId = "removeConsole"; + suggestion.data = { propertyName: node.property.name }; + } + suggest.push(suggestion); + } + context.report({ + node, + loc: node.loc, + messageId: allowed.length ? "limited" : "unexpected", + data: { allowed: allowed.join(", ") }, + suggest, + }); + } + + return { + "Program:exit"(node) { + const scope = sourceCode.getScope(node); + const consoleVar = astUtils.getVariableByName(scope, "console"); + const shadowed = consoleVar && consoleVar.defs.length > 0; + + /* + * 'scope.through' includes all references to undefined + * variables. If the variable 'console' is not defined, it uses + * 'scope.through'. + */ + const references = consoleVar + ? consoleVar.references + : scope.through.filter(isConsole); + + if (!shadowed) { + references + .filter(isMemberAccessExceptAllowed) + .forEach(report); + } + }, + }; + }, }; diff --git a/lib/rules/no-const-assign.js b/lib/rules/no-const-assign.js index 0ceaf7ea5ed4..94371919e4bf 100644 --- a/lib/rules/no-const-assign.js +++ b/lib/rules/no-const-assign.js @@ -13,44 +13,50 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow reassigning `const` variables", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-const-assign" - }, - - schema: [], - - messages: { - const: "'{{name}}' is constant." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, messageId: "const", data: { name: reference.identifier.name } }); - }); - } - - return { - VariableDeclaration(node) { - if (node.kind === "const") { - sourceCode.getDeclaredVariables(node).forEach(checkVariable); - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow reassigning `const` variables", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-const-assign", + }, + + schema: [], + + messages: { + const: "'{{name}}' is constant.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils + .getModifyingReferences(variable.references) + .forEach(reference => { + context.report({ + node: reference.identifier, + messageId: "const", + data: { name: reference.identifier.name }, + }); + }); + } + + return { + VariableDeclaration(node) { + if (node.kind === "const") { + sourceCode + .getDeclaredVariables(node) + .forEach(checkVariable); + } + }, + }; + }, }; diff --git a/lib/rules/no-constant-binary-expression.js b/lib/rules/no-constant-binary-expression.js index bc8f0730aece..fa976cb07721 100644 --- a/lib/rules/no-constant-binary-expression.js +++ b/lib/rules/no-constant-binary-expression.js @@ -5,9 +5,28 @@ "use strict"; -const { isNullLiteral, isConstant, isReferenceToGlobalVariable, isLogicalAssignmentOperator, ECMASCRIPT_GLOBALS } = require("./utils/ast-utils"); - -const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|", "^", "&", "**", "<<", ">>", ">>>"]); +const { + isNullLiteral, + isConstant, + isReferenceToGlobalVariable, + isLogicalAssignmentOperator, + ECMASCRIPT_GLOBALS, +} = require("./utils/ast-utils"); + +const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set([ + "+", + "-", + "*", + "/", + "%", + "|", + "^", + "&", + "**", + "<<", + ">>", + ">>>", +]); //------------------------------------------------------------------------------ // Helpers @@ -23,11 +42,13 @@ const NUMERIC_OR_STRING_BINARY_OPERATORS = new Set(["+", "-", "*", "/", "%", "|" * @public */ function isNullOrUndefined(scope, node) { - return ( - isNullLiteral(node) || - (node.type === "Identifier" && node.name === "undefined" && isReferenceToGlobalVariable(scope, node)) || - (node.type === "UnaryExpression" && node.operator === "void") - ); + return ( + isNullLiteral(node) || + (node.type === "Identifier" && + node.name === "undefined" && + isReferenceToGlobalVariable(scope, node)) || + (node.type === "UnaryExpression" && node.operator === "void") + ); } /** @@ -41,79 +62,88 @@ function isNullOrUndefined(scope, node) { * @returns {boolean} Does `node` have constant nullishness? */ function hasConstantNullishness(scope, node, nonNullish) { - if (nonNullish && isNullOrUndefined(scope, node)) { - return false; - } - - switch (node.type) { - case "ObjectExpression": // Objects are never nullish - case "ArrayExpression": // Arrays are never nullish - case "ArrowFunctionExpression": // Functions never nullish - case "FunctionExpression": // Functions are never nullish - case "ClassExpression": // Classes are never nullish - case "NewExpression": // Objects are never nullish - case "Literal": // Nullish, or non-nullish, literals never change - case "TemplateLiteral": // A string is never nullish - case "UpdateExpression": // Numbers are never nullish - case "BinaryExpression": // Numbers, strings, or booleans are never nullish - return true; - case "CallExpression": { - if (node.callee.type !== "Identifier") { - return false; - } - const functionName = node.callee.name; - - return (functionName === "Boolean" || functionName === "String" || functionName === "Number") && - isReferenceToGlobalVariable(scope, node.callee); - } - case "LogicalExpression": { - return node.operator === "??" && hasConstantNullishness(scope, node.right, true); - } - case "AssignmentExpression": - if (node.operator === "=") { - return hasConstantNullishness(scope, node.right, nonNullish); - } - - /* - * Handling short-circuiting assignment operators would require - * walking the scope. We won't attempt that (for now...) / - */ - if (isLogicalAssignmentOperator(node.operator)) { - return false; - } - - /* - * The remaining assignment expressions all result in a numeric or - * string (non-nullish) value: - * "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&=" - */ - - return true; - case "UnaryExpression": - - /* - * "void" Always returns `undefined` - * "typeof" All types are strings, and thus non-nullish - * "!" Boolean is never nullish - * "delete" Returns a boolean, which is never nullish - * Math operators always return numbers or strings, neither of which - * are non-nullish "+", "-", "~" - */ - - return true; - case "SequenceExpression": { - const last = node.expressions.at(-1); - - return hasConstantNullishness(scope, last, nonNullish); - } - case "Identifier": - return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); - case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. - case "JSXFragment": - return false; - default: - return false; - } + if (nonNullish && isNullOrUndefined(scope, node)) { + return false; + } + + switch (node.type) { + case "ObjectExpression": // Objects are never nullish + case "ArrayExpression": // Arrays are never nullish + case "ArrowFunctionExpression": // Functions never nullish + case "FunctionExpression": // Functions are never nullish + case "ClassExpression": // Classes are never nullish + case "NewExpression": // Objects are never nullish + case "Literal": // Nullish, or non-nullish, literals never change + case "TemplateLiteral": // A string is never nullish + case "UpdateExpression": // Numbers are never nullish + case "BinaryExpression": // Numbers, strings, or booleans are never nullish + return true; + case "CallExpression": { + if (node.callee.type !== "Identifier") { + return false; + } + const functionName = node.callee.name; + + return ( + (functionName === "Boolean" || + functionName === "String" || + functionName === "Number") && + isReferenceToGlobalVariable(scope, node.callee) + ); + } + case "LogicalExpression": { + return ( + node.operator === "??" && + hasConstantNullishness(scope, node.right, true) + ); + } + case "AssignmentExpression": + if (node.operator === "=") { + return hasConstantNullishness(scope, node.right, nonNullish); + } + + /* + * Handling short-circuiting assignment operators would require + * walking the scope. We won't attempt that (for now...) / + */ + if (isLogicalAssignmentOperator(node.operator)) { + return false; + } + + /* + * The remaining assignment expressions all result in a numeric or + * string (non-nullish) value: + * "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&=" + */ + + return true; + case "UnaryExpression": + /* + * "void" Always returns `undefined` + * "typeof" All types are strings, and thus non-nullish + * "!" Boolean is never nullish + * "delete" Returns a boolean, which is never nullish + * Math operators always return numbers or strings, neither of which + * are non-nullish "+", "-", "~" + */ + + return true; + case "SequenceExpression": { + const last = node.expressions.at(-1); + + return hasConstantNullishness(scope, last, nonNullish); + } + case "Identifier": + return ( + node.name === "undefined" && + isReferenceToGlobalVariable(scope, node) + ); + case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. + case "JSXFragment": + return false; + default: + return false; + } } /** @@ -127,21 +157,26 @@ function hasConstantNullishness(scope, node, nonNullish) { * @returns {boolean} Is `node` guaranteed to be a boolean? */ function isStaticBoolean(scope, node) { - switch (node.type) { - case "Literal": - return typeof node.value === "boolean"; - case "CallExpression": - return node.callee.type === "Identifier" && node.callee.name === "Boolean" && - isReferenceToGlobalVariable(scope, node.callee) && - (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)); - case "UnaryExpression": - return node.operator === "!" && isConstant(scope, node.argument, true); - default: - return false; - } + switch (node.type) { + case "Literal": + return typeof node.value === "boolean"; + case "CallExpression": + return ( + node.callee.type === "Identifier" && + node.callee.name === "Boolean" && + isReferenceToGlobalVariable(scope, node.callee) && + (node.arguments.length === 0 || + isConstant(scope, node.arguments[0], true)) + ); + case "UnaryExpression": + return ( + node.operator === "!" && isConstant(scope, node.argument, true) + ); + default: + return false; + } } - /** * Test if an AST node will always give the same result when compared to a * boolean value. Note that comparison to boolean values is different than @@ -155,111 +190,116 @@ function isStaticBoolean(scope, node) { * @returns {boolean} Will `node` always coerce to the same boolean value? */ function hasConstantLooseBooleanComparison(scope, node) { - switch (node.type) { - case "ObjectExpression": - case "ClassExpression": - - /** - * In theory objects like: - * - * `{toString: () => a}` - * `{valueOf: () => a}` - * - * Or a classes like: - * - * `class { static toString() { return a } }` - * `class { static valueOf() { return a } }` - * - * Are not constant verifiably when `inBooleanPosition` is - * false, but it's an edge case we've opted not to handle. - */ - return true; - case "ArrayExpression": { - const nonSpreadElements = node.elements.filter(e => - - // Elements can be `null` in sparse arrays: `[,,]`; - e !== null && e.type !== "SpreadElement"); - - - /* - * Possible future direction if needed: We could check if the - * single value would result in variable boolean comparison. - * For now we will err on the side of caution since `[x]` could - * evaluate to `[0]` or `[1]`. - */ - return node.elements.length === 0 || nonSpreadElements.length > 1; - } - case "ArrowFunctionExpression": - case "FunctionExpression": - return true; - case "UnaryExpression": - if (node.operator === "void" || // Always returns `undefined` - node.operator === "typeof" // All `typeof` strings, when coerced to number, are not 0 or 1. - ) { - return true; - } - if (node.operator === "!") { - return isConstant(scope, node.argument, true); - } - - /* - * We won't try to reason about +, -, ~, or delete - * In theory, for the mathematical operators, we could look at the - * argument and try to determine if it coerces to a constant numeric - * value. - */ - return false; - case "NewExpression": // Objects might have custom `.valueOf` or `.toString`. - return false; - case "CallExpression": { - if (node.callee.type === "Identifier" && - node.callee.name === "Boolean" && - isReferenceToGlobalVariable(scope, node.callee) - ) { - return node.arguments.length === 0 || isConstant(scope, node.arguments[0], true); - } - return false; - } - case "Literal": // True or false, literals never change - return true; - case "Identifier": - return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); - case "TemplateLiteral": - - /* - * In theory we could try to check if the quasi are sufficient to - * prove that the expression will always be true, but it would be - * tricky to get right. For example: `000.${foo}000` - */ - return node.expressions.length === 0; - case "AssignmentExpression": - if (node.operator === "=") { - return hasConstantLooseBooleanComparison(scope, node.right); - } - - /* - * Handling short-circuiting assignment operators would require - * walking the scope. We won't attempt that (for now...) - * - * The remaining assignment expressions all result in a numeric or - * string (non-nullish) values which could be truthy or falsy: - * "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&=" - */ - return false; - case "SequenceExpression": { - const last = node.expressions.at(-1); - - return hasConstantLooseBooleanComparison(scope, last); - } - case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. - case "JSXFragment": - return false; - default: - return false; - } + switch (node.type) { + case "ObjectExpression": + case "ClassExpression": + /** + * In theory objects like: + * + * `{toString: () => a}` + * `{valueOf: () => a}` + * + * Or a classes like: + * + * `class { static toString() { return a } }` + * `class { static valueOf() { return a } }` + * + * Are not constant verifiably when `inBooleanPosition` is + * false, but it's an edge case we've opted not to handle. + */ + return true; + case "ArrayExpression": { + const nonSpreadElements = node.elements.filter( + e => + // Elements can be `null` in sparse arrays: `[,,]`; + e !== null && e.type !== "SpreadElement", + ); + + /* + * Possible future direction if needed: We could check if the + * single value would result in variable boolean comparison. + * For now we will err on the side of caution since `[x]` could + * evaluate to `[0]` or `[1]`. + */ + return node.elements.length === 0 || nonSpreadElements.length > 1; + } + case "ArrowFunctionExpression": + case "FunctionExpression": + return true; + case "UnaryExpression": + if ( + node.operator === "void" || // Always returns `undefined` + node.operator === "typeof" // All `typeof` strings, when coerced to number, are not 0 or 1. + ) { + return true; + } + if (node.operator === "!") { + return isConstant(scope, node.argument, true); + } + + /* + * We won't try to reason about +, -, ~, or delete + * In theory, for the mathematical operators, we could look at the + * argument and try to determine if it coerces to a constant numeric + * value. + */ + return false; + case "NewExpression": // Objects might have custom `.valueOf` or `.toString`. + return false; + case "CallExpression": { + if ( + node.callee.type === "Identifier" && + node.callee.name === "Boolean" && + isReferenceToGlobalVariable(scope, node.callee) + ) { + return ( + node.arguments.length === 0 || + isConstant(scope, node.arguments[0], true) + ); + } + return false; + } + case "Literal": // True or false, literals never change + return true; + case "Identifier": + return ( + node.name === "undefined" && + isReferenceToGlobalVariable(scope, node) + ); + case "TemplateLiteral": + /* + * In theory we could try to check if the quasi are sufficient to + * prove that the expression will always be true, but it would be + * tricky to get right. For example: `000.${foo}000` + */ + return node.expressions.length === 0; + case "AssignmentExpression": + if (node.operator === "=") { + return hasConstantLooseBooleanComparison(scope, node.right); + } + + /* + * Handling short-circuiting assignment operators would require + * walking the scope. We won't attempt that (for now...) + * + * The remaining assignment expressions all result in a numeric or + * string (non-nullish) values which could be truthy or falsy: + * "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "|=", "^=", "&=" + */ + return false; + case "SequenceExpression": { + const last = node.expressions.at(-1); + + return hasConstantLooseBooleanComparison(scope, last); + } + case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. + case "JSXFragment": + return false; + default: + return false; + } } - /** * Test if an AST node will always give the same result when _strictly_ compared * to a boolean value. This can happen if the expression can never be boolean, or @@ -270,82 +310,90 @@ function hasConstantLooseBooleanComparison(scope, node) { * static boolean value? */ function hasConstantStrictBooleanComparison(scope, node) { - switch (node.type) { - case "ObjectExpression": // Objects are not booleans - case "ArrayExpression": // Arrays are not booleans - case "ArrowFunctionExpression": // Functions are not booleans - case "FunctionExpression": - case "ClassExpression": // Classes are not booleans - case "NewExpression": // Objects are not booleans - case "TemplateLiteral": // Strings are not booleans - case "Literal": // True, false, or not boolean, literals never change. - case "UpdateExpression": // Numbers are not booleans - return true; - case "BinaryExpression": - return NUMERIC_OR_STRING_BINARY_OPERATORS.has(node.operator); - case "UnaryExpression": { - if (node.operator === "delete") { - return false; - } - if (node.operator === "!") { - return isConstant(scope, node.argument, true); - } - - /* - * The remaining operators return either strings or numbers, neither - * of which are boolean. - */ - return true; - } - case "SequenceExpression": { - const last = node.expressions.at(-1); - - return hasConstantStrictBooleanComparison(scope, last); - } - case "Identifier": - return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); - case "AssignmentExpression": - if (node.operator === "=") { - return hasConstantStrictBooleanComparison(scope, node.right); - } - - /* - * Handling short-circuiting assignment operators would require - * walking the scope. We won't attempt that (for now...) - */ - if (isLogicalAssignmentOperator(node.operator)) { - return false; - } - - /* - * The remaining assignment expressions all result in either a number - * or a string, neither of which can ever be boolean. - */ - return true; - case "CallExpression": { - if (node.callee.type !== "Identifier") { - return false; - } - const functionName = node.callee.name; - - if ( - (functionName === "String" || functionName === "Number") && - isReferenceToGlobalVariable(scope, node.callee) - ) { - return true; - } - if (functionName === "Boolean" && isReferenceToGlobalVariable(scope, node.callee)) { - return ( - node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)); - } - return false; - } - case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. - case "JSXFragment": - return false; - default: - return false; - } + switch (node.type) { + case "ObjectExpression": // Objects are not booleans + case "ArrayExpression": // Arrays are not booleans + case "ArrowFunctionExpression": // Functions are not booleans + case "FunctionExpression": + case "ClassExpression": // Classes are not booleans + case "NewExpression": // Objects are not booleans + case "TemplateLiteral": // Strings are not booleans + case "Literal": // True, false, or not boolean, literals never change. + case "UpdateExpression": // Numbers are not booleans + return true; + case "BinaryExpression": + return NUMERIC_OR_STRING_BINARY_OPERATORS.has(node.operator); + case "UnaryExpression": { + if (node.operator === "delete") { + return false; + } + if (node.operator === "!") { + return isConstant(scope, node.argument, true); + } + + /* + * The remaining operators return either strings or numbers, neither + * of which are boolean. + */ + return true; + } + case "SequenceExpression": { + const last = node.expressions.at(-1); + + return hasConstantStrictBooleanComparison(scope, last); + } + case "Identifier": + return ( + node.name === "undefined" && + isReferenceToGlobalVariable(scope, node) + ); + case "AssignmentExpression": + if (node.operator === "=") { + return hasConstantStrictBooleanComparison(scope, node.right); + } + + /* + * Handling short-circuiting assignment operators would require + * walking the scope. We won't attempt that (for now...) + */ + if (isLogicalAssignmentOperator(node.operator)) { + return false; + } + + /* + * The remaining assignment expressions all result in either a number + * or a string, neither of which can ever be boolean. + */ + return true; + case "CallExpression": { + if (node.callee.type !== "Identifier") { + return false; + } + const functionName = node.callee.name; + + if ( + (functionName === "String" || functionName === "Number") && + isReferenceToGlobalVariable(scope, node.callee) + ) { + return true; + } + if ( + functionName === "Boolean" && + isReferenceToGlobalVariable(scope, node.callee) + ) { + return ( + node.arguments.length === 0 || + isConstant(scope, node.arguments[0], true) + ); + } + return false; + } + case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. + case "JSXFragment": + return false; + default: + return false; + } } /** @@ -355,51 +403,55 @@ function hasConstantStrictBooleanComparison(scope, node) { * @returns {boolean} Will `node` always be new? */ function isAlwaysNew(scope, node) { - switch (node.type) { - case "ObjectExpression": - case "ArrayExpression": - case "ArrowFunctionExpression": - case "FunctionExpression": - case "ClassExpression": - return true; - case "NewExpression": { - if (node.callee.type !== "Identifier") { - return false; - } - - /* - * All the built-in constructors are always new, but - * user-defined constructors could return a sentinel - * object. - * - * Catching these is especially useful for primitive constructors - * which return boxed values, a surprising gotcha' in JavaScript. - */ - return Object.hasOwn(ECMASCRIPT_GLOBALS, node.callee.name) && - isReferenceToGlobalVariable(scope, node.callee); - } - case "Literal": - - // Regular expressions are objects, and thus always new - return typeof node.regex === "object"; - case "SequenceExpression": { - const last = node.expressions.at(-1); - - return isAlwaysNew(scope, last); - } - case "AssignmentExpression": - if (node.operator === "=") { - return isAlwaysNew(scope, node.right); - } - return false; - case "ConditionalExpression": - return isAlwaysNew(scope, node.consequent) && isAlwaysNew(scope, node.alternate); - case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. - case "JSXFragment": - return false; - default: - return false; - } + switch (node.type) { + case "ObjectExpression": + case "ArrayExpression": + case "ArrowFunctionExpression": + case "FunctionExpression": + case "ClassExpression": + return true; + case "NewExpression": { + if (node.callee.type !== "Identifier") { + return false; + } + + /* + * All the built-in constructors are always new, but + * user-defined constructors could return a sentinel + * object. + * + * Catching these is especially useful for primitive constructors + * which return boxed values, a surprising gotcha' in JavaScript. + */ + return ( + Object.hasOwn(ECMASCRIPT_GLOBALS, node.callee.name) && + isReferenceToGlobalVariable(scope, node.callee) + ); + } + case "Literal": + // Regular expressions are objects, and thus always new + return typeof node.regex === "object"; + case "SequenceExpression": { + const last = node.expressions.at(-1); + + return isAlwaysNew(scope, last); + } + case "AssignmentExpression": + if (node.operator === "=") { + return isAlwaysNew(scope, node.right); + } + return false; + case "ConditionalExpression": + return ( + isAlwaysNew(scope, node.consequent) && + isAlwaysNew(scope, node.alternate) + ); + case "JSXElement": // ESLint has a policy of not assuming any specific JSX behavior. + case "JSXFragment": + return false; + default: + return false; + } } /** @@ -411,22 +463,26 @@ function isAlwaysNew(scope, node) { * @returns {ASTNode | null} The node which will cause the expression to have a constant result. */ function findBinaryExpressionConstantOperand(scope, a, b, operator) { - if (operator === "==" || operator === "!=") { - if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || - (isStaticBoolean(scope, a) && hasConstantLooseBooleanComparison(scope, b)) - ) { - return b; - } - } else if (operator === "===" || operator === "!==") { - if ( - (isNullOrUndefined(scope, a) && hasConstantNullishness(scope, b, false)) || - (isStaticBoolean(scope, a) && hasConstantStrictBooleanComparison(scope, b)) - ) { - return b; - } - } - return null; + if (operator === "==" || operator === "!=") { + if ( + (isNullOrUndefined(scope, a) && + hasConstantNullishness(scope, b, false)) || + (isStaticBoolean(scope, a) && + hasConstantLooseBooleanComparison(scope, b)) + ) { + return b; + } + } else if (operator === "===" || operator === "!==") { + if ( + (isNullOrUndefined(scope, a) && + hasConstantNullishness(scope, b, false)) || + (isStaticBoolean(scope, a) && + hasConstantStrictBooleanComparison(scope, b)) + ) { + return b; + } + } + return null; } //------------------------------------------------------------------------------ @@ -435,74 +491,113 @@ function findBinaryExpressionConstantOperand(scope, a, b, operator) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - docs: { - description: "Disallow expressions where the operation doesn't affect the value", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression" - }, - schema: [], - messages: { - constantBinaryOperand: "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.", - constantShortCircuit: "Unexpected constant {{property}} on the left-hand side of a `{{operator}}` expression.", - alwaysNew: "Unexpected comparison to newly constructed object. These two values can never be equal.", - bothAlwaysNew: "Unexpected comparison of two newly constructed objects. These two values can never be equal." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - LogicalExpression(node) { - const { operator, left } = node; - const scope = sourceCode.getScope(node); - - if ((operator === "&&" || operator === "||") && isConstant(scope, left, true)) { - context.report({ node: left, messageId: "constantShortCircuit", data: { property: "truthiness", operator } }); - } else if (operator === "??" && hasConstantNullishness(scope, left, false)) { - context.report({ node: left, messageId: "constantShortCircuit", data: { property: "nullishness", operator } }); - } - }, - BinaryExpression(node) { - const scope = sourceCode.getScope(node); - const { right, left, operator } = node; - const rightConstantOperand = findBinaryExpressionConstantOperand(scope, left, right, operator); - const leftConstantOperand = findBinaryExpressionConstantOperand(scope, right, left, operator); - - if (rightConstantOperand) { - context.report({ node: rightConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "left" } }); - } else if (leftConstantOperand) { - context.report({ node: leftConstantOperand, messageId: "constantBinaryOperand", data: { operator, otherSide: "right" } }); - } else if (operator === "===" || operator === "!==") { - if (isAlwaysNew(scope, left)) { - context.report({ node: left, messageId: "alwaysNew" }); - } else if (isAlwaysNew(scope, right)) { - context.report({ node: right, messageId: "alwaysNew" }); - } - } else if (operator === "==" || operator === "!=") { - - /* - * If both sides are "new", then both sides are objects and - * therefore they will be compared by reference even with `==` - * equality. - */ - if (isAlwaysNew(scope, left) && isAlwaysNew(scope, right)) { - context.report({ node: left, messageId: "bothAlwaysNew" }); - } - } - - } - - /* - * In theory we could handle short-circuiting assignment operators, - * for some constant values, but that would require walking the - * scope to find the value of the variable being assigned. This is - * dependant on https://github.com/eslint/eslint/issues/13776 - * - * AssignmentExpression() {}, - */ - }; - } + meta: { + type: "problem", + docs: { + description: + "Disallow expressions where the operation doesn't affect the value", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-constant-binary-expression", + }, + schema: [], + messages: { + constantBinaryOperand: + "Unexpected constant binary expression. Compares constantly with the {{otherSide}}-hand side of the `{{operator}}`.", + constantShortCircuit: + "Unexpected constant {{property}} on the left-hand side of a `{{operator}}` expression.", + alwaysNew: + "Unexpected comparison to newly constructed object. These two values can never be equal.", + bothAlwaysNew: + "Unexpected comparison of two newly constructed objects. These two values can never be equal.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + LogicalExpression(node) { + const { operator, left } = node; + const scope = sourceCode.getScope(node); + + if ( + (operator === "&&" || operator === "||") && + isConstant(scope, left, true) + ) { + context.report({ + node: left, + messageId: "constantShortCircuit", + data: { property: "truthiness", operator }, + }); + } else if ( + operator === "??" && + hasConstantNullishness(scope, left, false) + ) { + context.report({ + node: left, + messageId: "constantShortCircuit", + data: { property: "nullishness", operator }, + }); + } + }, + BinaryExpression(node) { + const scope = sourceCode.getScope(node); + const { right, left, operator } = node; + const rightConstantOperand = + findBinaryExpressionConstantOperand( + scope, + left, + right, + operator, + ); + const leftConstantOperand = findBinaryExpressionConstantOperand( + scope, + right, + left, + operator, + ); + + if (rightConstantOperand) { + context.report({ + node: rightConstantOperand, + messageId: "constantBinaryOperand", + data: { operator, otherSide: "left" }, + }); + } else if (leftConstantOperand) { + context.report({ + node: leftConstantOperand, + messageId: "constantBinaryOperand", + data: { operator, otherSide: "right" }, + }); + } else if (operator === "===" || operator === "!==") { + if (isAlwaysNew(scope, left)) { + context.report({ node: left, messageId: "alwaysNew" }); + } else if (isAlwaysNew(scope, right)) { + context.report({ node: right, messageId: "alwaysNew" }); + } + } else if (operator === "==" || operator === "!=") { + /* + * If both sides are "new", then both sides are objects and + * therefore they will be compared by reference even with `==` + * equality. + */ + if (isAlwaysNew(scope, left) && isAlwaysNew(scope, right)) { + context.report({ + node: left, + messageId: "bothAlwaysNew", + }); + } + } + }, + + /* + * In theory we could handle short-circuiting assignment operators, + * for some constant values, but that would require walking the + * scope to find the value of the variable being assigned. This is + * dependant on https://github.com/eslint/eslint/issues/13776 + * + * AssignmentExpression() {}, + */ + }; + }, }; diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js index 1514b545cd5c..62aad7b8f976 100644 --- a/lib/rules/no-constant-condition.js +++ b/lib/rules/no-constant-condition.js @@ -17,146 +17,161 @@ const { isConstant } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ checkLoops: "allExceptWhileTrue" }], - - docs: { - description: "Disallow constant expressions in conditions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-constant-condition" - }, - - schema: [ - { - type: "object", - properties: { - checkLoops: { - enum: ["all", "allExceptWhileTrue", "none", true, false] - } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "Unexpected constant condition." - } - }, - - create(context) { - const loopSetStack = []; - const sourceCode = context.sourceCode; - let [{ checkLoops }] = context.options; - - if (checkLoops === true) { - checkLoops = "all"; - } else if (checkLoops === false) { - checkLoops = "none"; - } - - let loopsInCurrentScope = new Set(); - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Tracks when the given node contains a constant condition. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function trackConstantConditionLoop(node) { - if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { - loopsInCurrentScope.add(node); - } - } - - /** - * Reports when the set contains the given constant condition node - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkConstantConditionLoopInSet(node) { - if (loopsInCurrentScope.has(node)) { - loopsInCurrentScope.delete(node); - context.report({ node: node.test, messageId: "unexpected" }); - } - } - - /** - * Reports when the given node contains a constant condition. - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function reportIfConstant(node) { - if (node.test && isConstant(sourceCode.getScope(node), node.test, true)) { - context.report({ node: node.test, messageId: "unexpected" }); - } - } - - /** - * Stores current set of constant loops in loopSetStack temporarily - * and uses a new set to track constant loops - * @returns {void} - * @private - */ - function enterFunction() { - loopSetStack.push(loopsInCurrentScope); - loopsInCurrentScope = new Set(); - } - - /** - * Reports when the set still contains stored constant conditions - * @returns {void} - * @private - */ - function exitFunction() { - loopsInCurrentScope = loopSetStack.pop(); - } - - /** - * Checks node when checkLoops option is enabled - * @param {ASTNode} node The AST node to check. - * @returns {void} - * @private - */ - function checkLoop(node) { - if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") { - trackConstantConditionLoop(node); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - ConditionalExpression: reportIfConstant, - IfStatement: reportIfConstant, - WhileStatement(node) { - if (node.test.type === "Literal" && node.test.value === true && checkLoops === "allExceptWhileTrue") { - return; - } - - checkLoop(node); - }, - "WhileStatement:exit": checkConstantConditionLoopInSet, - DoWhileStatement: checkLoop, - "DoWhileStatement:exit": checkConstantConditionLoopInSet, - ForStatement: checkLoop, - "ForStatement > .test": node => checkLoop(node.parent), - "ForStatement:exit": checkConstantConditionLoopInSet, - FunctionDeclaration: enterFunction, - "FunctionDeclaration:exit": exitFunction, - FunctionExpression: enterFunction, - "FunctionExpression:exit": exitFunction, - YieldExpression: () => loopsInCurrentScope.clear() - }; - - } + meta: { + type: "problem", + + defaultOptions: [{ checkLoops: "allExceptWhileTrue" }], + + docs: { + description: "Disallow constant expressions in conditions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-constant-condition", + }, + + schema: [ + { + type: "object", + properties: { + checkLoops: { + enum: [ + "all", + "allExceptWhileTrue", + "none", + true, + false, + ], + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: "Unexpected constant condition.", + }, + }, + + create(context) { + const loopSetStack = []; + const sourceCode = context.sourceCode; + let [{ checkLoops }] = context.options; + + if (checkLoops === true) { + checkLoops = "all"; + } else if (checkLoops === false) { + checkLoops = "none"; + } + + let loopsInCurrentScope = new Set(); + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Tracks when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function trackConstantConditionLoop(node) { + if ( + node.test && + isConstant(sourceCode.getScope(node), node.test, true) + ) { + loopsInCurrentScope.add(node); + } + } + + /** + * Reports when the set contains the given constant condition node + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkConstantConditionLoopInSet(node) { + if (loopsInCurrentScope.has(node)) { + loopsInCurrentScope.delete(node); + context.report({ node: node.test, messageId: "unexpected" }); + } + } + + /** + * Reports when the given node contains a constant condition. + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function reportIfConstant(node) { + if ( + node.test && + isConstant(sourceCode.getScope(node), node.test, true) + ) { + context.report({ node: node.test, messageId: "unexpected" }); + } + } + + /** + * Stores current set of constant loops in loopSetStack temporarily + * and uses a new set to track constant loops + * @returns {void} + * @private + */ + function enterFunction() { + loopSetStack.push(loopsInCurrentScope); + loopsInCurrentScope = new Set(); + } + + /** + * Reports when the set still contains stored constant conditions + * @returns {void} + * @private + */ + function exitFunction() { + loopsInCurrentScope = loopSetStack.pop(); + } + + /** + * Checks node when checkLoops option is enabled + * @param {ASTNode} node The AST node to check. + * @returns {void} + * @private + */ + function checkLoop(node) { + if (checkLoops === "all" || checkLoops === "allExceptWhileTrue") { + trackConstantConditionLoop(node); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ConditionalExpression: reportIfConstant, + IfStatement: reportIfConstant, + WhileStatement(node) { + if ( + node.test.type === "Literal" && + node.test.value === true && + checkLoops === "allExceptWhileTrue" + ) { + return; + } + + checkLoop(node); + }, + "WhileStatement:exit": checkConstantConditionLoopInSet, + DoWhileStatement: checkLoop, + "DoWhileStatement:exit": checkConstantConditionLoopInSet, + ForStatement: checkLoop, + "ForStatement > .test": node => checkLoop(node.parent), + "ForStatement:exit": checkConstantConditionLoopInSet, + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + YieldExpression: () => loopsInCurrentScope.clear(), + }; + }, }; diff --git a/lib/rules/no-constructor-return.js b/lib/rules/no-constructor-return.js index e9ef73855625..83f6dd50ccf0 100644 --- a/lib/rules/no-constructor-return.js +++ b/lib/rules/no-constructor-return.js @@ -11,52 +11,52 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow returning value from constructor", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-constructor-return" - }, - - schema: [], - - fixable: null, - - messages: { - unexpected: "Unexpected return statement in constructor." - } - }, - - create(context) { - const stack = []; - - return { - onCodePathStart(_, node) { - stack.push(node); - }, - onCodePathEnd() { - stack.pop(); - }, - ReturnStatement(node) { - const last = stack.at(-1); - - if (!last.parent) { - return; - } - - if ( - last.parent.type === "MethodDefinition" && - last.parent.kind === "constructor" && - node.argument - ) { - context.report({ - node, - messageId: "unexpected" - }); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow returning value from constructor", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-constructor-return", + }, + + schema: [], + + fixable: null, + + messages: { + unexpected: "Unexpected return statement in constructor.", + }, + }, + + create(context) { + const stack = []; + + return { + onCodePathStart(_, node) { + stack.push(node); + }, + onCodePathEnd() { + stack.pop(); + }, + ReturnStatement(node) { + const last = stack.at(-1); + + if (!last.parent) { + return; + } + + if ( + last.parent.type === "MethodDefinition" && + last.parent.kind === "constructor" && + node.argument + ) { + context.report({ + node, + messageId: "unexpected", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-continue.js b/lib/rules/no-continue.js index c1b6d75ad390..be2740212c43 100644 --- a/lib/rules/no-continue.js +++ b/lib/rules/no-continue.js @@ -11,30 +11,28 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `continue` statements", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-continue" - }, - - schema: [], - - messages: { - unexpected: "Unexpected use of continue statement." - } - }, - - create(context) { - - return { - ContinueStatement(node) { - context.report({ node, messageId: "unexpected" }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow `continue` statements", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-continue", + }, + + schema: [], + + messages: { + unexpected: "Unexpected use of continue statement.", + }, + }, + + create(context) { + return { + ContinueStatement(node) { + context.report({ node, messageId: "unexpected" }); + }, + }; + }, }; diff --git a/lib/rules/no-control-regex.js b/lib/rules/no-control-regex.js index dc412fcabd52..65b8ecc17ba0 100644 --- a/lib/rules/no-control-regex.js +++ b/lib/rules/no-control-regex.js @@ -7,54 +7,54 @@ const RegExpValidator = require("@eslint-community/regexpp").RegExpValidator; const collector = new (class { - constructor() { - this._source = ""; - this._controlChars = []; - this._validator = new RegExpValidator(this); - } - - onPatternEnter() { - - /* - * `RegExpValidator` may parse the pattern twice in one `validatePattern`. - * So `this._controlChars` should be cleared here as well. - * - * For example, the `/(?\x1f)/` regex will parse the pattern twice. - * This is based on the content described in Annex B. - * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. - * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb - */ - this._controlChars = []; - } - - onCharacter(start, end, cp) { - if (cp >= 0x00 && - cp <= 0x1F && - ( - this._source.codePointAt(start) === cp || - this._source.slice(start, end).startsWith("\\x") || - this._source.slice(start, end).startsWith("\\u") - ) - ) { - this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`); - } - } - - collectControlChars(regexpStr, flags) { - const uFlag = typeof flags === "string" && flags.includes("u"); - const vFlag = typeof flags === "string" && flags.includes("v"); - - this._controlChars = []; - this._source = regexpStr; - - try { - this._validator.validatePattern(regexpStr, void 0, void 0, { unicode: uFlag, unicodeSets: vFlag }); // Call onCharacter hook - } catch { - - // Ignore syntax errors in RegExp. - } - return this._controlChars; - } + constructor() { + this._source = ""; + this._controlChars = []; + this._validator = new RegExpValidator(this); + } + + onPatternEnter() { + /* + * `RegExpValidator` may parse the pattern twice in one `validatePattern`. + * So `this._controlChars` should be cleared here as well. + * + * For example, the `/(?\x1f)/` regex will parse the pattern twice. + * This is based on the content described in Annex B. + * If the regex contains a `GroupName` and the `u` flag is not used, `ParseText` will be called twice. + * See https://tc39.es/ecma262/2023/multipage/additional-ecmascript-features-for-web-browsers.html#sec-parsepattern-annexb + */ + this._controlChars = []; + } + + onCharacter(start, end, cp) { + if ( + cp >= 0x00 && + cp <= 0x1f && + (this._source.codePointAt(start) === cp || + this._source.slice(start, end).startsWith("\\x") || + this._source.slice(start, end).startsWith("\\u")) + ) { + this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`); + } + } + + collectControlChars(regexpStr, flags) { + const uFlag = typeof flags === "string" && flags.includes("u"); + const vFlag = typeof flags === "string" && flags.includes("v"); + + this._controlChars = []; + this._source = regexpStr; + + try { + this._validator.validatePattern(regexpStr, void 0, void 0, { + unicode: uFlag, + unicodeSets: vFlag, + }); // Call onCharacter hook + } catch { + // Ignore syntax errors in RegExp. + } + return this._controlChars; + } })(); //------------------------------------------------------------------------------ @@ -63,76 +63,80 @@ const collector = new (class { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow control characters in regular expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-control-regex" - }, - - schema: [], - - messages: { - unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}." - } - }, - - create(context) { - - /** - * Get the regex expression - * @param {ASTNode} node `Literal` node to evaluate - * @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal - * or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined, - * the `flags` property will be `null`. - * @private - */ - function getRegExp(node) { - if (node.regex) { - return node.regex; - } - if (typeof node.value === "string" && - (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") && - node.parent.callee.type === "Identifier" && - node.parent.callee.name === "RegExp" && - node.parent.arguments[0] === node - ) { - const pattern = node.value; - const flags = - node.parent.arguments.length > 1 && - node.parent.arguments[1].type === "Literal" && - typeof node.parent.arguments[1].value === "string" - ? node.parent.arguments[1].value - : null; - - return { pattern, flags }; - } - - return null; - } - - return { - Literal(node) { - const regExp = getRegExp(node); - - if (regExp) { - const { pattern, flags } = regExp; - const controlCharacters = collector.collectControlChars(pattern, flags); - - if (controlCharacters.length > 0) { - context.report({ - node, - messageId: "unexpected", - data: { - controlChars: controlCharacters.join(", ") - } - }); - } - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow control characters in regular expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-control-regex", + }, + + schema: [], + + messages: { + unexpected: + "Unexpected control character(s) in regular expression: {{controlChars}}.", + }, + }, + + create(context) { + /** + * Get the regex expression + * @param {ASTNode} node `Literal` node to evaluate + * @returns {{ pattern: string, flags: string | null } | null} Regex if found (the given node is either a regex literal + * or a string literal that is the pattern argument of a RegExp constructor call). Otherwise `null`. If flags cannot be determined, + * the `flags` property will be `null`. + * @private + */ + function getRegExp(node) { + if (node.regex) { + return node.regex; + } + if ( + typeof node.value === "string" && + (node.parent.type === "NewExpression" || + node.parent.type === "CallExpression") && + node.parent.callee.type === "Identifier" && + node.parent.callee.name === "RegExp" && + node.parent.arguments[0] === node + ) { + const pattern = node.value; + const flags = + node.parent.arguments.length > 1 && + node.parent.arguments[1].type === "Literal" && + typeof node.parent.arguments[1].value === "string" + ? node.parent.arguments[1].value + : null; + + return { pattern, flags }; + } + + return null; + } + + return { + Literal(node) { + const regExp = getRegExp(node); + + if (regExp) { + const { pattern, flags } = regExp; + const controlCharacters = collector.collectControlChars( + pattern, + flags, + ); + + if (controlCharacters.length > 0) { + context.report({ + node, + messageId: "unexpected", + data: { + controlChars: controlCharacters.join(", "), + }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-debugger.js b/lib/rules/no-debugger.js index f69843515a3d..e96117f05c74 100644 --- a/lib/rules/no-debugger.js +++ b/lib/rules/no-debugger.js @@ -11,33 +11,31 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow the use of `debugger`", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-debugger" - }, - - fixable: null, - schema: [], - - messages: { - unexpected: "Unexpected 'debugger' statement." - } - }, - - create(context) { - - return { - DebuggerStatement(node) { - context.report({ - node, - messageId: "unexpected" - }); - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow the use of `debugger`", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-debugger", + }, + + fixable: null, + schema: [], + + messages: { + unexpected: "Unexpected 'debugger' statement.", + }, + }, + + create(context) { + return { + DebuggerStatement(node) { + context.report({ + node, + messageId: "unexpected", + }); + }, + }; + }, }; diff --git a/lib/rules/no-delete-var.js b/lib/rules/no-delete-var.js index 126603c83ceb..fe7022f472c0 100644 --- a/lib/rules/no-delete-var.js +++ b/lib/rules/no-delete-var.js @@ -11,32 +11,32 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow deleting variables", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-delete-var" - }, - - schema: [], - - messages: { - unexpected: "Variables should not be deleted." - } - }, - - create(context) { - - return { - - UnaryExpression(node) { - if (node.operator === "delete" && node.argument.type === "Identifier") { - context.report({ node, messageId: "unexpected" }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow deleting variables", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-delete-var", + }, + + schema: [], + + messages: { + unexpected: "Variables should not be deleted.", + }, + }, + + create(context) { + return { + UnaryExpression(node) { + if ( + node.operator === "delete" && + node.argument.type === "Identifier" + ) { + context.report({ node, messageId: "unexpected" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-div-regex.js b/lib/rules/no-div-regex.js index 24e6f892f7e7..13719b89b935 100644 --- a/lib/rules/no-div-regex.js +++ b/lib/rules/no-div-regex.js @@ -11,44 +11,50 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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", - - schema: [], - - messages: { - unexpected: "A regular expression literal can be confused with '/='." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - - Literal(node) { - const token = sourceCode.getFirstToken(node); - - if (token.type === "RegularExpression" && token.value[1] === "=") { - context.report({ - node, - messageId: "unexpected", - fix(fixer) { - return fixer.replaceTextRange([token.range[0] + 1, token.range[0] + 2], "[=]"); - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + 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", + + schema: [], + + messages: { + unexpected: + "A regular expression literal can be confused with '/='.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + Literal(node) { + const token = sourceCode.getFirstToken(node); + + if ( + token.type === "RegularExpression" && + token.value[1] === "=" + ) { + context.report({ + node, + messageId: "unexpected", + fix(fixer) { + return fixer.replaceTextRange( + [token.range[0] + 1, token.range[0] + 2], + "[=]", + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-dupe-args.js b/lib/rules/no-dupe-args.js index c04ede5af58d..1d7259994be0 100644 --- a/lib/rules/no-dupe-args.js +++ b/lib/rules/no-dupe-args.js @@ -11,72 +11,71 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow duplicate arguments in `function` definitions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-dupe-args" - }, - - schema: [], - - messages: { - unexpected: "Duplicate param '{{name}}'." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks whether or not a given definition is a parameter's. - * @param {eslint-scope.DefEntry} def A definition to check. - * @returns {boolean} `true` if the definition is a parameter's. - */ - function isParameter(def) { - return def.type === "Parameter"; - } - - /** - * Determines if a given node has duplicate parameters. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkParams(node) { - const variables = sourceCode.getDeclaredVariables(node); - - for (let i = 0; i < variables.length; ++i) { - const variable = variables[i]; - - // Checks and reports duplications. - const defs = variable.defs.filter(isParameter); - - if (defs.length >= 2) { - context.report({ - node, - messageId: "unexpected", - data: { name: variable.name } - }); - } - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - FunctionDeclaration: checkParams, - FunctionExpression: checkParams - }; - - } + meta: { + type: "problem", + + docs: { + description: + "Disallow duplicate arguments in `function` definitions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-dupe-args", + }, + + schema: [], + + messages: { + unexpected: "Duplicate param '{{name}}'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks whether or not a given definition is a parameter's. + * @param {eslint-scope.DefEntry} def A definition to check. + * @returns {boolean} `true` if the definition is a parameter's. + */ + function isParameter(def) { + return def.type === "Parameter"; + } + + /** + * Determines if a given node has duplicate parameters. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkParams(node) { + const variables = sourceCode.getDeclaredVariables(node); + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + // Checks and reports duplications. + const defs = variable.defs.filter(isParameter); + + if (defs.length >= 2) { + context.report({ + node, + messageId: "unexpected", + data: { name: variable.name }, + }); + } + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: checkParams, + FunctionExpression: checkParams, + }; + }, }; diff --git a/lib/rules/no-dupe-class-members.js b/lib/rules/no-dupe-class-members.js index c33553217590..9ba4afc3c9c7 100644 --- a/lib/rules/no-dupe-class-members.js +++ b/lib/rules/no-dupe-class-members.js @@ -13,92 +13,96 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow duplicate class members", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-dupe-class-members" - }, - - schema: [], - - messages: { - unexpected: "Duplicate name '{{name}}'." - } - }, - - create(context) { - let stack = []; - - /** - * Gets state of a given member name. - * @param {string} name A name of a member. - * @param {boolean} isStatic A flag which specifies that is a static member. - * @returns {Object} A state of a given member name. - * - retv.init {boolean} A flag which shows the name is declared as normal member. - * - retv.get {boolean} A flag which shows the name is declared as getter. - * - retv.set {boolean} A flag which shows the name is declared as setter. - */ - function getState(name, isStatic) { - const stateMap = stack.at(-1); - const key = `$${name}`; // to avoid "__proto__". - - if (!stateMap[key]) { - stateMap[key] = { - nonStatic: { init: false, get: false, set: false }, - static: { init: false, get: false, set: false } - }; - } - - return stateMap[key][isStatic ? "static" : "nonStatic"]; - } - - return { - - // Initializes the stack of state of member declarations. - Program() { - stack = []; - }, - - // Initializes state of member declarations for the class. - ClassBody() { - stack.push(Object.create(null)); - }, - - // Disposes the state for the class. - "ClassBody:exit"() { - stack.pop(); - }, - - // Reports the node if its name has been declared already. - "MethodDefinition, PropertyDefinition"(node) { - const name = astUtils.getStaticPropertyName(node); - const kind = node.type === "MethodDefinition" ? node.kind : "field"; - - if (name === null || kind === "constructor") { - return; - } - - const state = getState(name, node.static); - let isDuplicate; - - if (kind === "get") { - isDuplicate = (state.init || state.get); - state.get = true; - } else if (kind === "set") { - isDuplicate = (state.init || state.set); - state.set = true; - } else { - isDuplicate = (state.init || state.get || state.set); - state.init = true; - } - - if (isDuplicate) { - context.report({ node, messageId: "unexpected", data: { name } }); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow duplicate class members", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-dupe-class-members", + }, + + schema: [], + + messages: { + unexpected: "Duplicate name '{{name}}'.", + }, + }, + + create(context) { + let stack = []; + + /** + * Gets state of a given member name. + * @param {string} name A name of a member. + * @param {boolean} isStatic A flag which specifies that is a static member. + * @returns {Object} A state of a given member name. + * - retv.init {boolean} A flag which shows the name is declared as normal member. + * - retv.get {boolean} A flag which shows the name is declared as getter. + * - retv.set {boolean} A flag which shows the name is declared as setter. + */ + function getState(name, isStatic) { + const stateMap = stack.at(-1); + const key = `$${name}`; // to avoid "__proto__". + + if (!stateMap[key]) { + stateMap[key] = { + nonStatic: { init: false, get: false, set: false }, + static: { init: false, get: false, set: false }, + }; + } + + return stateMap[key][isStatic ? "static" : "nonStatic"]; + } + + return { + // Initializes the stack of state of member declarations. + Program() { + stack = []; + }, + + // Initializes state of member declarations for the class. + ClassBody() { + stack.push(Object.create(null)); + }, + + // Disposes the state for the class. + "ClassBody:exit"() { + stack.pop(); + }, + + // Reports the node if its name has been declared already. + "MethodDefinition, PropertyDefinition"(node) { + const name = astUtils.getStaticPropertyName(node); + const kind = + node.type === "MethodDefinition" ? node.kind : "field"; + + if (name === null || kind === "constructor") { + return; + } + + const state = getState(name, node.static); + let isDuplicate; + + if (kind === "get") { + isDuplicate = state.init || state.get; + state.get = true; + } else if (kind === "set") { + isDuplicate = state.init || state.set; + state.set = true; + } else { + isDuplicate = state.init || state.get || state.set; + state.init = true; + } + + if (isDuplicate) { + context.report({ + node, + messageId: "unexpected", + data: { name }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-dupe-else-if.js b/lib/rules/no-dupe-else-if.js index 60f436d1d795..de508071145c 100644 --- a/lib/rules/no-dupe-else-if.js +++ b/lib/rules/no-dupe-else-if.js @@ -23,7 +23,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the array `arrA` is a subset of the array `arrB`. */ function isSubsetByComparator(comparator, arrA, arrB) { - return arrA.every(a => arrB.some(b => comparator(a, b))); + return arrA.every(a => arrB.some(b => comparator(a, b))); } /** @@ -33,10 +33,13 @@ function isSubsetByComparator(comparator, arrA, arrB) { * @returns {ASTNode[]} Array of conditions that makes the node when joined by the operator. */ function splitByLogicalOperator(operator, node) { - if (node.type === "LogicalExpression" && node.operator === operator) { - return [...splitByLogicalOperator(operator, node.left), ...splitByLogicalOperator(operator, node.right)]; - } - return [node]; + if (node.type === "LogicalExpression" && node.operator === operator) { + return [ + ...splitByLogicalOperator(operator, node.left), + ...splitByLogicalOperator(operator, node.right), + ]; + } + return [node]; } const splitByOr = splitByLogicalOperator.bind(null, "||"); @@ -48,75 +51,95 @@ const splitByAnd = splitByLogicalOperator.bind(null, "&&"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow duplicate conditions in if-else-if chains", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-dupe-else-if" - }, - - schema: [], - - messages: { - unexpected: "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes - * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators. - * @param {ASTNode} a First node. - * @param {ASTNode} b Second node. - * @returns {boolean} `true` if the nodes are considered to be equal. - */ - function equal(a, b) { - if (a.type !== b.type) { - return false; - } - - if ( - a.type === "LogicalExpression" && - (a.operator === "||" || a.operator === "&&") && - a.operator === b.operator - ) { - return equal(a.left, b.left) && equal(a.right, b.right) || - equal(a.left, b.right) && equal(a.right, b.left); - } - - return astUtils.equalTokens(a, b, sourceCode); - } - - const isSubset = isSubsetByComparator.bind(null, equal); - - return { - IfStatement(node) { - const test = node.test, - conditionsToCheck = test.type === "LogicalExpression" && test.operator === "&&" - ? [test, ...splitByAnd(test)] - : [test]; - let current = node, - listToCheck = conditionsToCheck.map(c => splitByOr(c).map(splitByAnd)); - - while (current.parent && current.parent.type === "IfStatement" && current.parent.alternate === current) { - current = current.parent; - - const currentOrOperands = splitByOr(current.test).map(splitByAnd); - - listToCheck = listToCheck.map(orOperands => orOperands.filter( - orOperand => !currentOrOperands.some(currentOrOperand => isSubset(currentOrOperand, orOperand)) - )); - - if (listToCheck.some(orOperands => orOperands.length === 0)) { - context.report({ node: test, messageId: "unexpected" }); - break; - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow duplicate conditions in if-else-if chains", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-dupe-else-if", + }, + + schema: [], + + messages: { + unexpected: + "This branch can never execute. Its condition is a duplicate or covered by previous conditions in the if-else-if chain.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Determines whether the two given nodes are considered to be equal. In particular, given that the nodes + * represent expressions in a boolean context, `||` and `&&` can be considered as commutative operators. + * @param {ASTNode} a First node. + * @param {ASTNode} b Second node. + * @returns {boolean} `true` if the nodes are considered to be equal. + */ + function equal(a, b) { + if (a.type !== b.type) { + return false; + } + + if ( + a.type === "LogicalExpression" && + (a.operator === "||" || a.operator === "&&") && + a.operator === b.operator + ) { + return ( + (equal(a.left, b.left) && equal(a.right, b.right)) || + (equal(a.left, b.right) && equal(a.right, b.left)) + ); + } + + return astUtils.equalTokens(a, b, sourceCode); + } + + const isSubset = isSubsetByComparator.bind(null, equal); + + return { + IfStatement(node) { + const test = node.test, + conditionsToCheck = + test.type === "LogicalExpression" && + test.operator === "&&" + ? [test, ...splitByAnd(test)] + : [test]; + let current = node, + listToCheck = conditionsToCheck.map(c => + splitByOr(c).map(splitByAnd), + ); + + while ( + current.parent && + current.parent.type === "IfStatement" && + current.parent.alternate === current + ) { + current = current.parent; + + const currentOrOperands = splitByOr(current.test).map( + splitByAnd, + ); + + listToCheck = listToCheck.map(orOperands => + orOperands.filter( + orOperand => + !currentOrOperands.some(currentOrOperand => + isSubset(currentOrOperand, orOperand), + ), + ), + ); + + if ( + listToCheck.some(orOperands => orOperands.length === 0) + ) { + context.report({ node: test, messageId: "unexpected" }); + break; + } + } + }, + }; + }, }; diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index 980b0044afd2..c9ca7f593b43 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -22,60 +22,59 @@ const SET_KIND = /^(?:init|set)$/u; * The class which stores properties' information of an object. */ class ObjectInfo { - - /** - * @param {ObjectInfo|null} upper The information of the outer object. - * @param {ASTNode} node The ObjectExpression node of this information. - */ - constructor(upper, node) { - this.upper = upper; - this.node = node; - this.properties = new Map(); - } - - /** - * Gets the information of the given Property node. - * @param {ASTNode} node The Property node to get. - * @returns {{get: boolean, set: boolean}} The information of the property. - */ - getPropertyInfo(node) { - const name = astUtils.getStaticPropertyName(node); - - if (!this.properties.has(name)) { - this.properties.set(name, { get: false, set: false }); - } - return this.properties.get(name); - } - - /** - * Checks whether the given property has been defined already or not. - * @param {ASTNode} node The Property node to check. - * @returns {boolean} `true` if the property has been defined. - */ - isPropertyDefined(node) { - const entry = this.getPropertyInfo(node); - - return ( - (GET_KIND.test(node.kind) && entry.get) || - (SET_KIND.test(node.kind) && entry.set) - ); - } - - /** - * Defines the given property. - * @param {ASTNode} node The Property node to define. - * @returns {void} - */ - defineProperty(node) { - const entry = this.getPropertyInfo(node); - - if (GET_KIND.test(node.kind)) { - entry.get = true; - } - if (SET_KIND.test(node.kind)) { - entry.set = true; - } - } + /** + * @param {ObjectInfo|null} upper The information of the outer object. + * @param {ASTNode} node The ObjectExpression node of this information. + */ + constructor(upper, node) { + this.upper = upper; + this.node = node; + this.properties = new Map(); + } + + /** + * Gets the information of the given Property node. + * @param {ASTNode} node The Property node to get. + * @returns {{get: boolean, set: boolean}} The information of the property. + */ + getPropertyInfo(node) { + const name = astUtils.getStaticPropertyName(node); + + if (!this.properties.has(name)) { + this.properties.set(name, { get: false, set: false }); + } + return this.properties.get(name); + } + + /** + * Checks whether the given property has been defined already or not. + * @param {ASTNode} node The Property node to check. + * @returns {boolean} `true` if the property has been defined. + */ + isPropertyDefined(node) { + const entry = this.getPropertyInfo(node); + + return ( + (GET_KIND.test(node.kind) && entry.get) || + (SET_KIND.test(node.kind) && entry.set) + ); + } + + /** + * Defines the given property. + * @param {ASTNode} node The Property node to define. + * @returns {void} + */ + defineProperty(node) { + const entry = this.getPropertyInfo(node); + + if (GET_KIND.test(node.kind)) { + entry.get = true; + } + if (SET_KIND.test(node.kind)) { + entry.set = true; + } + } } //------------------------------------------------------------------------------ @@ -84,59 +83,83 @@ class ObjectInfo { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow duplicate keys in object literals", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-dupe-keys" - }, - - schema: [], - - messages: { - unexpected: "Duplicate key '{{name}}'." - } - }, - - create(context) { - let info = null; - - return { - ObjectExpression(node) { - info = new ObjectInfo(info, node); - }, - "ObjectExpression:exit"() { - info = info.upper; - }, - - Property(node) { - const name = astUtils.getStaticPropertyName(node); - - // Skip destructuring. - if (node.parent.type !== "ObjectExpression") { - return; - } - - // Skip if the name is not static. - if (name === null) { - return; - } - - // Reports if the name is defined already. - if (info.isPropertyDefined(node)) { - context.report({ - node: info.node, - loc: node.key.loc, - messageId: "unexpected", - data: { name } - }); - } - - // Update info. - info.defineProperty(node); - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow duplicate keys in object literals", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-dupe-keys", + }, + + schema: [], + + messages: { + unexpected: "Duplicate key '{{name}}'.", + }, + }, + + create(context) { + let info = null; + + return { + ObjectExpression(node) { + info = new ObjectInfo(info, node); + }, + "ObjectExpression:exit"() { + info = info.upper; + }, + + Property(node) { + const name = astUtils.getStaticPropertyName(node); + + // Skip destructuring. + if (node.parent.type !== "ObjectExpression") { + return; + } + + // Skip if the name is not static. + if (name === null) { + return; + } + + /* + * Skip if the property node is a proto setter. + * Proto setter is a special syntax that sets + * object's prototype instead of creating a property. + * It can be in one of the following forms: + * + * __proto__: + * '__proto__': + * "__proto__": + * + * Duplicate proto setters produce parsing errors, + * so we can just skip them to not interfere with + * regular properties named "__proto__". + */ + if ( + name === "__proto__" && + node.kind === "init" && + !node.computed && + !node.shorthand && + !node.method + ) { + return; + } + + // Reports if the name is defined already. + if (info.isPropertyDefined(node)) { + context.report({ + node: info.node, + loc: node.key.loc, + messageId: "unexpected", + data: { name }, + }); + } + + // Update info. + info.defineProperty(node); + }, + }; + }, }; diff --git a/lib/rules/no-duplicate-case.js b/lib/rules/no-duplicate-case.js index 839f357e74fb..0160cf34d1fe 100644 --- a/lib/rules/no-duplicate-case.js +++ b/lib/rules/no-duplicate-case.js @@ -18,54 +18,61 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - docs: { - description: "Disallow duplicate case labels", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-duplicate-case" - }, + docs: { + description: "Disallow duplicate case labels", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-duplicate-case", + }, - schema: [], + schema: [], - messages: { - unexpected: "Duplicate case label." - } - }, + messages: { + unexpected: "Duplicate case label.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; + create(context) { + const sourceCode = context.sourceCode; - /** - * Determines whether the two given nodes are considered to be equal. - * @param {ASTNode} a First node. - * @param {ASTNode} b Second node. - * @returns {boolean} `true` if the nodes are considered to be equal. - */ - function equal(a, b) { - if (a.type !== b.type) { - return false; - } + /** + * Determines whether the two given nodes are considered to be equal. + * @param {ASTNode} a First node. + * @param {ASTNode} b Second node. + * @returns {boolean} `true` if the nodes are considered to be equal. + */ + function equal(a, b) { + if (a.type !== b.type) { + return false; + } - return astUtils.equalTokens(a, b, sourceCode); - } - return { - SwitchStatement(node) { - const previousTests = []; + return astUtils.equalTokens(a, b, sourceCode); + } + return { + SwitchStatement(node) { + const previousTests = []; - for (const switchCase of node.cases) { - if (switchCase.test) { - const test = switchCase.test; + for (const switchCase of node.cases) { + if (switchCase.test) { + const test = switchCase.test; - if (previousTests.some(previousTest => equal(previousTest, test))) { - context.report({ node: switchCase, messageId: "unexpected" }); - } else { - previousTests.push(test); - } - } - } - } - }; - } + if ( + previousTests.some(previousTest => + equal(previousTest, test), + ) + ) { + context.report({ + node: switchCase, + messageId: "unexpected", + }); + } else { + previousTests.push(test); + } + } + } + }, + }; + }, }; diff --git a/lib/rules/no-duplicate-imports.js b/lib/rules/no-duplicate-imports.js index 1bc52cf97acc..b15098a80964 100644 --- a/lib/rules/no-duplicate-imports.js +++ b/lib/rules/no-duplicate-imports.js @@ -10,8 +10,8 @@ const NAMED_TYPES = ["ImportSpecifier", "ExportSpecifier"]; const NAMESPACE_TYPES = [ - "ImportNamespaceSpecifier", - "ExportNamespaceSpecifier" + "ImportNamespaceSpecifier", + "ExportNamespaceSpecifier", ]; //------------------------------------------------------------------------------ @@ -25,9 +25,9 @@ const NAMESPACE_TYPES = [ * @returns {boolean} True if import/export type belongs to (ImportSpecifier|ExportSpecifier) or (ImportNamespaceSpecifier|ExportNamespaceSpecifier) and false if it doesn't. */ function isImportExportSpecifier(importExportType, type) { - const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES; + const arrayToCheck = type === "named" ? NAMED_TYPES : NAMESPACE_TYPES; - return arrayToCheck.includes(importExportType); + return arrayToCheck.includes(importExportType); } /** @@ -36,24 +36,24 @@ function isImportExportSpecifier(importExportType, type) { * @returns {string} The type of the (import|export). */ function getImportExportType(node) { - if (node.specifiers && node.specifiers.length > 0) { - const nodeSpecifiers = node.specifiers; - const index = nodeSpecifiers.findIndex( - ({ type }) => - isImportExportSpecifier(type, "named") || - isImportExportSpecifier(type, "namespace") - ); - const i = index > -1 ? index : 0; + if (node.specifiers && node.specifiers.length > 0) { + const nodeSpecifiers = node.specifiers; + const index = nodeSpecifiers.findIndex( + ({ type }) => + isImportExportSpecifier(type, "named") || + isImportExportSpecifier(type, "namespace"), + ); + const i = index > -1 ? index : 0; - return nodeSpecifiers[i].type; - } - if (node.type === "ExportAllDeclaration") { - if (node.exported) { - return "ExportNamespaceSpecifier"; - } - return "ExportAll"; - } - return "SideEffectImport"; + return nodeSpecifiers[i].type; + } + if (node.type === "ExportAllDeclaration") { + if (node.exported) { + return "ExportNamespaceSpecifier"; + } + return "ExportAll"; + } + return "SideEffectImport"; } /** @@ -63,28 +63,28 @@ function getImportExportType(node) { * @returns {boolean} True if two (import|export) can be merged, false if they can't. */ function isImportExportCanBeMerged(node1, node2) { - const importExportType1 = getImportExportType(node1); - const importExportType2 = getImportExportType(node2); + const importExportType1 = getImportExportType(node1); + const importExportType2 = getImportExportType(node2); - if ( - (importExportType1 === "ExportAll" && - importExportType2 !== "ExportAll" && - importExportType2 !== "SideEffectImport") || - (importExportType1 !== "ExportAll" && - importExportType1 !== "SideEffectImport" && - importExportType2 === "ExportAll") - ) { - return false; - } - if ( - (isImportExportSpecifier(importExportType1, "namespace") && - isImportExportSpecifier(importExportType2, "named")) || - (isImportExportSpecifier(importExportType2, "namespace") && - isImportExportSpecifier(importExportType1, "named")) - ) { - return false; - } - return true; + if ( + (importExportType1 === "ExportAll" && + importExportType2 !== "ExportAll" && + importExportType2 !== "SideEffectImport") || + (importExportType1 !== "ExportAll" && + importExportType1 !== "SideEffectImport" && + importExportType2 === "ExportAll") + ) { + return false; + } + if ( + (isImportExportSpecifier(importExportType1, "namespace") && + isImportExportSpecifier(importExportType2, "named")) || + (isImportExportSpecifier(importExportType2, "namespace") && + isImportExportSpecifier(importExportType1, "named")) + ) { + return false; + } + return true; } /** @@ -94,15 +94,15 @@ function isImportExportCanBeMerged(node1, node2) { * @returns {boolean} True if the (import|export) should be reported. */ function shouldReportImportExport(node, previousNodes) { - let i = 0; + let i = 0; - while (i < previousNodes.length) { - if (isImportExportCanBeMerged(node, previousNodes[i])) { - return true; - } - i++; - } - return false; + while (i < previousNodes.length) { + if (isImportExportCanBeMerged(node, previousNodes[i])) { + return true; + } + i++; + } + return false; } /** @@ -112,9 +112,9 @@ function shouldReportImportExport(node, previousNodes) { * @returns {[ASTNode]} An array contains only nodes with declarations types equal to type. */ function getNodesByDeclarationType(nodes, type) { - return nodes - .filter(({ declarationType }) => declarationType === type) - .map(({ node }) => node); + return nodes + .filter(({ declarationType }) => declarationType === type) + .map(({ node }) => node); } /** @@ -123,10 +123,10 @@ function getNodesByDeclarationType(nodes, type) { * @returns {string} The name of the module, or empty string if no name. */ function getModule(node) { - if (node && node.source && node.source.value) { - return node.source.value.trim(); - } - return ""; + if (node && node.source && node.source.value) { + return node.source.value.trim(); + } + return ""; } /** @@ -139,49 +139,50 @@ function getModule(node) { * @returns {void} No return value. */ function checkAndReport( - context, - node, - modules, - declarationType, - includeExports + context, + node, + modules, + declarationType, + includeExports, ) { - const module = getModule(node); + const module = getModule(node); - if (modules.has(module)) { - const previousNodes = modules.get(module); - const messagesIds = []; - const importNodes = getNodesByDeclarationType(previousNodes, "import"); - let exportNodes; + if (modules.has(module)) { + const previousNodes = modules.get(module); + const messagesIds = []; + const importNodes = getNodesByDeclarationType(previousNodes, "import"); + let exportNodes; - if (includeExports) { - exportNodes = getNodesByDeclarationType(previousNodes, "export"); - } - if (declarationType === "import") { - if (shouldReportImportExport(node, importNodes)) { - messagesIds.push("import"); - } - if (includeExports) { - if (shouldReportImportExport(node, exportNodes)) { - messagesIds.push("importAs"); - } - } - } else if (declarationType === "export") { - if (shouldReportImportExport(node, exportNodes)) { - messagesIds.push("export"); - } - if (shouldReportImportExport(node, importNodes)) { - messagesIds.push("exportAs"); - } - } - messagesIds.forEach(messageId => - context.report({ - node, - messageId, - data: { - module - } - })); - } + if (includeExports) { + exportNodes = getNodesByDeclarationType(previousNodes, "export"); + } + if (declarationType === "import") { + if (shouldReportImportExport(node, importNodes)) { + messagesIds.push("import"); + } + if (includeExports) { + if (shouldReportImportExport(node, exportNodes)) { + messagesIds.push("importAs"); + } + } + } else if (declarationType === "export") { + if (shouldReportImportExport(node, exportNodes)) { + messagesIds.push("export"); + } + if (shouldReportImportExport(node, importNodes)) { + messagesIds.push("exportAs"); + } + } + messagesIds.forEach(messageId => + context.report({ + node, + messageId, + data: { + module, + }, + }), + ); + } } /** @@ -198,96 +199,98 @@ function checkAndReport( * @returns {nodeCallback} A function passed to ESLint to handle the statement. */ function handleImportsExports( - context, - modules, - declarationType, - includeExports + context, + modules, + declarationType, + includeExports, ) { - return function(node) { - const module = getModule(node); + return function (node) { + const module = getModule(node); - if (module) { - checkAndReport( - context, - node, - modules, - declarationType, - includeExports - ); - const currentNode = { node, declarationType }; - let nodes = [currentNode]; + if (module) { + checkAndReport( + context, + node, + modules, + declarationType, + includeExports, + ); + const currentNode = { node, declarationType }; + let nodes = [currentNode]; - if (modules.has(module)) { - const previousNodes = modules.get(module); + if (modules.has(module)) { + const previousNodes = modules.get(module); - nodes = [...previousNodes, currentNode]; - } - modules.set(module, nodes); - } - }; + nodes = [...previousNodes, currentNode]; + } + modules.set(module, nodes); + } + }; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - defaultOptions: [{ - includeExports: false - }], + defaultOptions: [ + { + includeExports: false, + }, + ], - docs: { - description: "Disallow duplicate module imports", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-duplicate-imports" - }, + docs: { + description: "Disallow duplicate module imports", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-duplicate-imports", + }, - schema: [ - { - type: "object", - properties: { - includeExports: { - type: "boolean" - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + includeExports: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], - messages: { - import: "'{{module}}' import is duplicated.", - importAs: "'{{module}}' import is duplicated as export.", - export: "'{{module}}' export is duplicated.", - exportAs: "'{{module}}' export is duplicated as import." - } - }, + messages: { + import: "'{{module}}' import is duplicated.", + importAs: "'{{module}}' import is duplicated as export.", + export: "'{{module}}' export is duplicated.", + exportAs: "'{{module}}' export is duplicated as import.", + }, + }, - create(context) { - const [{ includeExports }] = context.options; - const modules = new Map(); - const handlers = { - ImportDeclaration: handleImportsExports( - context, - modules, - "import", - includeExports - ) - }; + create(context) { + const [{ includeExports }] = context.options; + const modules = new Map(); + const handlers = { + ImportDeclaration: handleImportsExports( + context, + modules, + "import", + includeExports, + ), + }; - if (includeExports) { - handlers.ExportNamedDeclaration = handleImportsExports( - context, - modules, - "export", - includeExports - ); - handlers.ExportAllDeclaration = handleImportsExports( - context, - modules, - "export", - includeExports - ); - } - return handlers; - } + if (includeExports) { + handlers.ExportNamedDeclaration = handleImportsExports( + context, + modules, + "export", + includeExports, + ); + handlers.ExportAllDeclaration = handleImportsExports( + context, + modules, + "export", + includeExports, + ); + } + return handlers; + }, }; diff --git a/lib/rules/no-else-return.js b/lib/rules/no-else-return.js index e4653d46a4dc..1fc9930f14e4 100644 --- a/lib/rules/no-else-return.js +++ b/lib/rules/no-else-return.js @@ -18,388 +18,433 @@ const FixTracker = require("./utils/fix-tracker"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [{ - type: "object", - properties: { - allowElseIf: { - type: "boolean" - } - }, - additionalProperties: false - }], - - fixable: "code", - - messages: { - unexpected: "Unnecessary 'else' after 'return'." - } - }, - - create(context) { - const [{ allowElseIf }] = context.options; - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks whether the given names can be safely used to declare block-scoped variables - * in the given scope. Name collisions can produce redeclaration syntax errors, - * or silently change references and modify behavior of the original code. - * - * This is not a generic function. In particular, it is assumed that the scope is a function scope or - * a function's inner scope, and that the names can be valid identifiers in the given scope. - * @param {string[]} names Array of variable names. - * @param {eslint-scope.Scope} scope Function scope or a function's inner scope. - * @returns {boolean} True if all names can be safely declared, false otherwise. - */ - function isSafeToDeclare(names, scope) { - - if (names.length === 0) { - return true; - } - - const functionScope = scope.variableScope; - - /* - * If this is a function scope, scope.variables will contain parameters, implicit variables such as "arguments", - * all function-scoped variables ('var'), and block-scoped variables defined in the scope. - * If this is an inner scope, scope.variables will contain block-scoped variables defined in the scope. - * - * Redeclaring any of these would cause a syntax error, except for the implicit variables. - */ - const declaredVariables = scope.variables.filter(({ defs }) => defs.length > 0); - - if (declaredVariables.some(({ name }) => names.includes(name))) { - return false; - } - - // Redeclaring a catch variable would also cause a syntax error. - if (scope !== functionScope && scope.upper.type === "catch") { - if (scope.upper.variables.some(({ name }) => names.includes(name))) { - return false; - } - } - - /* - * Redeclaring an implicit variable, such as "arguments", would not cause a syntax error. - * However, if the variable was used, declaring a new one with the same name would change references - * and modify behavior. - */ - const usedImplicitVariables = scope.variables.filter(({ defs, references }) => - defs.length === 0 && references.length > 0); - - if (usedImplicitVariables.some(({ name }) => names.includes(name))) { - return false; - } - - /* - * Declaring a variable with a name that was already used to reference a variable from an upper scope - * would change references and modify behavior. - */ - if (scope.through.some(t => names.includes(t.identifier.name))) { - return false; - } - - /* - * If the scope is an inner scope (not the function scope), an uninitialized `var` variable declared inside - * the scope node (directly or in one of its descendants) is neither declared nor 'through' in the scope. - * - * For example, this would be a syntax error "Identifier 'a' has already been declared": - * function foo() { if (bar) { let a; if (baz) { var a; } } } - */ - if (scope !== functionScope) { - const scopeNodeRange = scope.block.range; - const variablesToCheck = functionScope.variables.filter(({ name }) => names.includes(name)); - - if (variablesToCheck.some(v => v.defs.some(({ node: { range } }) => - scopeNodeRange[0] <= range[0] && range[1] <= scopeNodeRange[1]))) { - return false; - } - } - - return true; - } - - - /** - * Checks whether the removal of `else` and its braces is safe from variable name collisions. - * @param {Node} node The 'else' node. - * @param {eslint-scope.Scope} scope The scope in which the node and the whole 'if' statement is. - * @returns {boolean} True if it is safe, false otherwise. - */ - function isSafeFromNameCollisions(node, scope) { - - if (node.type === "FunctionDeclaration") { - - // Conditional function declaration. Scope and hoisting are unpredictable, different engines work differently. - return false; - } - - if (node.type !== "BlockStatement") { - return true; - } - - const elseBlockScope = scope.childScopes.find(({ block }) => block === node); - - if (!elseBlockScope) { - - // ecmaVersion < 6, `else` block statement cannot have its own scope, no possible collisions. - return true; - } - - /* - * elseBlockScope is supposed to merge into its upper scope. elseBlockScope.variables array contains - * only block-scoped variables (such as let and const variables or class and function declarations) - * defined directly in the elseBlockScope. These are exactly the only names that could cause collisions. - */ - const namesToCheck = elseBlockScope.variables.map(({ name }) => name); - - return isSafeToDeclare(namesToCheck, scope); - } - - /** - * Display the context report if rule is violated - * @param {Node} elseNode The 'else' node - * @returns {void} - */ - function displayReport(elseNode) { - const currentScope = sourceCode.getScope(elseNode.parent); - - context.report({ - node: elseNode, - messageId: "unexpected", - fix(fixer) { - - if (!isSafeFromNameCollisions(elseNode, currentScope)) { - return null; - } - - const startToken = sourceCode.getFirstToken(elseNode); - const elseToken = sourceCode.getTokenBefore(startToken); - const source = sourceCode.getText(elseNode); - const lastIfToken = sourceCode.getTokenBefore(elseToken); - let fixedSource, firstTokenOfElseBlock; - - if (startToken.type === "Punctuator" && startToken.value === "{") { - firstTokenOfElseBlock = sourceCode.getTokenAfter(startToken); - } else { - firstTokenOfElseBlock = startToken; - } - - /* - * If the if block does not have curly braces and does not end in a semicolon - * and the else block starts with (, [, /, +, ` or -, then it is not - * safe to remove the else keyword, because ASI will not add a semicolon - * after the if block - */ - const ifBlockMaybeUnsafe = elseNode.parent.consequent.type !== "BlockStatement" && lastIfToken.value !== ";"; - const elseBlockUnsafe = /^[([/+`-]/u.test(firstTokenOfElseBlock.value); - - if (ifBlockMaybeUnsafe && elseBlockUnsafe) { - return null; - } - - const endToken = sourceCode.getLastToken(elseNode); - const lastTokenOfElseBlock = sourceCode.getTokenBefore(endToken); - - if (lastTokenOfElseBlock.value !== ";") { - const nextToken = sourceCode.getTokenAfter(endToken); - - const nextTokenUnsafe = nextToken && /^[([/+`-]/u.test(nextToken.value); - const nextTokenOnSameLine = nextToken && nextToken.loc.start.line === lastTokenOfElseBlock.loc.start.line; - - /* - * If the else block contents does not end in a semicolon, - * and the else block starts with (, [, /, +, ` or -, then it is not - * safe to remove the else block, because ASI will not add a semicolon - * after the remaining else block contents - */ - if (nextTokenUnsafe || (nextTokenOnSameLine && nextToken.value !== "}")) { - return null; - } - } - - if (startToken.type === "Punctuator" && startToken.value === "{") { - fixedSource = source.slice(1, -1); - } else { - fixedSource = source; - } - - /* - * Extend the replacement range to include the entire - * function to avoid conflicting with no-useless-return. - * https://github.com/eslint/eslint/issues/8026 - * - * Also, to avoid name collisions between two else blocks. - */ - return new FixTracker(fixer, sourceCode) - .retainEnclosingFunction(elseNode) - .replaceTextRange([elseToken.range[0], elseNode.range[1]], fixedSource); - } - }); - } - - /** - * Check to see if the node is a ReturnStatement - * @param {Node} node The node being evaluated - * @returns {boolean} True if node is a return - */ - function checkForReturn(node) { - return node.type === "ReturnStatement"; - } - - /** - * Naive return checking, does not iterate through the whole - * BlockStatement because we make the assumption that the ReturnStatement - * will be the last node in the body of the BlockStatement. - * @param {Node} node The consequent/alternate node - * @returns {boolean} True if it has a return - */ - function naiveHasReturn(node) { - if (node.type === "BlockStatement") { - const body = node.body, - lastChildNode = body.at(-1); - - return lastChildNode && checkForReturn(lastChildNode); - } - return checkForReturn(node); - } - - /** - * Check to see if the node is valid for evaluation, - * meaning it has an else. - * @param {Node} node The node being evaluated - * @returns {boolean} True if the node is valid - */ - function hasElse(node) { - return node.alternate && node.consequent; - } - - /** - * If the consequent is an IfStatement, check to see if it has an else - * and both its consequent and alternate path return, meaning this is - * a nested case of rule violation. If-Else not considered currently. - * @param {Node} node The consequent node - * @returns {boolean} True if this is a nested rule violation - */ - function checkForIf(node) { - return node.type === "IfStatement" && hasElse(node) && - naiveHasReturn(node.alternate) && naiveHasReturn(node.consequent); - } - - /** - * Check the consequent/body node to make sure it is not - * a ReturnStatement or an IfStatement that returns on both - * code paths. - * @param {Node} node The consequent or body node - * @returns {boolean} `true` if it is a Return/If node that always returns. - */ - function checkForReturnOrIf(node) { - return checkForReturn(node) || checkForIf(node); - } - - - /** - * Check whether a node returns in every codepath. - * @param {Node} node The node to be checked - * @returns {boolean} `true` if it returns on every codepath. - */ - function alwaysReturns(node) { - if (node.type === "BlockStatement") { - - // If we have a BlockStatement, check each consequent body node. - return node.body.some(checkForReturnOrIf); - } - - /* - * If not a block statement, make sure the consequent isn't a - * ReturnStatement or an IfStatement with returns on both paths. - */ - return checkForReturnOrIf(node); - } - - - /** - * Check the if statement, but don't catch else-if blocks. - * @returns {void} - * @param {Node} node The node for the if statement to check - * @private - */ - function checkIfWithoutElse(node) { - const parent = node.parent; - - /* - * Fixing this would require splitting one statement into two, so no error should - * be reported if this node is in a position where only one statement is allowed. - */ - if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { - return; - } - - const consequents = []; - let alternate; - - for (let currentNode = node; currentNode.type === "IfStatement"; currentNode = currentNode.alternate) { - if (!currentNode.alternate) { - return; - } - consequents.push(currentNode.consequent); - alternate = currentNode.alternate; - } - - if (consequents.every(alwaysReturns)) { - displayReport(alternate); - } - } - - /** - * Check the if statement - * @returns {void} - * @param {Node} node The node for the if statement to check - * @private - */ - function checkIfWithElse(node) { - const parent = node.parent; - - - /* - * Fixing this would require splitting one statement into two, so no error should - * be reported if this node is in a position where only one statement is allowed. - */ - if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { - return; - } - - const alternate = node.alternate; - - if (alternate && alwaysReturns(node.consequent)) { - displayReport(alternate); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - "IfStatement:exit": allowElseIf ? checkIfWithoutElse : checkIfWithElse - - }; - - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + allowElseIf: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + unexpected: "Unnecessary 'else' after 'return'.", + }, + }, + + create(context) { + const [{ allowElseIf }] = context.options; + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks whether the given names can be safely used to declare block-scoped variables + * in the given scope. Name collisions can produce redeclaration syntax errors, + * or silently change references and modify behavior of the original code. + * + * This is not a generic function. In particular, it is assumed that the scope is a function scope or + * a function's inner scope, and that the names can be valid identifiers in the given scope. + * @param {string[]} names Array of variable names. + * @param {eslint-scope.Scope} scope Function scope or a function's inner scope. + * @returns {boolean} True if all names can be safely declared, false otherwise. + */ + function isSafeToDeclare(names, scope) { + if (names.length === 0) { + return true; + } + + const functionScope = scope.variableScope; + + /* + * If this is a function scope, scope.variables will contain parameters, implicit variables such as "arguments", + * all function-scoped variables ('var'), and block-scoped variables defined in the scope. + * If this is an inner scope, scope.variables will contain block-scoped variables defined in the scope. + * + * Redeclaring any of these would cause a syntax error, except for the implicit variables. + */ + const declaredVariables = scope.variables.filter( + ({ defs }) => defs.length > 0, + ); + + if (declaredVariables.some(({ name }) => names.includes(name))) { + return false; + } + + // Redeclaring a catch variable would also cause a syntax error. + if (scope !== functionScope && scope.upper.type === "catch") { + if ( + scope.upper.variables.some(({ name }) => + names.includes(name), + ) + ) { + return false; + } + } + + /* + * Redeclaring an implicit variable, such as "arguments", would not cause a syntax error. + * However, if the variable was used, declaring a new one with the same name would change references + * and modify behavior. + */ + const usedImplicitVariables = scope.variables.filter( + ({ defs, references }) => + defs.length === 0 && references.length > 0, + ); + + if ( + usedImplicitVariables.some(({ name }) => names.includes(name)) + ) { + return false; + } + + /* + * Declaring a variable with a name that was already used to reference a variable from an upper scope + * would change references and modify behavior. + */ + if (scope.through.some(t => names.includes(t.identifier.name))) { + return false; + } + + /* + * If the scope is an inner scope (not the function scope), an uninitialized `var` variable declared inside + * the scope node (directly or in one of its descendants) is neither declared nor 'through' in the scope. + * + * For example, this would be a syntax error "Identifier 'a' has already been declared": + * function foo() { if (bar) { let a; if (baz) { var a; } } } + */ + if (scope !== functionScope) { + const scopeNodeRange = scope.block.range; + const variablesToCheck = functionScope.variables.filter( + ({ name }) => names.includes(name), + ); + + if ( + variablesToCheck.some(v => + v.defs.some( + ({ node: { range } }) => + scopeNodeRange[0] <= range[0] && + range[1] <= scopeNodeRange[1], + ), + ) + ) { + return false; + } + } + + return true; + } + + /** + * Checks whether the removal of `else` and its braces is safe from variable name collisions. + * @param {Node} node The 'else' node. + * @param {eslint-scope.Scope} scope The scope in which the node and the whole 'if' statement is. + * @returns {boolean} True if it is safe, false otherwise. + */ + function isSafeFromNameCollisions(node, scope) { + if (node.type === "FunctionDeclaration") { + // Conditional function declaration. Scope and hoisting are unpredictable, different engines work differently. + return false; + } + + if (node.type !== "BlockStatement") { + return true; + } + + const elseBlockScope = scope.childScopes.find( + ({ block }) => block === node, + ); + + if (!elseBlockScope) { + // ecmaVersion < 6, `else` block statement cannot have its own scope, no possible collisions. + return true; + } + + /* + * elseBlockScope is supposed to merge into its upper scope. elseBlockScope.variables array contains + * only block-scoped variables (such as let and const variables or class and function declarations) + * defined directly in the elseBlockScope. These are exactly the only names that could cause collisions. + */ + const namesToCheck = elseBlockScope.variables.map( + ({ name }) => name, + ); + + return isSafeToDeclare(namesToCheck, scope); + } + + /** + * Display the context report if rule is violated + * @param {Node} elseNode The 'else' node + * @returns {void} + */ + function displayReport(elseNode) { + const currentScope = sourceCode.getScope(elseNode.parent); + + context.report({ + node: elseNode, + messageId: "unexpected", + fix(fixer) { + if (!isSafeFromNameCollisions(elseNode, currentScope)) { + return null; + } + + const startToken = sourceCode.getFirstToken(elseNode); + const elseToken = sourceCode.getTokenBefore(startToken); + const source = sourceCode.getText(elseNode); + const lastIfToken = sourceCode.getTokenBefore(elseToken); + let fixedSource, firstTokenOfElseBlock; + + if ( + startToken.type === "Punctuator" && + startToken.value === "{" + ) { + firstTokenOfElseBlock = + sourceCode.getTokenAfter(startToken); + } else { + firstTokenOfElseBlock = startToken; + } + + /* + * If the if block does not have curly braces and does not end in a semicolon + * and the else block starts with (, [, /, +, ` or -, then it is not + * safe to remove the else keyword, because ASI will not add a semicolon + * after the if block + */ + const ifBlockMaybeUnsafe = + elseNode.parent.consequent.type !== "BlockStatement" && + lastIfToken.value !== ";"; + const elseBlockUnsafe = /^[([/+`-]/u.test( + firstTokenOfElseBlock.value, + ); + + if (ifBlockMaybeUnsafe && elseBlockUnsafe) { + return null; + } + + const endToken = sourceCode.getLastToken(elseNode); + const lastTokenOfElseBlock = + sourceCode.getTokenBefore(endToken); + + if (lastTokenOfElseBlock.value !== ";") { + const nextToken = sourceCode.getTokenAfter(endToken); + + const nextTokenUnsafe = + nextToken && /^[([/+`-]/u.test(nextToken.value); + const nextTokenOnSameLine = + nextToken && + nextToken.loc.start.line === + lastTokenOfElseBlock.loc.start.line; + + /* + * If the else block contents does not end in a semicolon, + * and the else block starts with (, [, /, +, ` or -, then it is not + * safe to remove the else block, because ASI will not add a semicolon + * after the remaining else block contents + */ + if ( + nextTokenUnsafe || + (nextTokenOnSameLine && nextToken.value !== "}") + ) { + return null; + } + } + + if ( + startToken.type === "Punctuator" && + startToken.value === "{" + ) { + fixedSource = source.slice(1, -1); + } else { + fixedSource = source; + } + + /* + * Extend the replacement range to include the entire + * function to avoid conflicting with no-useless-return. + * https://github.com/eslint/eslint/issues/8026 + * + * Also, to avoid name collisions between two else blocks. + */ + return new FixTracker(fixer, sourceCode) + .retainEnclosingFunction(elseNode) + .replaceTextRange( + [elseToken.range[0], elseNode.range[1]], + fixedSource, + ); + }, + }); + } + + /** + * Check to see if the node is a ReturnStatement + * @param {Node} node The node being evaluated + * @returns {boolean} True if node is a return + */ + function checkForReturn(node) { + return node.type === "ReturnStatement"; + } + + /** + * Naive return checking, does not iterate through the whole + * BlockStatement because we make the assumption that the ReturnStatement + * will be the last node in the body of the BlockStatement. + * @param {Node} node The consequent/alternate node + * @returns {boolean} True if it has a return + */ + function naiveHasReturn(node) { + if (node.type === "BlockStatement") { + const body = node.body, + lastChildNode = body.at(-1); + + return lastChildNode && checkForReturn(lastChildNode); + } + return checkForReturn(node); + } + + /** + * Check to see if the node is valid for evaluation, + * meaning it has an else. + * @param {Node} node The node being evaluated + * @returns {boolean} True if the node is valid + */ + function hasElse(node) { + return node.alternate && node.consequent; + } + + /** + * If the consequent is an IfStatement, check to see if it has an else + * and both its consequent and alternate path return, meaning this is + * a nested case of rule violation. If-Else not considered currently. + * @param {Node} node The consequent node + * @returns {boolean} True if this is a nested rule violation + */ + function checkForIf(node) { + return ( + node.type === "IfStatement" && + hasElse(node) && + naiveHasReturn(node.alternate) && + naiveHasReturn(node.consequent) + ); + } + + /** + * Check the consequent/body node to make sure it is not + * a ReturnStatement or an IfStatement that returns on both + * code paths. + * @param {Node} node The consequent or body node + * @returns {boolean} `true` if it is a Return/If node that always returns. + */ + function checkForReturnOrIf(node) { + return checkForReturn(node) || checkForIf(node); + } + + /** + * Check whether a node returns in every codepath. + * @param {Node} node The node to be checked + * @returns {boolean} `true` if it returns on every codepath. + */ + function alwaysReturns(node) { + if (node.type === "BlockStatement") { + // If we have a BlockStatement, check each consequent body node. + return node.body.some(checkForReturnOrIf); + } + + /* + * If not a block statement, make sure the consequent isn't a + * ReturnStatement or an IfStatement with returns on both paths. + */ + return checkForReturnOrIf(node); + } + + /** + * Check the if statement, but don't catch else-if blocks. + * @returns {void} + * @param {Node} node The node for the if statement to check + * @private + */ + function checkIfWithoutElse(node) { + const parent = node.parent; + + /* + * Fixing this would require splitting one statement into two, so no error should + * be reported if this node is in a position where only one statement is allowed. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { + return; + } + + const consequents = []; + let alternate; + + for ( + let currentNode = node; + currentNode.type === "IfStatement"; + currentNode = currentNode.alternate + ) { + if (!currentNode.alternate) { + return; + } + consequents.push(currentNode.consequent); + alternate = currentNode.alternate; + } + + if (consequents.every(alwaysReturns)) { + displayReport(alternate); + } + } + + /** + * Check the if statement + * @returns {void} + * @param {Node} node The node for the if statement to check + * @private + */ + function checkIfWithElse(node) { + const parent = node.parent; + + /* + * Fixing this would require splitting one statement into two, so no error should + * be reported if this node is in a position where only one statement is allowed. + */ + if (!astUtils.STATEMENT_LIST_PARENTS.has(parent.type)) { + return; + } + + const alternate = node.alternate; + + if (alternate && alwaysReturns(node.consequent)) { + displayReport(alternate); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "IfStatement:exit": allowElseIf + ? checkIfWithoutElse + : checkIfWithElse, + }; + }, }; diff --git a/lib/rules/no-empty-character-class.js b/lib/rules/no-empty-character-class.js index 5c8410235bcc..3296ad9f39d8 100644 --- a/lib/rules/no-empty-character-class.js +++ b/lib/rules/no-empty-character-class.js @@ -24,53 +24,60 @@ const QUICK_TEST_REGEX = /\[\]/u; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow empty character classes in regular expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-empty-character-class" - }, - - schema: [], - - messages: { - unexpected: "Empty class." - } - }, - - create(context) { - return { - "Literal[regex]"(node) { - const { pattern, flags } = node.regex; - - if (!QUICK_TEST_REGEX.test(pattern)) { - return; - } - - let regExpAST; - - try { - regExpAST = parser.parsePattern(pattern, 0, pattern.length, { - unicode: flags.includes("u"), - unicodeSets: flags.includes("v") - }); - } catch { - - // Ignore regular expressions that regexpp cannot parse - return; - } - - visitRegExpAST(regExpAST, { - onCharacterClassEnter(characterClass) { - if (!characterClass.negate && characterClass.elements.length === 0) { - context.report({ node, messageId: "unexpected" }); - } - } - }); - } - }; - - } + meta: { + type: "problem", + + docs: { + description: + "Disallow empty character classes in regular expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-empty-character-class", + }, + + schema: [], + + messages: { + unexpected: "Empty class.", + }, + }, + + create(context) { + return { + "Literal[regex]"(node) { + const { pattern, flags } = node.regex; + + if (!QUICK_TEST_REGEX.test(pattern)) { + return; + } + + let regExpAST; + + try { + regExpAST = parser.parsePattern( + pattern, + 0, + pattern.length, + { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v"), + }, + ); + } catch { + // Ignore regular expressions that regexpp cannot parse + return; + } + + visitRegExpAST(regExpAST, { + onCharacterClassEnter(characterClass) { + if ( + !characterClass.negate && + characterClass.elements.length === 0 + ) { + context.report({ node, messageId: "unexpected" }); + } + }, + }); + }, + }; + }, }; diff --git a/lib/rules/no-empty-function.js b/lib/rules/no-empty-function.js index 16e611bb138a..c9b66ae13186 100644 --- a/lib/rules/no-empty-function.js +++ b/lib/rules/no-empty-function.js @@ -16,16 +16,16 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const ALLOW_OPTIONS = Object.freeze([ - "functions", - "arrowFunctions", - "generatorFunctions", - "methods", - "generatorMethods", - "getters", - "setters", - "constructors", - "asyncFunctions", - "asyncMethods" + "functions", + "arrowFunctions", + "generatorFunctions", + "methods", + "generatorMethods", + "getters", + "setters", + "constructors", + "asyncFunctions", + "asyncMethods", ]); /** @@ -39,50 +39,48 @@ const ALLOW_OPTIONS = Object.freeze([ * "constructors". */ function getKind(node) { - const parent = node.parent; - let kind; - - if (node.type === "ArrowFunctionExpression") { - return "arrowFunctions"; - } - - // Detects main kind. - if (parent.type === "Property") { - if (parent.kind === "get") { - return "getters"; - } - if (parent.kind === "set") { - return "setters"; - } - kind = parent.method ? "methods" : "functions"; - - } else if (parent.type === "MethodDefinition") { - if (parent.kind === "get") { - return "getters"; - } - if (parent.kind === "set") { - return "setters"; - } - if (parent.kind === "constructor") { - return "constructors"; - } - kind = "methods"; - - } else { - kind = "functions"; - } - - // Detects prefix. - let prefix; - - if (node.generator) { - prefix = "generator"; - } else if (node.async) { - prefix = "async"; - } else { - return kind; - } - return prefix + kind[0].toUpperCase() + kind.slice(1); + const parent = node.parent; + let kind; + + if (node.type === "ArrowFunctionExpression") { + return "arrowFunctions"; + } + + // Detects main kind. + if (parent.type === "Property") { + if (parent.kind === "get") { + return "getters"; + } + if (parent.kind === "set") { + return "setters"; + } + kind = parent.method ? "methods" : "functions"; + } else if (parent.type === "MethodDefinition") { + if (parent.kind === "get") { + return "getters"; + } + if (parent.kind === "set") { + return "setters"; + } + if (parent.kind === "constructor") { + return "constructors"; + } + kind = "methods"; + } else { + kind = "functions"; + } + + // Detects prefix. + let prefix; + + if (node.generator) { + prefix = "generator"; + } else if (node.async) { + prefix = "async"; + } else { + return kind; + } + return prefix + kind[0].toUpperCase() + kind.slice(1); } //------------------------------------------------------------------------------ @@ -91,77 +89,78 @@ function getKind(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ allow: [] }], - - docs: { - description: "Disallow empty functions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-empty-function" - }, - - schema: [ - { - type: "object", - properties: { - allow: { - type: "array", - items: { enum: ALLOW_OPTIONS }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "Unexpected empty {{name}}." - } - }, - - create(context) { - const [{ allow }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports a given function node if the node matches the following patterns. - * - * - Not allowed by options. - * - The body is empty. - * - The body doesn't have any comments. - * @param {ASTNode} node A function node to report. This is one of - * an ArrowFunctionExpression, a FunctionDeclaration, or a - * FunctionExpression. - * @returns {void} - */ - function reportIfEmpty(node) { - const kind = getKind(node); - const name = astUtils.getFunctionNameWithKind(node); - const innerComments = sourceCode.getTokens(node.body, { - includeComments: true, - filter: astUtils.isCommentToken - }); - - if (!allow.includes(kind) && - node.body.type === "BlockStatement" && - node.body.body.length === 0 && - innerComments.length === 0 - ) { - context.report({ - node, - loc: node.body.loc, - messageId: "unexpected", - data: { name } - }); - } - } - - return { - ArrowFunctionExpression: reportIfEmpty, - FunctionDeclaration: reportIfEmpty, - FunctionExpression: reportIfEmpty - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{ allow: [] }], + + docs: { + description: "Disallow empty functions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-empty-function", + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { enum: ALLOW_OPTIONS }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: "Unexpected empty {{name}}.", + }, + }, + + create(context) { + const [{ allow }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports a given function node if the node matches the following patterns. + * + * - Not allowed by options. + * - The body is empty. + * - The body doesn't have any comments. + * @param {ASTNode} node A function node to report. This is one of + * an ArrowFunctionExpression, a FunctionDeclaration, or a + * FunctionExpression. + * @returns {void} + */ + function reportIfEmpty(node) { + const kind = getKind(node); + const name = astUtils.getFunctionNameWithKind(node); + const innerComments = sourceCode.getTokens(node.body, { + includeComments: true, + filter: astUtils.isCommentToken, + }); + + if ( + !allow.includes(kind) && + node.body.type === "BlockStatement" && + node.body.body.length === 0 && + innerComments.length === 0 + ) { + context.report({ + node, + loc: node.body.loc, + messageId: "unexpected", + data: { name }, + }); + } + } + + return { + ArrowFunctionExpression: reportIfEmpty, + FunctionDeclaration: reportIfEmpty, + FunctionExpression: reportIfEmpty, + }; + }, }; diff --git a/lib/rules/no-empty-pattern.js b/lib/rules/no-empty-pattern.js index eb81d8ab4c0d..7bb53fbf3f39 100644 --- a/lib/rules/no-empty-pattern.js +++ b/lib/rules/no-empty-pattern.js @@ -12,69 +12,74 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - defaultOptions: [{ - allowObjectPatternsAsParameters: false - }], + defaultOptions: [ + { + allowObjectPatternsAsParameters: false, + }, + ], - docs: { - description: "Disallow empty destructuring patterns", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-empty-pattern" - }, + docs: { + description: "Disallow empty destructuring patterns", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-empty-pattern", + }, - schema: [ - { - type: "object", - properties: { - allowObjectPatternsAsParameters: { - type: "boolean" - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + allowObjectPatternsAsParameters: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], - messages: { - unexpected: "Unexpected empty {{type}} pattern." - } - }, + messages: { + unexpected: "Unexpected empty {{type}} pattern.", + }, + }, - create(context) { - const [{ allowObjectPatternsAsParameters }] = context.options; + create(context) { + const [{ allowObjectPatternsAsParameters }] = context.options; - return { - ObjectPattern(node) { + return { + ObjectPattern(node) { + if (node.properties.length > 0) { + return; + } - if (node.properties.length > 0) { - return; - } + // Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true + if ( + allowObjectPatternsAsParameters && + (astUtils.isFunction(node.parent) || + (node.parent.type === "AssignmentPattern" && + astUtils.isFunction(node.parent.parent) && + node.parent.right.type === "ObjectExpression" && + node.parent.right.properties.length === 0)) + ) { + return; + } - // Allow {} and {} = {} empty object patterns as parameters when allowObjectPatternsAsParameters is true - if ( - allowObjectPatternsAsParameters && - ( - astUtils.isFunction(node.parent) || - ( - node.parent.type === "AssignmentPattern" && - astUtils.isFunction(node.parent.parent) && - node.parent.right.type === "ObjectExpression" && - node.parent.right.properties.length === 0 - ) - ) - ) { - return; - } - - context.report({ node, messageId: "unexpected", data: { type: "object" } }); - }, - ArrayPattern(node) { - if (node.elements.length === 0) { - context.report({ node, messageId: "unexpected", data: { type: "array" } }); - } - } - }; - } + context.report({ + node, + messageId: "unexpected", + data: { type: "object" }, + }); + }, + ArrayPattern(node) { + if (node.elements.length === 0) { + context.report({ + node, + messageId: "unexpected", + data: { type: "array" }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-empty-static-block.js b/lib/rules/no-empty-static-block.js index 558c4fad4552..cbb7e8ec24df 100644 --- a/lib/rules/no-empty-static-block.js +++ b/lib/rules/no-empty-static-block.js @@ -10,38 +10,40 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow empty static blocks", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-empty-static-block" - }, - - schema: [], - - messages: { - unexpected: "Unexpected empty static block." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - StaticBlock(node) { - if (node.body.length === 0) { - const closingBrace = sourceCode.getLastToken(node); - - if (sourceCode.getCommentsBefore(closingBrace).length === 0) { - context.report({ - node, - messageId: "unexpected" - }); - } - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow empty static blocks", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-empty-static-block", + }, + + schema: [], + + messages: { + unexpected: "Unexpected empty static block.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + StaticBlock(node) { + if (node.body.length === 0) { + const closingBrace = sourceCode.getLastToken(node); + + if ( + sourceCode.getCommentsBefore(closingBrace).length === 0 + ) { + context.report({ + node, + messageId: "unexpected", + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-empty.js b/lib/rules/no-empty.js index b5df621ce401..dea102a9ba9f 100644 --- a/lib/rules/no-empty.js +++ b/lib/rules/no-empty.js @@ -16,89 +16,101 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - hasSuggestions: true, - type: "suggestion", - - defaultOptions: [{ - allowEmptyCatch: false - }], - - docs: { - description: "Disallow empty block statements", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-empty" - }, - - schema: [ - { - type: "object", - properties: { - allowEmptyCatch: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "Empty {{type}} statement.", - suggestComment: "Add comment inside empty {{type}} statement." - } - }, - - create(context) { - const [{ allowEmptyCatch }] = context.options; - const sourceCode = context.sourceCode; - - return { - BlockStatement(node) { - - // if the body is not empty, we can just return immediately - if (node.body.length !== 0) { - return; - } - - // a function is generally allowed to be empty - if (astUtils.isFunction(node.parent)) { - return; - } - - if (allowEmptyCatch && node.parent.type === "CatchClause") { - return; - } - - // any other block is only allowed to be empty, if it contains a comment - if (sourceCode.getCommentsInside(node).length > 0) { - return; - } - - context.report({ - node, - messageId: "unexpected", - data: { type: "block" }, - suggest: [ - { - messageId: "suggestComment", - data: { type: "block" }, - fix(fixer) { - const range = [node.range[0] + 1, node.range[1] - 1]; - - return fixer.replaceTextRange(range, " /* empty */ "); - } - } - ] - }); - }, - - SwitchStatement(node) { - - if (typeof node.cases === "undefined" || node.cases.length === 0) { - context.report({ node, messageId: "unexpected", data: { type: "switch" } }); - } - } - }; - - } + meta: { + hasSuggestions: true, + type: "suggestion", + + defaultOptions: [ + { + allowEmptyCatch: false, + }, + ], + + docs: { + description: "Disallow empty block statements", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-empty", + }, + + schema: [ + { + type: "object", + properties: { + allowEmptyCatch: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: "Empty {{type}} statement.", + suggestComment: "Add comment inside empty {{type}} statement.", + }, + }, + + create(context) { + const [{ allowEmptyCatch }] = context.options; + const sourceCode = context.sourceCode; + + return { + BlockStatement(node) { + // if the body is not empty, we can just return immediately + if (node.body.length !== 0) { + return; + } + + // a function is generally allowed to be empty + if (astUtils.isFunction(node.parent)) { + return; + } + + if (allowEmptyCatch && node.parent.type === "CatchClause") { + return; + } + + // any other block is only allowed to be empty, if it contains a comment + if (sourceCode.getCommentsInside(node).length > 0) { + return; + } + + context.report({ + node, + messageId: "unexpected", + data: { type: "block" }, + suggest: [ + { + messageId: "suggestComment", + data: { type: "block" }, + fix(fixer) { + const range = [ + node.range[0] + 1, + node.range[1] - 1, + ]; + + return fixer.replaceTextRange( + range, + " /* empty */ ", + ); + }, + }, + ], + }); + }, + + SwitchStatement(node) { + if ( + typeof node.cases === "undefined" || + node.cases.length === 0 + ) { + context.report({ + node, + messageId: "unexpected", + data: { type: "switch" }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-eq-null.js b/lib/rules/no-eq-null.js index 9252907b6b69..d6fd48dd5717 100644 --- a/lib/rules/no-eq-null.js +++ b/lib/rules/no-eq-null.js @@ -12,35 +12,40 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `null` comparisons without type-checking operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-eq-null" - }, - - schema: [], - - messages: { - unexpected: "Use '===' to compare with null." - } - }, - - create(context) { - - return { - - BinaryExpression(node) { - const badOperator = node.operator === "==" || node.operator === "!="; - - if (node.right.type === "Literal" && node.right.raw === "null" && badOperator || - node.left.type === "Literal" && node.left.raw === "null" && badOperator) { - context.report({ node, messageId: "unexpected" }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow `null` comparisons without type-checking operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-eq-null", + }, + + schema: [], + + messages: { + unexpected: "Use '===' to compare with null.", + }, + }, + + create(context) { + return { + BinaryExpression(node) { + const badOperator = + node.operator === "==" || node.operator === "!="; + + if ( + (node.right.type === "Literal" && + node.right.raw === "null" && + badOperator) || + (node.left.type === "Literal" && + node.left.raw === "null" && + badOperator) + ) { + context.report({ node, messageId: "unexpected" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-eval.js b/lib/rules/no-eval.js index bb35d4e097e5..dce744877338 100644 --- a/lib/rules/no-eval.js +++ b/lib/rules/no-eval.js @@ -16,9 +16,9 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const candidatesOfGlobalObject = Object.freeze([ - "global", - "window", - "globalThis" + "global", + "window", + "globalThis", ]); /** @@ -30,7 +30,7 @@ const candidatesOfGlobalObject = Object.freeze([ * the specified name's property */ function isMember(node, name) { - return astUtils.isSpecificMemberAccess(node, null, name); + return astUtils.isSpecificMemberAccess(node, null, name); } //------------------------------------------------------------------------------ @@ -39,249 +39,255 @@ function isMember(node, name) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - allowIndirect: false - }], - - docs: { - description: "Disallow the use of `eval()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-eval" - }, - - schema: [ - { - type: "object", - properties: { - allowIndirect: { type: "boolean" } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "eval can be harmful." - } - }, - - create(context) { - const [{ allowIndirect }] = context.options; - const sourceCode = context.sourceCode; - let funcInfo = null; - - /** - * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. - * Top-level scopes are handled separately. - * - * This is used in order to check whether or not `this` binding is a - * reference to the global object. - * @param {ASTNode} node A node of the scope. - * For functions, this is one of FunctionDeclaration, FunctionExpression. - * For class static blocks, this is StaticBlock. - * For class field initializers, this can be any node that is PropertyDefinition#value. - * @returns {void} - */ - function enterThisScope(node) { - const strict = sourceCode.getScope(node).isStrict; - - funcInfo = { - upper: funcInfo, - node, - strict, - isTopLevelOfScript: false, - defaultThis: false, - initialized: strict - }; - } - - /** - * Pops a variable scope from the stack. - * @returns {void} - */ - function exitThisScope() { - funcInfo = funcInfo.upper; - } - - /** - * Reports a given node. - * - * `node` is `Identifier` or `MemberExpression`. - * The parent of `node` might be `CallExpression`. - * - * The location of the report is always `eval` `Identifier` (or possibly - * `Literal`). The type of the report is `CallExpression` if the parent is - * `CallExpression`. Otherwise, it's the given node type. - * @param {ASTNode} node A node to report. - * @returns {void} - */ - function report(node) { - const parent = node.parent; - const locationNode = node.type === "MemberExpression" - ? node.property - : node; - - const reportNode = parent.type === "CallExpression" && parent.callee === node - ? parent - : node; - - context.report({ - node: reportNode, - loc: locationNode.loc, - messageId: "unexpected" - }); - } - - /** - * Reports accesses of `eval` via the global object. - * @param {eslint-scope.Scope} globalScope The global scope. - * @returns {void} - */ - function reportAccessingEvalViaGlobalObject(globalScope) { - for (let i = 0; i < candidatesOfGlobalObject.length; ++i) { - const name = candidatesOfGlobalObject[i]; - const variable = astUtils.getVariableByName(globalScope, name); - - if (!variable) { - continue; - } - - const references = variable.references; - - for (let j = 0; j < references.length; ++j) { - const identifier = references[j].identifier; - let node = identifier.parent; - - // To detect code like `window.window.eval`. - while (isMember(node, name)) { - node = node.parent; - } - - // Reports. - if (isMember(node, "eval")) { - report(node); - } - } - } - } - - /** - * Reports all accesses of `eval` (excludes direct calls to eval). - * @param {eslint-scope.Scope} globalScope The global scope. - * @returns {void} - */ - function reportAccessingEval(globalScope) { - const variable = astUtils.getVariableByName(globalScope, "eval"); - - if (!variable) { - return; - } - - const references = variable.references; - - for (let i = 0; i < references.length; ++i) { - const reference = references[i]; - const id = reference.identifier; - - if (id.name === "eval" && !astUtils.isCallee(id)) { - - // Is accessing to eval (excludes direct calls to eval) - report(id); - } - } - } - - if (allowIndirect) { - - // Checks only direct calls to eval. It's simple! - return { - "CallExpression:exit"(node) { - const callee = node.callee; - - /* - * Optional call (`eval?.("code")`) is not direct eval. - * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation - * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation - */ - if (!node.optional && astUtils.isSpecificId(callee, "eval")) { - report(callee); - } - } - }; - } - - return { - "CallExpression:exit"(node) { - const callee = node.callee; - - if (astUtils.isSpecificId(callee, "eval")) { - report(callee); - } - }, - - Program(node) { - const scope = sourceCode.getScope(node), - features = context.parserOptions.ecmaFeatures || {}, - strict = - scope.isStrict || - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict), - isTopLevelOfScript = node.sourceType !== "module" && !features.globalReturn; - - funcInfo = { - upper: null, - node, - strict, - isTopLevelOfScript, - defaultThis: true, - initialized: true - }; - }, - - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - - exitThisScope(); - reportAccessingEval(globalScope); - reportAccessingEvalViaGlobalObject(globalScope); - }, - - FunctionDeclaration: enterThisScope, - "FunctionDeclaration:exit": exitThisScope, - FunctionExpression: enterThisScope, - "FunctionExpression:exit": exitThisScope, - "PropertyDefinition > *.value": enterThisScope, - "PropertyDefinition > *.value:exit": exitThisScope, - StaticBlock: enterThisScope, - "StaticBlock:exit": exitThisScope, - - ThisExpression(node) { - if (!isMember(node.parent, "eval")) { - return; - } - - /* - * `this.eval` is found. - * Checks whether or not the value of `this` is the global object. - */ - if (!funcInfo.initialized) { - funcInfo.initialized = true; - funcInfo.defaultThis = astUtils.isDefaultThisBinding( - funcInfo.node, - sourceCode - ); - } - - // `this` at the top level of scripts always refers to the global object - if (funcInfo.isTopLevelOfScript || (!funcInfo.strict && funcInfo.defaultThis)) { - - // `this.eval` is possible built-in `eval`. - report(node.parent); - } - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + allowIndirect: false, + }, + ], + + docs: { + description: "Disallow the use of `eval()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-eval", + }, + + schema: [ + { + type: "object", + properties: { + allowIndirect: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: "eval can be harmful.", + }, + }, + + create(context) { + const [{ allowIndirect }] = context.options; + const sourceCode = context.sourceCode; + let funcInfo = null; + + /** + * Pushes a `this` scope (non-arrow function, class static block, or class field initializer) information to the stack. + * Top-level scopes are handled separately. + * + * This is used in order to check whether or not `this` binding is a + * reference to the global object. + * @param {ASTNode} node A node of the scope. + * For functions, this is one of FunctionDeclaration, FunctionExpression. + * For class static blocks, this is StaticBlock. + * For class field initializers, this can be any node that is PropertyDefinition#value. + * @returns {void} + */ + function enterThisScope(node) { + const strict = sourceCode.getScope(node).isStrict; + + funcInfo = { + upper: funcInfo, + node, + strict, + isTopLevelOfScript: false, + defaultThis: false, + initialized: strict, + }; + } + + /** + * Pops a variable scope from the stack. + * @returns {void} + */ + function exitThisScope() { + funcInfo = funcInfo.upper; + } + + /** + * Reports a given node. + * + * `node` is `Identifier` or `MemberExpression`. + * The parent of `node` might be `CallExpression`. + * + * The location of the report is always `eval` `Identifier` (or possibly + * `Literal`). The type of the report is `CallExpression` if the parent is + * `CallExpression`. Otherwise, it's the given node type. + * @param {ASTNode} node A node to report. + * @returns {void} + */ + function report(node) { + const parent = node.parent; + const locationNode = + node.type === "MemberExpression" ? node.property : node; + + const reportNode = + parent.type === "CallExpression" && parent.callee === node + ? parent + : node; + + context.report({ + node: reportNode, + loc: locationNode.loc, + messageId: "unexpected", + }); + } + + /** + * Reports accesses of `eval` via the global object. + * @param {eslint-scope.Scope} globalScope The global scope. + * @returns {void} + */ + function reportAccessingEvalViaGlobalObject(globalScope) { + for (let i = 0; i < candidatesOfGlobalObject.length; ++i) { + const name = candidatesOfGlobalObject[i]; + const variable = astUtils.getVariableByName(globalScope, name); + + if (!variable) { + continue; + } + + const references = variable.references; + + for (let j = 0; j < references.length; ++j) { + const identifier = references[j].identifier; + let node = identifier.parent; + + // To detect code like `window.window.eval`. + while (isMember(node, name)) { + node = node.parent; + } + + // Reports. + if (isMember(node, "eval")) { + report(node); + } + } + } + } + + /** + * Reports all accesses of `eval` (excludes direct calls to eval). + * @param {eslint-scope.Scope} globalScope The global scope. + * @returns {void} + */ + function reportAccessingEval(globalScope) { + const variable = astUtils.getVariableByName(globalScope, "eval"); + + if (!variable) { + return; + } + + const references = variable.references; + + for (let i = 0; i < references.length; ++i) { + const reference = references[i]; + const id = reference.identifier; + + if (id.name === "eval" && !astUtils.isCallee(id)) { + // Is accessing to eval (excludes direct calls to eval) + report(id); + } + } + } + + if (allowIndirect) { + // Checks only direct calls to eval. It's simple! + return { + "CallExpression:exit"(node) { + const callee = node.callee; + + /* + * Optional call (`eval?.("code")`) is not direct eval. + * The direct eval is only step 6.a.vi of https://tc39.es/ecma262/#sec-function-calls-runtime-semantics-evaluation + * But the optional call is https://tc39.es/ecma262/#sec-optional-chaining-chain-evaluation + */ + if ( + !node.optional && + astUtils.isSpecificId(callee, "eval") + ) { + report(callee); + } + }, + }; + } + + return { + "CallExpression:exit"(node) { + const callee = node.callee; + + if (astUtils.isSpecificId(callee, "eval")) { + report(callee); + } + }, + + Program(node) { + const scope = sourceCode.getScope(node), + features = context.parserOptions.ecmaFeatures || {}, + strict = + scope.isStrict || + node.sourceType === "module" || + (features.globalReturn && + scope.childScopes[0].isStrict), + isTopLevelOfScript = + node.sourceType !== "module" && !features.globalReturn; + + funcInfo = { + upper: null, + node, + strict, + isTopLevelOfScript, + defaultThis: true, + initialized: true, + }; + }, + + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + exitThisScope(); + reportAccessingEval(globalScope); + reportAccessingEvalViaGlobalObject(globalScope); + }, + + FunctionDeclaration: enterThisScope, + "FunctionDeclaration:exit": exitThisScope, + FunctionExpression: enterThisScope, + "FunctionExpression:exit": exitThisScope, + "PropertyDefinition > *.value": enterThisScope, + "PropertyDefinition > *.value:exit": exitThisScope, + StaticBlock: enterThisScope, + "StaticBlock:exit": exitThisScope, + + ThisExpression(node) { + if (!isMember(node.parent, "eval")) { + return; + } + + /* + * `this.eval` is found. + * Checks whether or not the value of `this` is the global object. + */ + if (!funcInfo.initialized) { + funcInfo.initialized = true; + funcInfo.defaultThis = astUtils.isDefaultThisBinding( + funcInfo.node, + sourceCode, + ); + } + + // `this` at the top level of scripts always refers to the global object + if ( + funcInfo.isTopLevelOfScript || + (!funcInfo.strict && funcInfo.defaultThis) + ) { + // `this.eval` is possible built-in `eval`. + report(node.parent); + } + }, + }; + }, }; diff --git a/lib/rules/no-ex-assign.js b/lib/rules/no-ex-assign.js index d0e9febae041..c49d278e7bf2 100644 --- a/lib/rules/no-ex-assign.js +++ b/lib/rules/no-ex-assign.js @@ -13,42 +13,45 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow reassigning exceptions in `catch` clauses", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-ex-assign" - }, - - schema: [], - - messages: { - unexpected: "Do not assign to the exception parameter." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - astUtils.getModifyingReferences(variable.references).forEach(reference => { - context.report({ node: reference.identifier, messageId: "unexpected" }); - }); - } - - return { - CatchClause(node) { - sourceCode.getDeclaredVariables(node).forEach(checkVariable); - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow reassigning exceptions in `catch` clauses", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-ex-assign", + }, + + schema: [], + + messages: { + unexpected: "Do not assign to the exception parameter.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + astUtils + .getModifyingReferences(variable.references) + .forEach(reference => { + context.report({ + node: reference.identifier, + messageId: "unexpected", + }); + }); + } + + return { + CatchClause(node) { + sourceCode.getDeclaredVariables(node).forEach(checkVariable); + }, + }; + }, }; diff --git a/lib/rules/no-extend-native.js b/lib/rules/no-extend-native.js index 7164c091c9f1..6b275543896d 100644 --- a/lib/rules/no-extend-native.js +++ b/lib/rules/no-extend-native.js @@ -17,162 +17,164 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ exceptions: [] }], - - docs: { - description: "Disallow extending native types", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-extend-native" - }, - - schema: [ - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpected: "{{builtin}} prototype is read only, properties should not be added." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const exceptions = new Set(context.options[0].exceptions); - const modifiedBuiltins = new Set( - Object.keys(astUtils.ECMASCRIPT_GLOBALS) - .filter(builtin => builtin[0].toUpperCase() === builtin[0]) - .filter(builtin => !exceptions.has(builtin)) - ); - - /** - * Reports a lint error for the given node. - * @param {ASTNode} node The node to report. - * @param {string} builtin The name of the native builtin being extended. - * @returns {void} - */ - function reportNode(node, builtin) { - context.report({ - node, - messageId: "unexpected", - data: { - builtin - } - }); - } - - /** - * Check to see if the `prototype` property of the given object - * identifier node is being accessed. - * @param {ASTNode} identifierNode The Identifier representing the object - * to check. - * @returns {boolean} True if the identifier is the object of a - * MemberExpression and its `prototype` property is being accessed, - * false otherwise. - */ - function isPrototypePropertyAccessed(identifierNode) { - return Boolean( - identifierNode && - identifierNode.parent && - identifierNode.parent.type === "MemberExpression" && - identifierNode.parent.object === identifierNode && - astUtils.getStaticPropertyName(identifierNode.parent) === "prototype" - ); - } - - /** - * Check if it's an assignment to the property of the given node. - * Example: `*.prop = 0` // the `*` is the given node. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if an assignment to the property of the node. - */ - function isAssigningToPropertyOf(node) { - return ( - node.parent.type === "MemberExpression" && - node.parent.object === node && - node.parent.parent.type === "AssignmentExpression" && - node.parent.parent.left === node.parent - ); - } - - /** - * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`. - */ - function isInDefinePropertyCall(node) { - return ( - node.parent.type === "CallExpression" && - node.parent.arguments[0] === node && - astUtils.isSpecificMemberAccess(node.parent.callee, "Object", /^definePropert(?:y|ies)$/u) - ); - } - - /** - * Check to see if object prototype access is part of a prototype - * extension. There are three ways a prototype can be extended: - * 1. Assignment to prototype property (Object.prototype.foo = 1) - * 2. Object.defineProperty()/Object.defineProperties() on a prototype - * If prototype extension is detected, report the AssignmentExpression - * or CallExpression node. - * @param {ASTNode} identifierNode The Identifier representing the object - * which prototype is being accessed and possibly extended. - * @returns {void} - */ - function checkAndReportPrototypeExtension(identifierNode) { - if (!isPrototypePropertyAccessed(identifierNode)) { - return; // This is not `*.prototype` access. - } - - /* - * `identifierNode.parent` is a MemberExpression `*.prototype`. - * If it's an optional member access, it may be wrapped by a `ChainExpression` node. - */ - const prototypeNode = - identifierNode.parent.parent.type === "ChainExpression" - ? identifierNode.parent.parent - : identifierNode.parent; - - if (isAssigningToPropertyOf(prototypeNode)) { - - // `*.prototype` -> MemberExpression -> AssignmentExpression - reportNode(prototypeNode.parent.parent, identifierNode.name); - } else if (isInDefinePropertyCall(prototypeNode)) { - - // `*.prototype` -> CallExpression - reportNode(prototypeNode.parent, identifierNode.name); - } - } - - return { - - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - - modifiedBuiltins.forEach(builtin => { - const builtinVar = globalScope.set.get(builtin); - - if (builtinVar && builtinVar.references) { - builtinVar.references - .map(ref => ref.identifier) - .forEach(checkAndReportPrototypeExtension); - } - }); - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [{ exceptions: [] }], + + docs: { + description: "Disallow extending native types", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-extend-native", + }, + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpected: + "{{builtin}} prototype is read only, properties should not be added.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const exceptions = new Set(context.options[0].exceptions); + const modifiedBuiltins = new Set( + Object.keys(astUtils.ECMASCRIPT_GLOBALS) + .filter(builtin => builtin[0].toUpperCase() === builtin[0]) + .filter(builtin => !exceptions.has(builtin)), + ); + + /** + * Reports a lint error for the given node. + * @param {ASTNode} node The node to report. + * @param {string} builtin The name of the native builtin being extended. + * @returns {void} + */ + function reportNode(node, builtin) { + context.report({ + node, + messageId: "unexpected", + data: { + builtin, + }, + }); + } + + /** + * Check to see if the `prototype` property of the given object + * identifier node is being accessed. + * @param {ASTNode} identifierNode The Identifier representing the object + * to check. + * @returns {boolean} True if the identifier is the object of a + * MemberExpression and its `prototype` property is being accessed, + * false otherwise. + */ + function isPrototypePropertyAccessed(identifierNode) { + return Boolean( + identifierNode && + identifierNode.parent && + identifierNode.parent.type === "MemberExpression" && + identifierNode.parent.object === identifierNode && + astUtils.getStaticPropertyName(identifierNode.parent) === + "prototype", + ); + } + + /** + * Check if it's an assignment to the property of the given node. + * Example: `*.prop = 0` // the `*` is the given node. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if an assignment to the property of the node. + */ + function isAssigningToPropertyOf(node) { + return ( + node.parent.type === "MemberExpression" && + node.parent.object === node && + node.parent.parent.type === "AssignmentExpression" && + node.parent.parent.left === node.parent + ); + } + + /** + * Checks if the given node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is at the first argument of the method call of `Object.defineProperty()` or `Object.defineProperties()`. + */ + function isInDefinePropertyCall(node) { + return ( + node.parent.type === "CallExpression" && + node.parent.arguments[0] === node && + astUtils.isSpecificMemberAccess( + node.parent.callee, + "Object", + /^definePropert(?:y|ies)$/u, + ) + ); + } + + /** + * Check to see if object prototype access is part of a prototype + * extension. There are three ways a prototype can be extended: + * 1. Assignment to prototype property (Object.prototype.foo = 1) + * 2. Object.defineProperty()/Object.defineProperties() on a prototype + * If prototype extension is detected, report the AssignmentExpression + * or CallExpression node. + * @param {ASTNode} identifierNode The Identifier representing the object + * which prototype is being accessed and possibly extended. + * @returns {void} + */ + function checkAndReportPrototypeExtension(identifierNode) { + if (!isPrototypePropertyAccessed(identifierNode)) { + return; // This is not `*.prototype` access. + } + + /* + * `identifierNode.parent` is a MemberExpression `*.prototype`. + * If it's an optional member access, it may be wrapped by a `ChainExpression` node. + */ + const prototypeNode = + identifierNode.parent.parent.type === "ChainExpression" + ? identifierNode.parent.parent + : identifierNode.parent; + + if (isAssigningToPropertyOf(prototypeNode)) { + // `*.prototype` -> MemberExpression -> AssignmentExpression + reportNode(prototypeNode.parent.parent, identifierNode.name); + } else if (isInDefinePropertyCall(prototypeNode)) { + // `*.prototype` -> CallExpression + reportNode(prototypeNode.parent, identifierNode.name); + } + } + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + modifiedBuiltins.forEach(builtin => { + const builtinVar = globalScope.set.get(builtin); + + if (builtinVar && builtinVar.references) { + builtinVar.references + .map(ref => ref.identifier) + .forEach(checkAndReportPrototypeExtension); + } + }); + }, + }; + }, }; diff --git a/lib/rules/no-extra-bind.js b/lib/rules/no-extra-bind.js index e1e72b0c7452..f5bc557d302c 100644 --- a/lib/rules/no-extra-bind.js +++ b/lib/rules/no-extra-bind.js @@ -14,7 +14,12 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]); +const SIDE_EFFECT_FREE_NODE_TYPES = new Set([ + "Literal", + "Identifier", + "ThisExpression", + "FunctionExpression", +]); //------------------------------------------------------------------------------ // Rule Definition @@ -22,192 +27,198 @@ const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpre /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary calls to `.bind()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-extra-bind" - }, - - schema: [], - fixable: "code", - - messages: { - unexpected: "The function binding is unnecessary." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - let scopeInfo = null; - - /** - * Checks if a node is free of side effects. - * - * This check is stricter than it needs to be, in order to keep the implementation simple. - * @param {ASTNode} node A node to check. - * @returns {boolean} True if the node is known to be side-effect free, false otherwise. - */ - function isSideEffectFree(node) { - return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type); - } - - /** - * Reports a given function node. - * @param {ASTNode} node A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {void} - */ - function report(node) { - const memberNode = node.parent; - const callNode = memberNode.parent.type === "ChainExpression" - ? memberNode.parent.parent - : memberNode.parent; - - context.report({ - node: callNode, - messageId: "unexpected", - loc: memberNode.property.loc, - - fix(fixer) { - if (!isSideEffectFree(callNode.arguments[0])) { - return null; - } - - /* - * The list of the first/last token pair of a removal range. - * This is two parts because closing parentheses may exist between the method name and arguments. - * E.g. `(function(){}.bind ) (obj)` - * ^^^^^ ^^^^^ < removal ranges - * E.g. `(function(){}?.['bind'] ) ?.(obj)` - * ^^^^^^^^^^ ^^^^^^^ < removal ranges - */ - const tokenPairs = [ - [ - - // `.`, `?.`, or `[` token. - sourceCode.getTokenAfter( - memberNode.object, - astUtils.isNotClosingParenToken - ), - - // property name or `]` token. - sourceCode.getLastToken(memberNode) - ], - [ - - // `?.` or `(` token of arguments. - sourceCode.getTokenAfter( - memberNode, - astUtils.isNotClosingParenToken - ), - - // `)` token of arguments. - sourceCode.getLastToken(callNode) - ] - ]; - const firstTokenToRemove = tokenPairs[0][0]; - const lastTokenToRemove = tokenPairs[1][1]; - - if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) { - return null; - } - - return tokenPairs.map(([start, end]) => - fixer.removeRange([start.range[0], end.range[1]])); - } - }); - } - - /** - * Checks whether or not a given function node is the callee of `.bind()` - * method. - * - * e.g. `(function() {}.bind(foo))` - * @param {ASTNode} node A node to report. This is a FunctionExpression or - * an ArrowFunctionExpression. - * @returns {boolean} `true` if the node is the callee of `.bind()` method. - */ - function isCalleeOfBindMethod(node) { - if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) { - return false; - } - - // The node of `*.bind` member access. - const bindNode = node.parent.parent.type === "ChainExpression" - ? node.parent.parent - : node.parent; - - return ( - bindNode.parent.type === "CallExpression" && - bindNode.parent.callee === bindNode && - bindNode.parent.arguments.length === 1 && - bindNode.parent.arguments[0].type !== "SpreadElement" - ); - } - - /** - * Adds a scope information object to the stack. - * @param {ASTNode} node A node to add. This node is a FunctionExpression - * or a FunctionDeclaration node. - * @returns {void} - */ - function enterFunction(node) { - scopeInfo = { - isBound: isCalleeOfBindMethod(node), - thisFound: false, - upper: scopeInfo - }; - } - - /** - * Removes the scope information object from the top of the stack. - * At the same time, this reports the function node if the function has - * `.bind()` and the `this` keywords found. - * @param {ASTNode} node A node to remove. This node is a - * FunctionExpression or a FunctionDeclaration node. - * @returns {void} - */ - function exitFunction(node) { - if (scopeInfo.isBound && !scopeInfo.thisFound) { - report(node); - } - - scopeInfo = scopeInfo.upper; - } - - /** - * Reports a given arrow function if the function is callee of `.bind()` - * method. - * @param {ASTNode} node A node to report. This node is an - * ArrowFunctionExpression. - * @returns {void} - */ - function exitArrowFunction(node) { - if (isCalleeOfBindMethod(node)) { - report(node); - } - } - - /** - * Set the mark as the `this` keyword was found in this scope. - * @returns {void} - */ - function markAsThisFound() { - if (scopeInfo) { - scopeInfo.thisFound = true; - } - } - - return { - "ArrowFunctionExpression:exit": exitArrowFunction, - FunctionDeclaration: enterFunction, - "FunctionDeclaration:exit": exitFunction, - FunctionExpression: enterFunction, - "FunctionExpression:exit": exitFunction, - ThisExpression: markAsThisFound - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unnecessary calls to `.bind()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-extra-bind", + }, + + schema: [], + fixable: "code", + + messages: { + unexpected: "The function binding is unnecessary.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + let scopeInfo = null; + + /** + * Checks if a node is free of side effects. + * + * This check is stricter than it needs to be, in order to keep the implementation simple. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is known to be side-effect free, false otherwise. + */ + function isSideEffectFree(node) { + return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type); + } + + /** + * Reports a given function node. + * @param {ASTNode} node A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {void} + */ + function report(node) { + const memberNode = node.parent; + const callNode = + memberNode.parent.type === "ChainExpression" + ? memberNode.parent.parent + : memberNode.parent; + + context.report({ + node: callNode, + messageId: "unexpected", + loc: memberNode.property.loc, + + fix(fixer) { + if (!isSideEffectFree(callNode.arguments[0])) { + return null; + } + + /* + * The list of the first/last token pair of a removal range. + * This is two parts because closing parentheses may exist between the method name and arguments. + * E.g. `(function(){}.bind ) (obj)` + * ^^^^^ ^^^^^ < removal ranges + * E.g. `(function(){}?.['bind'] ) ?.(obj)` + * ^^^^^^^^^^ ^^^^^^^ < removal ranges + */ + const tokenPairs = [ + [ + // `.`, `?.`, or `[` token. + sourceCode.getTokenAfter( + memberNode.object, + astUtils.isNotClosingParenToken, + ), + + // property name or `]` token. + sourceCode.getLastToken(memberNode), + ], + [ + // `?.` or `(` token of arguments. + sourceCode.getTokenAfter( + memberNode, + astUtils.isNotClosingParenToken, + ), + + // `)` token of arguments. + sourceCode.getLastToken(callNode), + ], + ]; + const firstTokenToRemove = tokenPairs[0][0]; + const lastTokenToRemove = tokenPairs[1][1]; + + if ( + sourceCode.commentsExistBetween( + firstTokenToRemove, + lastTokenToRemove, + ) + ) { + return null; + } + + return tokenPairs.map(([start, end]) => + fixer.removeRange([start.range[0], end.range[1]]), + ); + }, + }); + } + + /** + * Checks whether or not a given function node is the callee of `.bind()` + * method. + * + * e.g. `(function() {}.bind(foo))` + * @param {ASTNode} node A node to report. This is a FunctionExpression or + * an ArrowFunctionExpression. + * @returns {boolean} `true` if the node is the callee of `.bind()` method. + */ + function isCalleeOfBindMethod(node) { + if (!astUtils.isSpecificMemberAccess(node.parent, null, "bind")) { + return false; + } + + // The node of `*.bind` member access. + const bindNode = + node.parent.parent.type === "ChainExpression" + ? node.parent.parent + : node.parent; + + return ( + bindNode.parent.type === "CallExpression" && + bindNode.parent.callee === bindNode && + bindNode.parent.arguments.length === 1 && + bindNode.parent.arguments[0].type !== "SpreadElement" + ); + } + + /** + * Adds a scope information object to the stack. + * @param {ASTNode} node A node to add. This node is a FunctionExpression + * or a FunctionDeclaration node. + * @returns {void} + */ + function enterFunction(node) { + scopeInfo = { + isBound: isCalleeOfBindMethod(node), + thisFound: false, + upper: scopeInfo, + }; + } + + /** + * Removes the scope information object from the top of the stack. + * At the same time, this reports the function node if the function has + * `.bind()` and the `this` keywords found. + * @param {ASTNode} node A node to remove. This node is a + * FunctionExpression or a FunctionDeclaration node. + * @returns {void} + */ + function exitFunction(node) { + if (scopeInfo.isBound && !scopeInfo.thisFound) { + report(node); + } + + scopeInfo = scopeInfo.upper; + } + + /** + * Reports a given arrow function if the function is callee of `.bind()` + * method. + * @param {ASTNode} node A node to report. This node is an + * ArrowFunctionExpression. + * @returns {void} + */ + function exitArrowFunction(node) { + if (isCalleeOfBindMethod(node)) { + report(node); + } + } + + /** + * Set the mark as the `this` keyword was found in this scope. + * @returns {void} + */ + function markAsThisFound() { + if (scopeInfo) { + scopeInfo.thisFound = true; + } + } + + return { + "ArrowFunctionExpression:exit": exitArrowFunction, + FunctionDeclaration: enterFunction, + "FunctionDeclaration:exit": exitFunction, + FunctionExpression: enterFunction, + "FunctionExpression:exit": exitFunction, + ThisExpression: markAsThisFound, + }; + }, }; diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js index 63450c310b7e..6a844a85ecb2 100644 --- a/lib/rules/no-extra-boolean-cast.js +++ b/lib/rules/no-extra-boolean-cast.js @@ -20,351 +20,401 @@ const precedence = astUtils.getPrecedence; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{}], - - docs: { - description: "Disallow unnecessary boolean casts", - recommended: true, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-extra-boolean-cast" - }, - - schema: [{ - anyOf: [ - { - type: "object", - properties: { - enforceForInnerExpressions: { - type: "boolean" - } - }, - additionalProperties: false - }, - - // deprecated - { - type: "object", - properties: { - enforceForLogicalOperands: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - }], - fixable: "code", - - messages: { - unexpectedCall: "Redundant Boolean call.", - unexpectedNegation: "Redundant double negation." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ enforceForLogicalOperands, enforceForInnerExpressions }] = context.options; - - // Node types which have a test which will coerce values to booleans. - const BOOLEAN_NODE_TYPES = new Set([ - "IfStatement", - "DoWhileStatement", - "WhileStatement", - "ConditionalExpression", - "ForStatement" - ]); - - /** - * Check if a node is a Boolean function or constructor. - * @param {ASTNode} node the node - * @returns {boolean} If the node is Boolean function or constructor - */ - function isBooleanFunctionOrConstructorCall(node) { - - // Boolean() and new Boolean() - return (node.type === "CallExpression" || node.type === "NewExpression") && - node.callee.type === "Identifier" && - node.callee.name === "Boolean"; - } - - /** - * Check if a node is in a context where its value would be coerced to a boolean at runtime. - * @param {ASTNode} node The node - * @returns {boolean} If it is in a boolean context - */ - function isInBooleanContext(node) { - return ( - (isBooleanFunctionOrConstructorCall(node.parent) && - node === node.parent.arguments[0]) || - - (BOOLEAN_NODE_TYPES.has(node.parent.type) && - node === node.parent.test) || - - // ! - (node.parent.type === "UnaryExpression" && - node.parent.operator === "!") - ); - } - - /** - * Checks whether the node is a context that should report an error - * Acts recursively if it is in a logical context - * @param {ASTNode} node the node - * @returns {boolean} If the node is in one of the flagged contexts - */ - function isInFlaggedContext(node) { - if (node.parent.type === "ChainExpression") { - return isInFlaggedContext(node.parent); - } - - /* - * legacy behavior - enforceForLogicalOperands will only recurse on - * logical expressions, not on other contexts. - * enforceForInnerExpressions will recurse on logical expressions - * as well as the other recursive syntaxes. - */ - - if (enforceForLogicalOperands || enforceForInnerExpressions) { - if (node.parent.type === "LogicalExpression") { - if (node.parent.operator === "||" || node.parent.operator === "&&") { - return isInFlaggedContext(node.parent); - } - - // Check the right hand side of a `??` operator. - if (enforceForInnerExpressions && - node.parent.operator === "??" && - node.parent.right === node - ) { - return isInFlaggedContext(node.parent); - } - } - } - - if (enforceForInnerExpressions) { - if ( - node.parent.type === "ConditionalExpression" && - (node.parent.consequent === node || node.parent.alternate === node) - ) { - return isInFlaggedContext(node.parent); - } - - /* - * Check last expression only in a sequence, i.e. if ((1, 2, Boolean(3))) {}, since - * the others don't affect the result of the expression. - */ - if ( - node.parent.type === "SequenceExpression" && - node.parent.expressions.at(-1) === node - ) { - return isInFlaggedContext(node.parent); - } - - } - - return isInBooleanContext(node); - } - - - /** - * Check if a node has comments inside. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if it has comments inside. - */ - function hasCommentsInside(node) { - return Boolean(sourceCode.getCommentsInside(node).length); - } - - /** - * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if the node is parenthesized. - * @private - */ - function isParenthesized(node) { - return eslintUtils.isParenthesized(1, node, sourceCode); - } - - /** - * Determines whether the given node needs to be parenthesized when replacing the previous node. - * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list - * of possible parent node types. By the same assumption, the node's role in a particular parent is already known. - * @param {ASTNode} previousNode Previous node. - * @param {ASTNode} node The node to check. - * @throws {Error} (Unreachable.) - * @returns {boolean} `true` if the node needs to be parenthesized. - */ - function needsParens(previousNode, node) { - if (previousNode.parent.type === "ChainExpression") { - return needsParens(previousNode.parent, node); - } - - if (isParenthesized(previousNode)) { - - // parentheses around the previous node will stay, so there is no need for an additional pair - return false; - } - - // parent of the previous node will become parent of the replacement node - const parent = previousNode.parent; - - switch (parent.type) { - case "CallExpression": - case "NewExpression": - return node.type === "SequenceExpression"; - case "IfStatement": - case "DoWhileStatement": - case "WhileStatement": - case "ForStatement": - case "SequenceExpression": - return false; - case "ConditionalExpression": - if (previousNode === parent.test) { - return precedence(node) <= precedence(parent); - } - if (previousNode === parent.consequent || previousNode === parent.alternate) { - return precedence(node) < precedence({ type: "AssignmentExpression" }); - } - - /* c8 ignore next */ - throw new Error("Ternary child must be test, consequent, or alternate."); - case "UnaryExpression": - return precedence(node) < precedence(parent); - case "LogicalExpression": - if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) { - return true; - } - if (previousNode === parent.left) { - return precedence(node) < precedence(parent); - } - return precedence(node) <= precedence(parent); - - /* c8 ignore next */ - default: - throw new Error(`Unexpected parent type: ${parent.type}`); - } - } - - return { - UnaryExpression(node) { - const parent = node.parent; - - - // Exit early if it's guaranteed not to match - if (node.operator !== "!" || - parent.type !== "UnaryExpression" || - parent.operator !== "!") { - return; - } - - - if (isInFlaggedContext(parent)) { - context.report({ - node: parent, - messageId: "unexpectedNegation", - fix(fixer) { - if (hasCommentsInside(parent)) { - return null; - } - - if (needsParens(parent, node.argument)) { - return fixer.replaceText(parent, `(${sourceCode.getText(node.argument)})`); - } - - let prefix = ""; - const tokenBefore = sourceCode.getTokenBefore(parent); - const firstReplacementToken = sourceCode.getFirstToken(node.argument); - - if ( - tokenBefore && - tokenBefore.range[1] === parent.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken) - ) { - prefix = " "; - } - - return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument)); - } - }); - } - }, - - CallExpression(node) { - if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") { - return; - } - - if (isInFlaggedContext(node)) { - context.report({ - node, - messageId: "unexpectedCall", - fix(fixer) { - const parent = node.parent; - - if (node.arguments.length === 0) { - if (parent.type === "UnaryExpression" && parent.operator === "!") { - - /* - * !Boolean() -> true - */ - - if (hasCommentsInside(parent)) { - return null; - } - - const replacement = "true"; - let prefix = ""; - const tokenBefore = sourceCode.getTokenBefore(parent); - - if ( - tokenBefore && - tokenBefore.range[1] === parent.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, replacement) - ) { - prefix = " "; - } - - return fixer.replaceText(parent, prefix + replacement); - } - - /* - * Boolean() -> false - */ - - if (hasCommentsInside(node)) { - return null; - } - - return fixer.replaceText(node, "false"); - } - - if (node.arguments.length === 1) { - const argument = node.arguments[0]; - - if (argument.type === "SpreadElement" || hasCommentsInside(node)) { - return null; - } - - /* - * Boolean(expression) -> expression - */ - - if (needsParens(node, argument)) { - return fixer.replaceText(node, `(${sourceCode.getText(argument)})`); - } - - return fixer.replaceText(node, sourceCode.getText(argument)); - } - - // two or more arguments - return null; - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [{}], + + docs: { + description: "Disallow unnecessary boolean casts", + recommended: true, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-extra-boolean-cast", + }, + + schema: [ + { + anyOf: [ + { + type: "object", + properties: { + enforceForInnerExpressions: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + + // deprecated + { + type: "object", + properties: { + enforceForLogicalOperands: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + fixable: "code", + + messages: { + unexpectedCall: "Redundant Boolean call.", + unexpectedNegation: "Redundant double negation.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ enforceForLogicalOperands, enforceForInnerExpressions }] = + context.options; + + // Node types which have a test which will coerce values to booleans. + const BOOLEAN_NODE_TYPES = new Set([ + "IfStatement", + "DoWhileStatement", + "WhileStatement", + "ConditionalExpression", + "ForStatement", + ]); + + /** + * Check if a node is a Boolean function or constructor. + * @param {ASTNode} node the node + * @returns {boolean} If the node is Boolean function or constructor + */ + function isBooleanFunctionOrConstructorCall(node) { + // Boolean() and new Boolean() + return ( + (node.type === "CallExpression" || + node.type === "NewExpression") && + node.callee.type === "Identifier" && + node.callee.name === "Boolean" + ); + } + + /** + * Check if a node is in a context where its value would be coerced to a boolean at runtime. + * @param {ASTNode} node The node + * @returns {boolean} If it is in a boolean context + */ + function isInBooleanContext(node) { + return ( + (isBooleanFunctionOrConstructorCall(node.parent) && + node === node.parent.arguments[0]) || + (BOOLEAN_NODE_TYPES.has(node.parent.type) && + node === node.parent.test) || + // ! + (node.parent.type === "UnaryExpression" && + node.parent.operator === "!") + ); + } + + /** + * Checks whether the node is a context that should report an error + * Acts recursively if it is in a logical context + * @param {ASTNode} node the node + * @returns {boolean} If the node is in one of the flagged contexts + */ + function isInFlaggedContext(node) { + if (node.parent.type === "ChainExpression") { + return isInFlaggedContext(node.parent); + } + + /* + * legacy behavior - enforceForLogicalOperands will only recurse on + * logical expressions, not on other contexts. + * enforceForInnerExpressions will recurse on logical expressions + * as well as the other recursive syntaxes. + */ + + if (enforceForLogicalOperands || enforceForInnerExpressions) { + if (node.parent.type === "LogicalExpression") { + if ( + node.parent.operator === "||" || + node.parent.operator === "&&" + ) { + return isInFlaggedContext(node.parent); + } + + // Check the right hand side of a `??` operator. + if ( + enforceForInnerExpressions && + node.parent.operator === "??" && + node.parent.right === node + ) { + return isInFlaggedContext(node.parent); + } + } + } + + if (enforceForInnerExpressions) { + if ( + node.parent.type === "ConditionalExpression" && + (node.parent.consequent === node || + node.parent.alternate === node) + ) { + return isInFlaggedContext(node.parent); + } + + /* + * Check last expression only in a sequence, i.e. if ((1, 2, Boolean(3))) {}, since + * the others don't affect the result of the expression. + */ + if ( + node.parent.type === "SequenceExpression" && + node.parent.expressions.at(-1) === node + ) { + return isInFlaggedContext(node.parent); + } + } + + return isInBooleanContext(node); + } + + /** + * Check if a node has comments inside. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if it has comments inside. + */ + function hasCommentsInside(node) { + return Boolean(sourceCode.getCommentsInside(node).length); + } + + /** + * Checks if the given node is wrapped in grouping parentheses. Parentheses for constructs such as if() don't count. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is parenthesized. + * @private + */ + function isParenthesized(node) { + return eslintUtils.isParenthesized(1, node, sourceCode); + } + + /** + * Determines whether the given node needs to be parenthesized when replacing the previous node. + * It assumes that `previousNode` is the node to be reported by this rule, so it has a limited list + * of possible parent node types. By the same assumption, the node's role in a particular parent is already known. + * @param {ASTNode} previousNode Previous node. + * @param {ASTNode} node The node to check. + * @throws {Error} (Unreachable.) + * @returns {boolean} `true` if the node needs to be parenthesized. + */ + function needsParens(previousNode, node) { + if (previousNode.parent.type === "ChainExpression") { + return needsParens(previousNode.parent, node); + } + + if (isParenthesized(previousNode)) { + // parentheses around the previous node will stay, so there is no need for an additional pair + return false; + } + + // parent of the previous node will become parent of the replacement node + const parent = previousNode.parent; + + switch (parent.type) { + case "CallExpression": + case "NewExpression": + return node.type === "SequenceExpression"; + case "IfStatement": + case "DoWhileStatement": + case "WhileStatement": + case "ForStatement": + case "SequenceExpression": + return false; + case "ConditionalExpression": + if (previousNode === parent.test) { + return precedence(node) <= precedence(parent); + } + if ( + previousNode === parent.consequent || + previousNode === parent.alternate + ) { + return ( + precedence(node) < + precedence({ type: "AssignmentExpression" }) + ); + } + + /* c8 ignore next */ + throw new Error( + "Ternary child must be test, consequent, or alternate.", + ); + case "UnaryExpression": + return precedence(node) < precedence(parent); + case "LogicalExpression": + if ( + astUtils.isMixedLogicalAndCoalesceExpressions( + node, + parent, + ) + ) { + return true; + } + if (previousNode === parent.left) { + return precedence(node) < precedence(parent); + } + return precedence(node) <= precedence(parent); + + /* c8 ignore next */ + default: + throw new Error(`Unexpected parent type: ${parent.type}`); + } + } + + return { + UnaryExpression(node) { + const parent = node.parent; + + // Exit early if it's guaranteed not to match + if ( + node.operator !== "!" || + parent.type !== "UnaryExpression" || + parent.operator !== "!" + ) { + return; + } + + if (isInFlaggedContext(parent)) { + context.report({ + node: parent, + messageId: "unexpectedNegation", + fix(fixer) { + if (hasCommentsInside(parent)) { + return null; + } + + if (needsParens(parent, node.argument)) { + return fixer.replaceText( + parent, + `(${sourceCode.getText(node.argument)})`, + ); + } + + let prefix = ""; + const tokenBefore = + sourceCode.getTokenBefore(parent); + const firstReplacementToken = + sourceCode.getFirstToken(node.argument); + + if ( + tokenBefore && + tokenBefore.range[1] === parent.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBefore, + firstReplacementToken, + ) + ) { + prefix = " "; + } + + return fixer.replaceText( + parent, + prefix + sourceCode.getText(node.argument), + ); + }, + }); + } + }, + + CallExpression(node) { + if ( + node.callee.type !== "Identifier" || + node.callee.name !== "Boolean" + ) { + return; + } + + if (isInFlaggedContext(node)) { + context.report({ + node, + messageId: "unexpectedCall", + fix(fixer) { + const parent = node.parent; + + if (node.arguments.length === 0) { + if ( + parent.type === "UnaryExpression" && + parent.operator === "!" + ) { + /* + * !Boolean() -> true + */ + + if (hasCommentsInside(parent)) { + return null; + } + + const replacement = "true"; + let prefix = ""; + const tokenBefore = + sourceCode.getTokenBefore(parent); + + if ( + tokenBefore && + tokenBefore.range[1] === + parent.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBefore, + replacement, + ) + ) { + prefix = " "; + } + + return fixer.replaceText( + parent, + prefix + replacement, + ); + } + + /* + * Boolean() -> false + */ + + if (hasCommentsInside(node)) { + return null; + } + + return fixer.replaceText(node, "false"); + } + + if (node.arguments.length === 1) { + const argument = node.arguments[0]; + + if ( + argument.type === "SpreadElement" || + hasCommentsInside(node) + ) { + return null; + } + + /* + * Boolean(expression) -> expression + */ + + if (needsParens(node, argument)) { + return fixer.replaceText( + node, + `(${sourceCode.getText(argument)})`, + ); + } + + return fixer.replaceText( + node, + sourceCode.getText(argument), + ); + } + + // two or more arguments + return null; + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-extra-label.js b/lib/rules/no-extra-label.js index 11986c96d34c..6b82de20e914 100644 --- a/lib/rules/no-extra-label.js +++ b/lib/rules/no-extra-label.js @@ -17,134 +17,153 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary labels", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-extra-label" - }, - - schema: [], - fixable: "code", - - messages: { - unexpected: "This label '{{name}}' is unnecessary." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - let scopeInfo = null; - - /** - * Creates a new scope with a breakable statement. - * @param {ASTNode} node A node to create. This is a BreakableStatement. - * @returns {void} - */ - function enterBreakableStatement(node) { - scopeInfo = { - label: node.parent.type === "LabeledStatement" ? node.parent.label : null, - breakable: true, - upper: scopeInfo - }; - } - - /** - * Removes the top scope of the stack. - * @returns {void} - */ - function exitBreakableStatement() { - scopeInfo = scopeInfo.upper; - } - - /** - * Creates a new scope with a labeled statement. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `enterBreakableStatement` function. - * @param {ASTNode} node A node to create. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { - scopeInfo = { - label: node.label, - breakable: false, - upper: scopeInfo - }; - } - } - - /** - * Removes the top scope of the stack. - * - * This ignores it if the body is a breakable statement. - * In this case it's handled in the `exitBreakableStatement` function. - * @param {ASTNode} node A node. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledStatement(node) { - if (!astUtils.isBreakableStatement(node.body)) { - scopeInfo = scopeInfo.upper; - } - } - - /** - * Reports a given control node if it's unnecessary. - * @param {ASTNode} node A node. This is a BreakStatement or a - * ContinueStatement. - * @returns {void} - */ - function reportIfUnnecessary(node) { - if (!node.label) { - return; - } - - const labelNode = node.label; - - for (let info = scopeInfo; info !== null; info = info.upper) { - if (info.breakable || info.label && info.label.name === labelNode.name) { - if (info.breakable && info.label && info.label.name === labelNode.name) { - context.report({ - node: labelNode, - messageId: "unexpected", - data: labelNode, - fix(fixer) { - const breakOrContinueToken = sourceCode.getFirstToken(node); - - if (sourceCode.commentsExistBetween(breakOrContinueToken, labelNode)) { - return null; - } - - return fixer.removeRange([breakOrContinueToken.range[1], labelNode.range[1]]); - } - }); - } - return; - } - } - } - - return { - WhileStatement: enterBreakableStatement, - "WhileStatement:exit": exitBreakableStatement, - DoWhileStatement: enterBreakableStatement, - "DoWhileStatement:exit": exitBreakableStatement, - ForStatement: enterBreakableStatement, - "ForStatement:exit": exitBreakableStatement, - ForInStatement: enterBreakableStatement, - "ForInStatement:exit": exitBreakableStatement, - ForOfStatement: enterBreakableStatement, - "ForOfStatement:exit": exitBreakableStatement, - SwitchStatement: enterBreakableStatement, - "SwitchStatement:exit": exitBreakableStatement, - LabeledStatement: enterLabeledStatement, - "LabeledStatement:exit": exitLabeledStatement, - BreakStatement: reportIfUnnecessary, - ContinueStatement: reportIfUnnecessary - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unnecessary labels", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-extra-label", + }, + + schema: [], + fixable: "code", + + messages: { + unexpected: "This label '{{name}}' is unnecessary.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + let scopeInfo = null; + + /** + * Creates a new scope with a breakable statement. + * @param {ASTNode} node A node to create. This is a BreakableStatement. + * @returns {void} + */ + function enterBreakableStatement(node) { + scopeInfo = { + label: + node.parent.type === "LabeledStatement" + ? node.parent.label + : null, + breakable: true, + upper: scopeInfo, + }; + } + + /** + * Removes the top scope of the stack. + * @returns {void} + */ + function exitBreakableStatement() { + scopeInfo = scopeInfo.upper; + } + + /** + * Creates a new scope with a labeled statement. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `enterBreakableStatement` function. + * @param {ASTNode} node A node to create. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = { + label: node.label, + breakable: false, + upper: scopeInfo, + }; + } + } + + /** + * Removes the top scope of the stack. + * + * This ignores it if the body is a breakable statement. + * In this case it's handled in the `exitBreakableStatement` function. + * @param {ASTNode} node A node. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledStatement(node) { + if (!astUtils.isBreakableStatement(node.body)) { + scopeInfo = scopeInfo.upper; + } + } + + /** + * Reports a given control node if it's unnecessary. + * @param {ASTNode} node A node. This is a BreakStatement or a + * ContinueStatement. + * @returns {void} + */ + function reportIfUnnecessary(node) { + if (!node.label) { + return; + } + + const labelNode = node.label; + + for (let info = scopeInfo; info !== null; info = info.upper) { + if ( + info.breakable || + (info.label && info.label.name === labelNode.name) + ) { + if ( + info.breakable && + info.label && + info.label.name === labelNode.name + ) { + context.report({ + node: labelNode, + messageId: "unexpected", + data: labelNode, + fix(fixer) { + const breakOrContinueToken = + sourceCode.getFirstToken(node); + + if ( + sourceCode.commentsExistBetween( + breakOrContinueToken, + labelNode, + ) + ) { + return null; + } + + return fixer.removeRange([ + breakOrContinueToken.range[1], + labelNode.range[1], + ]); + }, + }); + } + return; + } + } + } + + return { + WhileStatement: enterBreakableStatement, + "WhileStatement:exit": exitBreakableStatement, + DoWhileStatement: enterBreakableStatement, + "DoWhileStatement:exit": exitBreakableStatement, + ForStatement: enterBreakableStatement, + "ForStatement:exit": exitBreakableStatement, + ForInStatement: enterBreakableStatement, + "ForInStatement:exit": exitBreakableStatement, + ForOfStatement: enterBreakableStatement, + "ForOfStatement:exit": exitBreakableStatement, + SwitchStatement: enterBreakableStatement, + "SwitchStatement:exit": exitBreakableStatement, + LabeledStatement: enterLabeledStatement, + "LabeledStatement:exit": exitLabeledStatement, + BreakStatement: reportIfUnnecessary, + ContinueStatement: reportIfUnnecessary, + }; + }, }; diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js index bb3481aa1fd9..518916c27615 100644 --- a/lib/rules/no-extra-parens.js +++ b/lib/rules/no-extra-parens.js @@ -9,1332 +9,1661 @@ // Rule Definition //------------------------------------------------------------------------------ -const { isParenthesized: isParenthesizedRaw } = require("@eslint-community/eslint-utils"); +const { + isParenthesized: isParenthesizedRaw, +} = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-extra-parens", - url: "https://eslint.style/rules/js/no-extra-parens" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow unnecessary parentheses", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-extra-parens" - }, - - fixable: "code", - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["functions"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["all"] - }, - { - type: "object", - properties: { - conditionalAssign: { type: "boolean" }, - ternaryOperandBinaryExpressions: { type: "boolean" }, - nestedBinaryExpressions: { type: "boolean" }, - returnAssign: { type: "boolean" }, - ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] }, - enforceForArrowConditionals: { type: "boolean" }, - enforceForSequenceExpressions: { type: "boolean" }, - enforceForNewInMemberExpressions: { type: "boolean" }, - enforceForFunctionPrototypeMethods: { type: "boolean" }, - allowParensAfterCommentPattern: { type: "string" } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - messages: { - unexpected: "Unnecessary parentheses around expression." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const tokensToIgnore = new WeakSet(); - const precedence = astUtils.getPrecedence; - const ALL_NODES = context.options[0] !== "functions"; - const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false; - const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false; - const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false; - const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false; - const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX; - const IGNORE_ARROW_CONDITIONALS = ALL_NODES && context.options[1] && - context.options[1].enforceForArrowConditionals === false; - const IGNORE_SEQUENCE_EXPRESSIONS = ALL_NODES && context.options[1] && - context.options[1].enforceForSequenceExpressions === false; - const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] && - context.options[1].enforceForNewInMemberExpressions === false; - const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] && - context.options[1].enforceForFunctionPrototypeMethods === false; - const ALLOW_PARENS_AFTER_COMMENT_PATTERN = ALL_NODES && context.options[1] && context.options[1].allowParensAfterCommentPattern; - - const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" }); - const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" }); - - let reportsBuffer; - - /** - * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node. - * Example: function(){}.call() - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is an immediate `call` or `apply` method call. - * @private - */ - function isImmediateFunctionPrototypeMethodCall(node) { - const callNode = astUtils.skipChainExpression(node); - - if (callNode.type !== "CallExpression") { - return false; - } - const callee = astUtils.skipChainExpression(callNode.callee); - - return ( - callee.type === "MemberExpression" && - callee.object.type === "FunctionExpression" && - ["call", "apply"].includes(astUtils.getStaticPropertyName(callee)) - ); - } - - /** - * Determines if this rule should be enforced for a node given the current configuration. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the rule should be enforced for this node. - * @private - */ - function ruleApplies(node) { - if (node.type === "JSXElement" || node.type === "JSXFragment") { - const isSingleLine = node.loc.start.line === node.loc.end.line; - - switch (IGNORE_JSX) { - - // Exclude this JSX element from linting - case "all": - return false; - - // Exclude this JSX element if it is multi-line element - case "multi-line": - return isSingleLine; - - // Exclude this JSX element if it is single-line element - case "single-line": - return !isSingleLine; - - // Nothing special to be done for JSX elements - case "none": - break; - - // no default - } - } - - if (node.type === "SequenceExpression" && IGNORE_SEQUENCE_EXPRESSIONS) { - return false; - } - - if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) { - return false; - } - - return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression"; - } - - /** - * Determines if a node is surrounded by parentheses. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is parenthesised. - * @private - */ - function isParenthesised(node) { - return isParenthesizedRaw(1, node, sourceCode); - } - - /** - * Determines if a node is surrounded by parentheses twice. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is doubly parenthesised. - * @private - */ - function isParenthesisedTwice(node) { - return isParenthesizedRaw(2, node, sourceCode); - } - - /** - * Determines if a node is surrounded by (potentially) invalid parentheses. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParens(node) { - return ruleApplies(node) && isParenthesised(node); - } - - /** - * Determines if a node that is expected to be parenthesised is surrounded by - * (potentially) invalid extra parentheses. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. - * @private - */ - function hasDoubleExcessParens(node) { - return ruleApplies(node) && isParenthesisedTwice(node); - } - - /** - * Determines if a node that is expected to be parenthesised is surrounded by - * (potentially) invalid extra parentheses with considering precedence level of the node. - * If the preference level of the node is not higher or equal to precedence lower limit, it also checks - * whether the node is surrounded by parentheses twice or not. - * @param {ASTNode} node The node to be checked. - * @param {number} precedenceLowerLimit The lower limit of precedence. - * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. - * @private - */ - function hasExcessParensWithPrecedence(node, precedenceLowerLimit) { - if (ruleApplies(node) && isParenthesised(node)) { - if ( - precedence(node) >= precedenceLowerLimit || - isParenthesisedTwice(node) - ) { - return true; - } - } - return false; - } - - /** - * Determines if a node test expression is allowed to have a parenthesised assignment - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the assignment can be parenthesised. - * @private - */ - function isCondAssignException(node) { - return EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression"; - } - - /** - * Determines if a node is in a return statement - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is in a return statement. - * @private - */ - function isInReturnStatement(node) { - for (let currentNode = node; currentNode; currentNode = currentNode.parent) { - if ( - currentNode.type === "ReturnStatement" || - (currentNode.type === "ArrowFunctionExpression" && currentNode.body.type !== "BlockStatement") - ) { - return true; - } - } - - return false; - } - - /** - * Determines if a constructor function is newed-up with parens - * @param {ASTNode} newExpression The NewExpression node to be checked. - * @returns {boolean} True if the constructor is called with parens. - * @private - */ - function isNewExpressionWithParens(newExpression) { - const lastToken = sourceCode.getLastToken(newExpression); - const penultimateToken = sourceCode.getTokenBefore(lastToken); - - return newExpression.arguments.length > 0 || - ( - - // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens - astUtils.isOpeningParenToken(penultimateToken) && - astUtils.isClosingParenToken(lastToken) && - newExpression.callee.range[1] < newExpression.range[1] - ); - } - - /** - * Determines if a node is or contains an assignment expression - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is or contains an assignment expression. - * @private - */ - function containsAssignment(node) { - if (node.type === "AssignmentExpression") { - return true; - } - if (node.type === "ConditionalExpression" && - (node.consequent.type === "AssignmentExpression" || node.alternate.type === "AssignmentExpression")) { - return true; - } - if ((node.left && node.left.type === "AssignmentExpression") || - (node.right && node.right.type === "AssignmentExpression")) { - return true; - } - - return false; - } - - /** - * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the assignment can be parenthesised. - * @private - */ - function isReturnAssignException(node) { - if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) { - return false; - } - - if (node.type === "ReturnStatement") { - return node.argument && containsAssignment(node.argument); - } - if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") { - return containsAssignment(node.body); - } - return containsAssignment(node); - - } - - /** - * Determines if a node following a [no LineTerminator here] restriction is - * surrounded by (potentially) invalid extra parentheses. - * @param {Token} token The token preceding the [no LineTerminator here] restriction. - * @param {ASTNode} node The node to be checked. - * @returns {boolean} True if the node is incorrectly parenthesised. - * @private - */ - function hasExcessParensNoLineTerminator(token, node) { - if (token.loc.end.line === node.loc.start.line) { - return hasExcessParens(node); - } - - return hasDoubleExcessParens(node); - } - - /** - * Determines whether a node should be preceded by an additional space when removing parens - * @param {ASTNode} node node to evaluate; must be surrounded by parentheses - * @returns {boolean} `true` if a space should be inserted before the node - * @private - */ - function requiresLeadingSpace(node) { - const leftParenToken = sourceCode.getTokenBefore(node); - const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true }); - const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true }); - - return tokenBeforeLeftParen && - tokenBeforeLeftParen.range[1] === leftParenToken.range[0] && - leftParenToken.range[1] === tokenAfterLeftParen.range[0] && - !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen); - } - - /** - * Determines whether a node should be followed by an additional space when removing parens - * @param {ASTNode} node node to evaluate; must be surrounded by parentheses - * @returns {boolean} `true` if a space should be inserted after the node - * @private - */ - function requiresTrailingSpace(node) { - const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 }); - const rightParenToken = nextTwoTokens[0]; - const tokenAfterRightParen = nextTwoTokens[1]; - const tokenBeforeRightParen = sourceCode.getLastToken(node); - - return rightParenToken && tokenAfterRightParen && - !sourceCode.isSpaceBetweenTokens(rightParenToken, tokenAfterRightParen) && - !astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen); - } - - /** - * Determines if a given expression node is an IIFE - * @param {ASTNode} node The node to check - * @returns {boolean} `true` if the given node is an IIFE - */ - function isIIFE(node) { - const maybeCallNode = astUtils.skipChainExpression(node); - - return maybeCallNode.type === "CallExpression" && maybeCallNode.callee.type === "FunctionExpression"; - } - - /** - * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment. - * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax, - * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary. - * @param {ASTNode} [node] The node to check - * @returns {boolean} `true` if the given node can be a valid assignment target - */ - function canBeAssignmentTarget(node) { - return node && (node.type === "Identifier" || node.type === "MemberExpression"); - } - - /** - * Checks if a node is fixable. - * A node is fixable if removing a single pair of surrounding parentheses does not turn it - * into a directive after fixing other nodes. - * Almost all nodes are fixable, except if all of the following conditions are met: - * The node is a string Literal - * It has a single pair of parentheses - * It is the only child of an ExpressionStatement - * @param {ASTNode} node The node to evaluate. - * @returns {boolean} Whether or not the node is fixable. - * @private - */ - function isFixable(node) { - - // if it's not a string literal it can be autofixed - if (node.type !== "Literal" || typeof node.value !== "string") { - return true; - } - if (isParenthesisedTwice(node)) { - return true; - } - return !astUtils.isTopLevelExpressionStatement(node.parent); - } - - /** - * Report the node - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function report(node) { - const leftParenToken = sourceCode.getTokenBefore(node); - const rightParenToken = sourceCode.getTokenAfter(node); - - if (!isParenthesisedTwice(node)) { - if (tokensToIgnore.has(sourceCode.getFirstToken(node))) { - return; - } - - if (isIIFE(node) && !isParenthesised(node.callee)) { - return; - } - - if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) { - const commentsBeforeLeftParenToken = sourceCode.getCommentsBefore(leftParenToken); - const totalCommentsBeforeLeftParenTokenCount = commentsBeforeLeftParenToken.length; - const ignorePattern = new RegExp(ALLOW_PARENS_AFTER_COMMENT_PATTERN, "u"); - - if ( - totalCommentsBeforeLeftParenTokenCount > 0 && - ignorePattern.test(commentsBeforeLeftParenToken[totalCommentsBeforeLeftParenTokenCount - 1].value) - ) { - return; - } - } - } - - /** - * Finishes reporting - * @returns {void} - * @private - */ - function finishReport() { - context.report({ - node, - loc: leftParenToken.loc, - messageId: "unexpected", - fix: isFixable(node) - ? fixer => { - const parenthesizedSource = sourceCode.text.slice(leftParenToken.range[1], rightParenToken.range[0]); - - return fixer.replaceTextRange([ - leftParenToken.range[0], - rightParenToken.range[1] - ], (requiresLeadingSpace(node) ? " " : "") + parenthesizedSource + (requiresTrailingSpace(node) ? " " : "")); - } - : null - }); - } - - if (reportsBuffer) { - reportsBuffer.reports.push({ node, finishReport }); - return; - } - - finishReport(); - } - - /** - * Evaluate a argument of the node. - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkArgumentWithPrecedence(node) { - if (hasExcessParensWithPrecedence(node.argument, precedence(node))) { - report(node.argument); - } - } - - /** - * Check if a member expression contains a call expression - * @param {ASTNode} node MemberExpression node to evaluate - * @returns {boolean} true if found, false if not - */ - function doesMemberExpressionContainCallExpression(node) { - let currentNode = node.object; - let currentNodeType = node.object.type; - - while (currentNodeType === "MemberExpression") { - currentNode = currentNode.object; - currentNodeType = currentNode.type; - } - - return currentNodeType === "CallExpression"; - } - - /** - * Evaluate a new call - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkCallNew(node) { - const callee = node.callee; - - if (hasExcessParensWithPrecedence(callee, precedence(node))) { - if ( - hasDoubleExcessParens(callee) || - !( - isIIFE(node) || - - // (new A)(); new (new A)(); - ( - callee.type === "NewExpression" && - !isNewExpressionWithParens(callee) && - !( - node.type === "NewExpression" && - !isNewExpressionWithParens(node) - ) - ) || - - // new (a().b)(); new (a.b().c); - ( - node.type === "NewExpression" && - callee.type === "MemberExpression" && - doesMemberExpressionContainCallExpression(callee) - ) || - - // (a?.b)(); (a?.())(); - ( - !node.optional && - callee.type === "ChainExpression" - ) - ) - ) { - report(node.callee); - } - } - node.arguments - .filter(arg => hasExcessParensWithPrecedence(arg, PRECEDENCE_OF_ASSIGNMENT_EXPR)) - .forEach(report); - } - - /** - * Evaluate binary logicals - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkBinaryLogical(node) { - const prec = precedence(node); - const leftPrecedence = precedence(node.left); - const rightPrecedence = precedence(node.right); - const isExponentiation = node.operator === "**"; - const shouldSkipLeft = NESTED_BINARY && (node.left.type === "BinaryExpression" || node.left.type === "LogicalExpression"); - const shouldSkipRight = NESTED_BINARY && (node.right.type === "BinaryExpression" || node.right.type === "LogicalExpression"); - - if (!shouldSkipLeft && hasExcessParens(node.left)) { - if ( - !(["AwaitExpression", "UnaryExpression"].includes(node.left.type) && isExponentiation) && - !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) && - (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) || - isParenthesisedTwice(node.left) - ) { - report(node.left); - } - } - - if (!shouldSkipRight && hasExcessParens(node.right)) { - if ( - !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) && - (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) || - isParenthesisedTwice(node.right) - ) { - report(node.right); - } - } - } - - /** - * Check the parentheses around the super class of the given class definition. - * @param {ASTNode} node The node of class declarations to check. - * @returns {void} - */ - function checkClass(node) { - if (!node.superClass) { - return; - } - - /* - * If `node.superClass` is a LeftHandSideExpression, parentheses are extra. - * Otherwise, parentheses are needed. - */ - const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR - ? hasExcessParens(node.superClass) - : hasDoubleExcessParens(node.superClass); - - if (hasExtraParens) { - report(node.superClass); - } - } - - /** - * Check the parentheses around the argument of the given spread operator. - * @param {ASTNode} node The node of spread elements/properties to check. - * @returns {void} - */ - function checkSpreadOperator(node) { - if (hasExcessParensWithPrecedence(node.argument, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.argument); - } - } - - /** - * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration - * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node - * @returns {void} - */ - function checkExpressionOrExportStatement(node) { - const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node); - const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken); - const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null; - const tokenAfterClosingParens = secondToken ? sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken) : null; - - if ( - astUtils.isOpeningParenToken(firstToken) && - ( - astUtils.isOpeningBraceToken(secondToken) || - secondToken.type === "Keyword" && ( - secondToken.value === "function" || - secondToken.value === "class" || - secondToken.value === "let" && - tokenAfterClosingParens && - ( - astUtils.isOpeningBracketToken(tokenAfterClosingParens) || - tokenAfterClosingParens.type === "Identifier" - ) - ) || - secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function" - ) - ) { - tokensToIgnore.add(secondToken); - } - - const hasExtraParens = node.parent.type === "ExportDefaultDeclaration" - ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR) - : hasExcessParens(node); - - if (hasExtraParens) { - report(node); - } - } - - /** - * Finds the path from the given node to the specified ancestor. - * @param {ASTNode} node First node in the path. - * @param {ASTNode} ancestor Last node in the path. - * @returns {ASTNode[]} Path, including both nodes. - * @throws {Error} If the given node does not have the specified ancestor. - */ - function pathToAncestor(node, ancestor) { - const path = [node]; - let currentNode = node; - - while (currentNode !== ancestor) { - - currentNode = currentNode.parent; - - /* c8 ignore start */ - if (currentNode === null) { - throw new Error("Nodes are not in the ancestor-descendant relationship."); - }/* c8 ignore stop */ - - path.push(currentNode); - } - - return path; - } - - /** - * Finds the path from the given node to the specified descendant. - * @param {ASTNode} node First node in the path. - * @param {ASTNode} descendant Last node in the path. - * @returns {ASTNode[]} Path, including both nodes. - * @throws {Error} If the given node does not have the specified descendant. - */ - function pathToDescendant(node, descendant) { - return pathToAncestor(descendant, node).reverse(); - } - - /** - * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer - * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop. - * @param {ASTNode} node Ancestor of an 'in' expression. - * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself. - * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis. - */ - function isSafelyEnclosingInExpression(node, child) { - switch (node.type) { - case "ArrayExpression": - case "ArrayPattern": - case "BlockStatement": - case "ObjectExpression": - case "ObjectPattern": - case "TemplateLiteral": - return true; - case "ArrowFunctionExpression": - case "FunctionExpression": - return node.params.includes(child); - case "CallExpression": - case "NewExpression": - return node.arguments.includes(child); - case "MemberExpression": - return node.computed && node.property === child; - case "ConditionalExpression": - return node.consequent === child; - default: - return false; - } - } - - /** - * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately. - * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings. - * @returns {void} - */ - function startNewReportsBuffering() { - reportsBuffer = { - upper: reportsBuffer, - inExpressionNodes: [], - reports: [] - }; - } - - /** - * Ends the current reports buffering. - * @returns {void} - */ - function endCurrentReportsBuffering() { - const { upper, inExpressionNodes, reports } = reportsBuffer; - - if (upper) { - upper.inExpressionNodes.push(...inExpressionNodes); - upper.reports.push(...reports); - } else { - - // flush remaining reports - reports.forEach(({ finishReport }) => finishReport()); - } - - reportsBuffer = upper; - } - - /** - * Checks whether the given node is in the current reports buffer. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is in the current buffer, false otherwise. - */ - function isInCurrentReportsBuffer(node) { - return reportsBuffer.reports.some(r => r.node === node); - } - - /** - * Removes the given node from the current reports buffer. - * @param {ASTNode} node Node to remove. - * @returns {void} - */ - function removeFromCurrentReportsBuffer(node) { - reportsBuffer.reports = reportsBuffer.reports.filter(r => r.node !== node); - } - - /** - * Checks whether a node is a MemberExpression at NewExpression's callee. - * @param {ASTNode} node node to check. - * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise. - */ - function isMemberExpInNewCallee(node) { - if (node.type === "MemberExpression") { - return node.parent.type === "NewExpression" && node.parent.callee === node - ? true - : node.parent.object === node && isMemberExpInNewCallee(node.parent); - } - return false; - } - - /** - * Checks if the left-hand side of an assignment is an identifier, the operator is one of - * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function. - * - * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an - * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand - * side is an anonymous class or function and the left-hand side is an *unparenthesized* - * identifier has different semantics than other assignments. - * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name` - * will be set to the string "foo", i.e. the identifier name. The same thing does not happen - * when evaluating `(foo) = function () {}`. - * Since the parenthesizing of the identifier in the left-hand side is significant in this - * special case, the parentheses, if present, should not be flagged as unnecessary. - * @param {ASTNode} node an AssignmentExpression node. - * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the - * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous - * class or function; otherwise, `false`. - */ - function isAnonymousFunctionAssignmentException({ left, operator, right }) { - if (left.type === "Identifier" && ["=", "&&=", "||=", "??="].includes(operator)) { - const rhsType = right.type; - - if (rhsType === "ArrowFunctionExpression") { - return true; - } - if ((rhsType === "FunctionExpression" || rhsType === "ClassExpression") && !right.id) { - return true; - } - } - return false; - } - - return { - ArrayExpression(node) { - node.elements - .filter(e => e && hasExcessParensWithPrecedence(e, PRECEDENCE_OF_ASSIGNMENT_EXPR)) - .forEach(report); - }, - - ArrayPattern(node) { - node.elements - .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e)) - .forEach(report); - }, - - ArrowFunctionExpression(node) { - if (isReturnAssignException(node)) { - return; - } - - if (node.body.type === "ConditionalExpression" && - IGNORE_ARROW_CONDITIONALS - ) { - return; - } - - if (node.body.type !== "BlockStatement") { - const firstBodyToken = sourceCode.getFirstToken(node.body, astUtils.isNotOpeningParenToken); - const tokenBeforeFirst = sourceCode.getTokenBefore(firstBodyToken); - - if (astUtils.isOpeningParenToken(tokenBeforeFirst) && astUtils.isOpeningBraceToken(firstBodyToken)) { - tokensToIgnore.add(firstBodyToken); - } - if (hasExcessParensWithPrecedence(node.body, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.body); - } - } - }, - - AssignmentExpression(node) { - if (canBeAssignmentTarget(node.left) && hasExcessParens(node.left) && - (!isAnonymousFunctionAssignmentException(node) || isParenthesisedTwice(node.left))) { - report(node.left); - } - - if (!isReturnAssignException(node) && hasExcessParensWithPrecedence(node.right, precedence(node))) { - report(node.right); - } - }, - - BinaryExpression(node) { - if (reportsBuffer && node.operator === "in") { - reportsBuffer.inExpressionNodes.push(node); - } - - checkBinaryLogical(node); - }, - - CallExpression: checkCallNew, - - ConditionalExpression(node) { - if (isReturnAssignException(node)) { - return; - } - - const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]); - - if ( - !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) && - !isCondAssignException(node) && - hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" })) - ) { - report(node.test); - } - - if ( - !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) && - hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.consequent); - } - - if ( - !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) && - hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.alternate); - } - }, - - DoWhileStatement(node) { - if (hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, - - ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration), - ExpressionStatement: node => checkExpressionOrExportStatement(node.expression), - - ForInStatement(node) { - if (node.left.type !== "VariableDeclaration") { - const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken); - - if ( - firstLeftToken.value === "let" && - astUtils.isOpeningBracketToken( - sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken) - ) - ) { - - // ForInStatement#left expression cannot start with `let[`. - tokensToIgnore.add(firstLeftToken); - } - } - - if (hasExcessParens(node.left)) { - report(node.left); - } - - if (hasExcessParens(node.right)) { - report(node.right); - } - }, - - ForOfStatement(node) { - if (node.left.type !== "VariableDeclaration") { - const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken); - - if (firstLeftToken.value === "let") { - - // ForOfStatement#left expression cannot start with `let`. - tokensToIgnore.add(firstLeftToken); - } - } - - if (hasExcessParens(node.left)) { - report(node.left); - } - - if (hasExcessParensWithPrecedence(node.right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.right); - } - }, - - ForStatement(node) { - if (node.test && hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - - if (node.update && hasExcessParens(node.update)) { - report(node.update); - } - - if (node.init) { - - if (node.init.type !== "VariableDeclaration") { - const firstToken = sourceCode.getFirstToken(node.init, astUtils.isNotOpeningParenToken); - - if ( - firstToken.value === "let" && - astUtils.isOpeningBracketToken( - sourceCode.getTokenAfter(firstToken, astUtils.isNotClosingParenToken) - ) - ) { - - // ForStatement#init expression cannot start with `let[`. - tokensToIgnore.add(firstToken); - } - } - - startNewReportsBuffering(); - - if (hasExcessParens(node.init)) { - report(node.init); - } - } - }, - - "ForStatement > *.init:exit"(node) { - - /* - * Removing parentheses around `in` expressions might change semantics and cause errors. - * - * For example, this valid for loop: - * for (let a = (b in c); ;); - * after removing parentheses would be treated as an invalid for-in loop: - * for (let a = b in c; ;); - */ - - if (reportsBuffer.reports.length) { - reportsBuffer.inExpressionNodes.forEach(inExpressionNode => { - const path = pathToDescendant(node, inExpressionNode); - let nodeToExclude; - - for (let i = 0; i < path.length; i++) { - const pathNode = path[i]; - - if (i < path.length - 1) { - const nextPathNode = path[i + 1]; - - if (isSafelyEnclosingInExpression(pathNode, nextPathNode)) { - - // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]'). - return; - } - } - - if (isParenthesised(pathNode)) { - if (isInCurrentReportsBuffer(pathNode)) { - - // This node was supposed to be reported, but parentheses might be necessary. - - if (isParenthesisedTwice(pathNode)) { - - /* - * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses. - * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses. - * The remaining pair is safely enclosing the 'in' expression. - */ - return; - } - - // Exclude the outermost node only. - if (!nodeToExclude) { - nodeToExclude = pathNode; - } - - // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside. - - } else { - - // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'. - return; - } - } - } - - // Exclude the node from the list (i.e. treat parentheses as necessary) - removeFromCurrentReportsBuffer(nodeToExclude); - }); - } - - endCurrentReportsBuffering(); - }, - - IfStatement(node) { - if (hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, - - ImportExpression(node) { - const { source } = node; - - if (source.type === "SequenceExpression") { - if (hasDoubleExcessParens(source)) { - report(source); - } - } else if (hasExcessParens(source)) { - report(source); - } - }, - - LogicalExpression: checkBinaryLogical, - - MemberExpression(node) { - const shouldAllowWrapOnce = isMemberExpInNewCallee(node) && - doesMemberExpressionContainCallExpression(node); - const nodeObjHasExcessParens = shouldAllowWrapOnce - ? hasDoubleExcessParens(node.object) - : hasExcessParens(node.object) && - !( - isImmediateFunctionPrototypeMethodCall(node.parent) && - node.parent.callee === node && - IGNORE_FUNCTION_PROTOTYPE_METHODS - ); - - if ( - nodeObjHasExcessParens && - precedence(node.object) >= precedence(node) && - ( - node.computed || - !( - astUtils.isDecimalInteger(node.object) || - - // RegExp literal is allowed to have parens (#1589) - (node.object.type === "Literal" && node.object.regex) - ) - ) - ) { - report(node.object); - } - - if (nodeObjHasExcessParens && - node.object.type === "CallExpression" - ) { - report(node.object); - } - - if (nodeObjHasExcessParens && - !IGNORE_NEW_IN_MEMBER_EXPR && - node.object.type === "NewExpression" && - isNewExpressionWithParens(node.object)) { - report(node.object); - } - - if (nodeObjHasExcessParens && - node.optional && - node.object.type === "ChainExpression" - ) { - report(node.object); - } - - if (node.computed && hasExcessParens(node.property)) { - report(node.property); - } - }, - - "MethodDefinition[computed=true]"(node) { - if (hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.key); - } - }, - - NewExpression: checkCallNew, - - ObjectExpression(node) { - node.properties - .filter(property => property.value && hasExcessParensWithPrecedence(property.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) - .forEach(property => report(property.value)); - }, - - ObjectPattern(node) { - node.properties - .filter(property => { - const value = property.value; - - return canBeAssignmentTarget(value) && hasExcessParens(value); - }).forEach(property => report(property.value)); - }, - - Property(node) { - if (node.computed) { - const { key } = node; - - if (key && hasExcessParensWithPrecedence(key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(key); - } - } - }, - - PropertyDefinition(node) { - if (node.computed && hasExcessParensWithPrecedence(node.key, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.key); - } - - if (node.value && hasExcessParensWithPrecedence(node.value, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(node.value); - } - }, - - RestElement(node) { - const argument = node.argument; - - if (canBeAssignmentTarget(argument) && hasExcessParens(argument)) { - report(argument); - } - }, - - ReturnStatement(node) { - const returnToken = sourceCode.getFirstToken(node); - - if (isReturnAssignException(node)) { - return; - } - - if (node.argument && - hasExcessParensNoLineTerminator(returnToken, node.argument) && - - // RegExp literal is allowed to have parens (#1589) - !(node.argument.type === "Literal" && node.argument.regex)) { - report(node.argument); - } - }, - - SequenceExpression(node) { - const precedenceOfNode = precedence(node); - - node.expressions - .filter(e => hasExcessParensWithPrecedence(e, precedenceOfNode)) - .forEach(report); - }, - - SwitchCase(node) { - if (node.test && hasExcessParens(node.test)) { - report(node.test); - } - }, - - SwitchStatement(node) { - if (hasExcessParens(node.discriminant)) { - report(node.discriminant); - } - }, - - ThrowStatement(node) { - const throwToken = sourceCode.getFirstToken(node); - - if (hasExcessParensNoLineTerminator(throwToken, node.argument)) { - report(node.argument); - } - }, - - UnaryExpression: checkArgumentWithPrecedence, - UpdateExpression(node) { - if (node.prefix) { - checkArgumentWithPrecedence(node); - } else { - const { argument } = node; - const operatorToken = sourceCode.getLastToken(node); - - if (argument.loc.end.line === operatorToken.loc.start.line) { - checkArgumentWithPrecedence(node); - } else { - if (hasDoubleExcessParens(argument)) { - report(argument); - } - } - } - }, - AwaitExpression: checkArgumentWithPrecedence, - - VariableDeclarator(node) { - if ( - node.init && hasExcessParensWithPrecedence(node.init, PRECEDENCE_OF_ASSIGNMENT_EXPR) && - - // RegExp literal is allowed to have parens (#1589) - !(node.init.type === "Literal" && node.init.regex) - ) { - report(node.init); - } - }, - - WhileStatement(node) { - if (hasExcessParens(node.test) && !isCondAssignException(node)) { - report(node.test); - } - }, - - WithStatement(node) { - if (hasExcessParens(node.object)) { - report(node.object); - } - }, - - YieldExpression(node) { - if (node.argument) { - const yieldToken = sourceCode.getFirstToken(node); - - if ((precedence(node.argument) >= precedence(node) && - hasExcessParensNoLineTerminator(yieldToken, node.argument)) || - hasDoubleExcessParens(node.argument)) { - report(node.argument); - } - } - }, - - ClassDeclaration: checkClass, - ClassExpression: checkClass, - - SpreadElement: checkSpreadOperator, - SpreadProperty: checkSpreadOperator, - ExperimentalSpreadProperty: checkSpreadOperator, - - TemplateLiteral(node) { - node.expressions - .filter(e => e && hasExcessParens(e)) - .forEach(report); - }, - - AssignmentPattern(node) { - const { left, right } = node; - - if (canBeAssignmentTarget(left) && hasExcessParens(left)) { - report(left); - } - - if (right && hasExcessParensWithPrecedence(right, PRECEDENCE_OF_ASSIGNMENT_EXPR)) { - report(right); - } - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-extra-parens", + url: "https://eslint.style/rules/js/no-extra-parens", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow unnecessary parentheses", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-extra-parens", + }, + + fixable: "code", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["functions"], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["all"], + }, + { + type: "object", + properties: { + conditionalAssign: { type: "boolean" }, + ternaryOperandBinaryExpressions: { + type: "boolean", + }, + nestedBinaryExpressions: { type: "boolean" }, + returnAssign: { type: "boolean" }, + ignoreJSX: { + enum: [ + "none", + "all", + "single-line", + "multi-line", + ], + }, + enforceForArrowConditionals: { + type: "boolean", + }, + enforceForSequenceExpressions: { + type: "boolean", + }, + enforceForNewInMemberExpressions: { + type: "boolean", + }, + enforceForFunctionPrototypeMethods: { + type: "boolean", + }, + allowParensAfterCommentPattern: { + type: "string", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + messages: { + unexpected: "Unnecessary parentheses around expression.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const tokensToIgnore = new WeakSet(); + const precedence = astUtils.getPrecedence; + const ALL_NODES = context.options[0] !== "functions"; + const EXCEPT_COND_ASSIGN = + ALL_NODES && + context.options[1] && + context.options[1].conditionalAssign === false; + const EXCEPT_COND_TERNARY = + ALL_NODES && + context.options[1] && + context.options[1].ternaryOperandBinaryExpressions === false; + const NESTED_BINARY = + ALL_NODES && + context.options[1] && + context.options[1].nestedBinaryExpressions === false; + const EXCEPT_RETURN_ASSIGN = + ALL_NODES && + context.options[1] && + context.options[1].returnAssign === false; + const IGNORE_JSX = + ALL_NODES && context.options[1] && context.options[1].ignoreJSX; + const IGNORE_ARROW_CONDITIONALS = + ALL_NODES && + context.options[1] && + context.options[1].enforceForArrowConditionals === false; + const IGNORE_SEQUENCE_EXPRESSIONS = + ALL_NODES && + context.options[1] && + context.options[1].enforceForSequenceExpressions === false; + const IGNORE_NEW_IN_MEMBER_EXPR = + ALL_NODES && + context.options[1] && + context.options[1].enforceForNewInMemberExpressions === false; + const IGNORE_FUNCTION_PROTOTYPE_METHODS = + ALL_NODES && + context.options[1] && + context.options[1].enforceForFunctionPrototypeMethods === false; + const ALLOW_PARENS_AFTER_COMMENT_PATTERN = + ALL_NODES && + context.options[1] && + context.options[1].allowParensAfterCommentPattern; + + const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ + type: "AssignmentExpression", + }); + const PRECEDENCE_OF_UPDATE_EXPR = precedence({ + type: "UpdateExpression", + }); + + let reportsBuffer; + + /** + * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node. + * Example: function(){}.call() + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is an immediate `call` or `apply` method call. + * @private + */ + function isImmediateFunctionPrototypeMethodCall(node) { + const callNode = astUtils.skipChainExpression(node); + + if (callNode.type !== "CallExpression") { + return false; + } + const callee = astUtils.skipChainExpression(callNode.callee); + + return ( + callee.type === "MemberExpression" && + callee.object.type === "FunctionExpression" && + ["call", "apply"].includes( + astUtils.getStaticPropertyName(callee), + ) + ); + } + + /** + * Determines if this rule should be enforced for a node given the current configuration. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the rule should be enforced for this node. + * @private + */ + function ruleApplies(node) { + if (node.type === "JSXElement" || node.type === "JSXFragment") { + const isSingleLine = node.loc.start.line === node.loc.end.line; + + switch (IGNORE_JSX) { + // Exclude this JSX element from linting + case "all": + return false; + + // Exclude this JSX element if it is multi-line element + case "multi-line": + return isSingleLine; + + // Exclude this JSX element if it is single-line element + case "single-line": + return !isSingleLine; + + // Nothing special to be done for JSX elements + case "none": + break; + + // no default + } + } + + if ( + node.type === "SequenceExpression" && + IGNORE_SEQUENCE_EXPRESSIONS + ) { + return false; + } + + if ( + isImmediateFunctionPrototypeMethodCall(node) && + IGNORE_FUNCTION_PROTOTYPE_METHODS + ) { + return false; + } + + return ( + ALL_NODES || + node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression" + ); + } + + /** + * Determines if a node is surrounded by parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is parenthesised. + * @private + */ + function isParenthesised(node) { + return isParenthesizedRaw(1, node, sourceCode); + } + + /** + * Determines if a node is surrounded by parentheses twice. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is doubly parenthesised. + * @private + */ + function isParenthesisedTwice(node) { + return isParenthesizedRaw(2, node, sourceCode); + } + + /** + * Determines if a node is surrounded by (potentially) invalid parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParens(node) { + return ruleApplies(node) && isParenthesised(node); + } + + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasDoubleExcessParens(node) { + return ruleApplies(node) && isParenthesisedTwice(node); + } + + /** + * Determines if a node that is expected to be parenthesised is surrounded by + * (potentially) invalid extra parentheses with considering precedence level of the node. + * If the preference level of the node is not higher or equal to precedence lower limit, it also checks + * whether the node is surrounded by parentheses twice or not. + * @param {ASTNode} node The node to be checked. + * @param {number} precedenceLowerLimit The lower limit of precedence. + * @returns {boolean} True if the node is has an unexpected extra pair of parentheses. + * @private + */ + function hasExcessParensWithPrecedence(node, precedenceLowerLimit) { + if (ruleApplies(node) && isParenthesised(node)) { + if ( + precedence(node) >= precedenceLowerLimit || + isParenthesisedTwice(node) + ) { + return true; + } + } + return false; + } + + /** + * Determines if a node test expression is allowed to have a parenthesised assignment + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isCondAssignException(node) { + return ( + EXCEPT_COND_ASSIGN && node.test.type === "AssignmentExpression" + ); + } + + /** + * Determines if a node is in a return statement + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is in a return statement. + * @private + */ + function isInReturnStatement(node) { + for ( + let currentNode = node; + currentNode; + currentNode = currentNode.parent + ) { + if ( + currentNode.type === "ReturnStatement" || + (currentNode.type === "ArrowFunctionExpression" && + currentNode.body.type !== "BlockStatement") + ) { + return true; + } + } + + return false; + } + + /** + * Determines if a constructor function is newed-up with parens + * @param {ASTNode} newExpression The NewExpression node to be checked. + * @returns {boolean} True if the constructor is called with parens. + * @private + */ + function isNewExpressionWithParens(newExpression) { + const lastToken = sourceCode.getLastToken(newExpression); + const penultimateToken = sourceCode.getTokenBefore(lastToken); + + return ( + newExpression.arguments.length > 0 || + // The expression should end with its own parens, e.g., new new foo() is not a new expression with parens + (astUtils.isOpeningParenToken(penultimateToken) && + astUtils.isClosingParenToken(lastToken) && + newExpression.callee.range[1] < newExpression.range[1]) + ); + } + + /** + * Determines if a node is or contains an assignment expression + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is or contains an assignment expression. + * @private + */ + function containsAssignment(node) { + if (node.type === "AssignmentExpression") { + return true; + } + if ( + node.type === "ConditionalExpression" && + (node.consequent.type === "AssignmentExpression" || + node.alternate.type === "AssignmentExpression") + ) { + return true; + } + if ( + (node.left && node.left.type === "AssignmentExpression") || + (node.right && node.right.type === "AssignmentExpression") + ) { + return true; + } + + return false; + } + + /** + * Determines if a node is contained by or is itself a return statement and is allowed to have a parenthesised assignment + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the assignment can be parenthesised. + * @private + */ + function isReturnAssignException(node) { + if (!EXCEPT_RETURN_ASSIGN || !isInReturnStatement(node)) { + return false; + } + + if (node.type === "ReturnStatement") { + return node.argument && containsAssignment(node.argument); + } + if ( + node.type === "ArrowFunctionExpression" && + node.body.type !== "BlockStatement" + ) { + return containsAssignment(node.body); + } + return containsAssignment(node); + } + + /** + * Determines if a node following a [no LineTerminator here] restriction is + * surrounded by (potentially) invalid extra parentheses. + * @param {Token} token The token preceding the [no LineTerminator here] restriction. + * @param {ASTNode} node The node to be checked. + * @returns {boolean} True if the node is incorrectly parenthesised. + * @private + */ + function hasExcessParensNoLineTerminator(token, node) { + if (token.loc.end.line === node.loc.start.line) { + return hasExcessParens(node); + } + + return hasDoubleExcessParens(node); + } + + /** + * Determines whether a node should be preceded by an additional space when removing parens + * @param {ASTNode} node node to evaluate; must be surrounded by parentheses + * @returns {boolean} `true` if a space should be inserted before the node + * @private + */ + function requiresLeadingSpace(node) { + const leftParenToken = sourceCode.getTokenBefore(node); + const tokenBeforeLeftParen = sourceCode.getTokenBefore( + leftParenToken, + { includeComments: true }, + ); + const tokenAfterLeftParen = sourceCode.getTokenAfter( + leftParenToken, + { includeComments: true }, + ); + + return ( + tokenBeforeLeftParen && + tokenBeforeLeftParen.range[1] === leftParenToken.range[0] && + leftParenToken.range[1] === tokenAfterLeftParen.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBeforeLeftParen, + tokenAfterLeftParen, + ) + ); + } + + /** + * Determines whether a node should be followed by an additional space when removing parens + * @param {ASTNode} node node to evaluate; must be surrounded by parentheses + * @returns {boolean} `true` if a space should be inserted after the node + * @private + */ + function requiresTrailingSpace(node) { + const nextTwoTokens = sourceCode.getTokensAfter(node, { count: 2 }); + const rightParenToken = nextTwoTokens[0]; + const tokenAfterRightParen = nextTwoTokens[1]; + const tokenBeforeRightParen = sourceCode.getLastToken(node); + + return ( + rightParenToken && + tokenAfterRightParen && + !sourceCode.isSpaceBetweenTokens( + rightParenToken, + tokenAfterRightParen, + ) && + !astUtils.canTokensBeAdjacent( + tokenBeforeRightParen, + tokenAfterRightParen, + ) + ); + } + + /** + * Determines if a given expression node is an IIFE + * @param {ASTNode} node The node to check + * @returns {boolean} `true` if the given node is an IIFE + */ + function isIIFE(node) { + const maybeCallNode = astUtils.skipChainExpression(node); + + return ( + maybeCallNode.type === "CallExpression" && + maybeCallNode.callee.type === "FunctionExpression" + ); + } + + /** + * Determines if the given node can be the assignment target in destructuring or the LHS of an assignment. + * This is to avoid an autofix that could change behavior because parsers mistakenly allow invalid syntax, + * such as `(a = b) = c` and `[(a = b) = c] = []`. Ideally, this function shouldn't be necessary. + * @param {ASTNode} [node] The node to check + * @returns {boolean} `true` if the given node can be a valid assignment target + */ + function canBeAssignmentTarget(node) { + return ( + node && + (node.type === "Identifier" || node.type === "MemberExpression") + ); + } + + /** + * Checks if a node is fixable. + * A node is fixable if removing a single pair of surrounding parentheses does not turn it + * into a directive after fixing other nodes. + * Almost all nodes are fixable, except if all of the following conditions are met: + * The node is a string Literal + * It has a single pair of parentheses + * It is the only child of an ExpressionStatement + * @param {ASTNode} node The node to evaluate. + * @returns {boolean} Whether or not the node is fixable. + * @private + */ + function isFixable(node) { + // if it's not a string literal it can be autofixed + if (node.type !== "Literal" || typeof node.value !== "string") { + return true; + } + if (isParenthesisedTwice(node)) { + return true; + } + return !astUtils.isTopLevelExpressionStatement(node.parent); + } + + /** + * Report the node + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function report(node) { + const leftParenToken = sourceCode.getTokenBefore(node); + const rightParenToken = sourceCode.getTokenAfter(node); + + if (!isParenthesisedTwice(node)) { + if (tokensToIgnore.has(sourceCode.getFirstToken(node))) { + return; + } + + if (isIIFE(node) && !isParenthesised(node.callee)) { + return; + } + + if (ALLOW_PARENS_AFTER_COMMENT_PATTERN) { + const commentsBeforeLeftParenToken = + sourceCode.getCommentsBefore(leftParenToken); + const totalCommentsBeforeLeftParenTokenCount = + commentsBeforeLeftParenToken.length; + const ignorePattern = new RegExp( + ALLOW_PARENS_AFTER_COMMENT_PATTERN, + "u", + ); + + if ( + totalCommentsBeforeLeftParenTokenCount > 0 && + ignorePattern.test( + commentsBeforeLeftParenToken[ + totalCommentsBeforeLeftParenTokenCount - 1 + ].value, + ) + ) { + return; + } + } + } + + /** + * Finishes reporting + * @returns {void} + * @private + */ + function finishReport() { + context.report({ + node, + loc: leftParenToken.loc, + messageId: "unexpected", + fix: isFixable(node) + ? fixer => { + const parenthesizedSource = + sourceCode.text.slice( + leftParenToken.range[1], + rightParenToken.range[0], + ); + + return fixer.replaceTextRange( + [ + leftParenToken.range[0], + rightParenToken.range[1], + ], + (requiresLeadingSpace(node) ? " " : "") + + parenthesizedSource + + (requiresTrailingSpace(node) + ? " " + : ""), + ); + } + : null, + }); + } + + if (reportsBuffer) { + reportsBuffer.reports.push({ node, finishReport }); + return; + } + + finishReport(); + } + + /** + * Evaluate a argument of the node. + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkArgumentWithPrecedence(node) { + if ( + hasExcessParensWithPrecedence(node.argument, precedence(node)) + ) { + report(node.argument); + } + } + + /** + * Check if a member expression contains a call expression + * @param {ASTNode} node MemberExpression node to evaluate + * @returns {boolean} true if found, false if not + */ + function doesMemberExpressionContainCallExpression(node) { + let currentNode = node.object; + let currentNodeType = node.object.type; + + while (currentNodeType === "MemberExpression") { + currentNode = currentNode.object; + currentNodeType = currentNode.type; + } + + return currentNodeType === "CallExpression"; + } + + /** + * Evaluate a new call + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkCallNew(node) { + const callee = node.callee; + + if (hasExcessParensWithPrecedence(callee, precedence(node))) { + if ( + hasDoubleExcessParens(callee) || + !( + isIIFE(node) || + // (new A)(); new (new A)(); + (callee.type === "NewExpression" && + !isNewExpressionWithParens(callee) && + !( + node.type === "NewExpression" && + !isNewExpressionWithParens(node) + )) || + // new (a().b)(); new (a.b().c); + (node.type === "NewExpression" && + callee.type === "MemberExpression" && + doesMemberExpressionContainCallExpression( + callee, + )) || + // (a?.b)(); (a?.())(); + (!node.optional && callee.type === "ChainExpression") + ) + ) { + report(node.callee); + } + } + node.arguments + .filter(arg => + hasExcessParensWithPrecedence( + arg, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ), + ) + .forEach(report); + } + + /** + * Evaluate binary logicals + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkBinaryLogical(node) { + const prec = precedence(node); + const leftPrecedence = precedence(node.left); + const rightPrecedence = precedence(node.right); + const isExponentiation = node.operator === "**"; + const shouldSkipLeft = + NESTED_BINARY && + (node.left.type === "BinaryExpression" || + node.left.type === "LogicalExpression"); + const shouldSkipRight = + NESTED_BINARY && + (node.right.type === "BinaryExpression" || + node.right.type === "LogicalExpression"); + + if (!shouldSkipLeft && hasExcessParens(node.left)) { + if ( + (!( + ["AwaitExpression", "UnaryExpression"].includes( + node.left.type, + ) && isExponentiation + ) && + !astUtils.isMixedLogicalAndCoalesceExpressions( + node.left, + node, + ) && + (leftPrecedence > prec || + (leftPrecedence === prec && !isExponentiation))) || + isParenthesisedTwice(node.left) + ) { + report(node.left); + } + } + + if (!shouldSkipRight && hasExcessParens(node.right)) { + if ( + (!astUtils.isMixedLogicalAndCoalesceExpressions( + node.right, + node, + ) && + (rightPrecedence > prec || + (rightPrecedence === prec && isExponentiation))) || + isParenthesisedTwice(node.right) + ) { + report(node.right); + } + } + } + + /** + * Check the parentheses around the super class of the given class definition. + * @param {ASTNode} node The node of class declarations to check. + * @returns {void} + */ + function checkClass(node) { + if (!node.superClass) { + return; + } + + /* + * If `node.superClass` is a LeftHandSideExpression, parentheses are extra. + * Otherwise, parentheses are needed. + */ + const hasExtraParens = + precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR + ? hasExcessParens(node.superClass) + : hasDoubleExcessParens(node.superClass); + + if (hasExtraParens) { + report(node.superClass); + } + } + + /** + * Check the parentheses around the argument of the given spread operator. + * @param {ASTNode} node The node of spread elements/properties to check. + * @returns {void} + */ + function checkSpreadOperator(node) { + if ( + hasExcessParensWithPrecedence( + node.argument, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.argument); + } + } + + /** + * Checks the parentheses for an ExpressionStatement or ExportDefaultDeclaration + * @param {ASTNode} node The ExpressionStatement.expression or ExportDefaultDeclaration.declaration node + * @returns {void} + */ + function checkExpressionOrExportStatement(node) { + const firstToken = isParenthesised(node) + ? sourceCode.getTokenBefore(node) + : sourceCode.getFirstToken(node); + const secondToken = sourceCode.getTokenAfter( + firstToken, + astUtils.isNotOpeningParenToken, + ); + const thirdToken = secondToken + ? sourceCode.getTokenAfter(secondToken) + : null; + const tokenAfterClosingParens = secondToken + ? sourceCode.getTokenAfter( + secondToken, + astUtils.isNotClosingParenToken, + ) + : null; + + if ( + astUtils.isOpeningParenToken(firstToken) && + (astUtils.isOpeningBraceToken(secondToken) || + (secondToken.type === "Keyword" && + (secondToken.value === "function" || + secondToken.value === "class" || + (secondToken.value === "let" && + tokenAfterClosingParens && + (astUtils.isOpeningBracketToken( + tokenAfterClosingParens, + ) || + tokenAfterClosingParens.type === + "Identifier")))) || + (secondToken && + secondToken.type === "Identifier" && + secondToken.value === "async" && + thirdToken && + thirdToken.type === "Keyword" && + thirdToken.value === "function")) + ) { + tokensToIgnore.add(secondToken); + } + + const hasExtraParens = + node.parent.type === "ExportDefaultDeclaration" + ? hasExcessParensWithPrecedence( + node, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + : hasExcessParens(node); + + if (hasExtraParens) { + report(node); + } + } + + /** + * Finds the path from the given node to the specified ancestor. + * @param {ASTNode} node First node in the path. + * @param {ASTNode} ancestor Last node in the path. + * @returns {ASTNode[]} Path, including both nodes. + * @throws {Error} If the given node does not have the specified ancestor. + */ + function pathToAncestor(node, ancestor) { + const path = [node]; + let currentNode = node; + + while (currentNode !== ancestor) { + currentNode = currentNode.parent; + + /* c8 ignore start */ + if (currentNode === null) { + throw new Error( + "Nodes are not in the ancestor-descendant relationship.", + ); + } /* c8 ignore stop */ + + path.push(currentNode); + } + + return path; + } + + /** + * Finds the path from the given node to the specified descendant. + * @param {ASTNode} node First node in the path. + * @param {ASTNode} descendant Last node in the path. + * @returns {ASTNode[]} Path, including both nodes. + * @throws {Error} If the given node does not have the specified descendant. + */ + function pathToDescendant(node, descendant) { + return pathToAncestor(descendant, node).reverse(); + } + + /** + * Checks whether the syntax of the given ancestor of an 'in' expression inside a for-loop initializer + * is preventing the 'in' keyword from being interpreted as a part of an ill-formed for-in loop. + * @param {ASTNode} node Ancestor of an 'in' expression. + * @param {ASTNode} child Child of the node, ancestor of the same 'in' expression or the 'in' expression itself. + * @returns {boolean} True if the keyword 'in' would be interpreted as the 'in' operator, without any parenthesis. + */ + function isSafelyEnclosingInExpression(node, child) { + switch (node.type) { + case "ArrayExpression": + case "ArrayPattern": + case "BlockStatement": + case "ObjectExpression": + case "ObjectPattern": + case "TemplateLiteral": + return true; + case "ArrowFunctionExpression": + case "FunctionExpression": + return node.params.includes(child); + case "CallExpression": + case "NewExpression": + return node.arguments.includes(child); + case "MemberExpression": + return node.computed && node.property === child; + case "ConditionalExpression": + return node.consequent === child; + default: + return false; + } + } + + /** + * Starts a new reports buffering. Warnings will be stored in a buffer instead of being reported immediately. + * An additional logic that requires multiple nodes (e.g. a whole subtree) may dismiss some of the stored warnings. + * @returns {void} + */ + function startNewReportsBuffering() { + reportsBuffer = { + upper: reportsBuffer, + inExpressionNodes: [], + reports: [], + }; + } + + /** + * Ends the current reports buffering. + * @returns {void} + */ + function endCurrentReportsBuffering() { + const { upper, inExpressionNodes, reports } = reportsBuffer; + + if (upper) { + upper.inExpressionNodes.push(...inExpressionNodes); + upper.reports.push(...reports); + } else { + // flush remaining reports + reports.forEach(({ finishReport }) => finishReport()); + } + + reportsBuffer = upper; + } + + /** + * Checks whether the given node is in the current reports buffer. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is in the current buffer, false otherwise. + */ + function isInCurrentReportsBuffer(node) { + return reportsBuffer.reports.some(r => r.node === node); + } + + /** + * Removes the given node from the current reports buffer. + * @param {ASTNode} node Node to remove. + * @returns {void} + */ + function removeFromCurrentReportsBuffer(node) { + reportsBuffer.reports = reportsBuffer.reports.filter( + r => r.node !== node, + ); + } + + /** + * Checks whether a node is a MemberExpression at NewExpression's callee. + * @param {ASTNode} node node to check. + * @returns {boolean} True if the node is a MemberExpression at NewExpression's callee. false otherwise. + */ + function isMemberExpInNewCallee(node) { + if (node.type === "MemberExpression") { + return node.parent.type === "NewExpression" && + node.parent.callee === node + ? true + : node.parent.object === node && + isMemberExpInNewCallee(node.parent); + } + return false; + } + + /** + * Checks if the left-hand side of an assignment is an identifier, the operator is one of + * `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous class or function. + * + * As per https://tc39.es/ecma262/#sec-assignment-operators-runtime-semantics-evaluation, an + * assignment involving one of the operators `=`, `&&=`, `||=` or `??=` where the right-hand + * side is an anonymous class or function and the left-hand side is an *unparenthesized* + * identifier has different semantics than other assignments. + * Specifically, when an expression like `foo = function () {}` is evaluated, `foo.name` + * will be set to the string "foo", i.e. the identifier name. The same thing does not happen + * when evaluating `(foo) = function () {}`. + * Since the parenthesizing of the identifier in the left-hand side is significant in this + * special case, the parentheses, if present, should not be flagged as unnecessary. + * @param {ASTNode} node an AssignmentExpression node. + * @returns {boolean} `true` if the left-hand side of the assignment is an identifier, the + * operator is one of `=`, `&&=`, `||=` or `??=` and the right-hand side is an anonymous + * class or function; otherwise, `false`. + */ + function isAnonymousFunctionAssignmentException({ + left, + operator, + right, + }) { + if ( + left.type === "Identifier" && + ["=", "&&=", "||=", "??="].includes(operator) + ) { + const rhsType = right.type; + + if (rhsType === "ArrowFunctionExpression") { + return true; + } + if ( + (rhsType === "FunctionExpression" || + rhsType === "ClassExpression") && + !right.id + ) { + return true; + } + } + return false; + } + + return { + ArrayExpression(node) { + node.elements + .filter( + e => + e && + hasExcessParensWithPrecedence( + e, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ), + ) + .forEach(report); + }, + + ArrayPattern(node) { + node.elements + .filter(e => canBeAssignmentTarget(e) && hasExcessParens(e)) + .forEach(report); + }, + + ArrowFunctionExpression(node) { + if (isReturnAssignException(node)) { + return; + } + + if ( + node.body.type === "ConditionalExpression" && + IGNORE_ARROW_CONDITIONALS + ) { + return; + } + + if (node.body.type !== "BlockStatement") { + const firstBodyToken = sourceCode.getFirstToken( + node.body, + astUtils.isNotOpeningParenToken, + ); + const tokenBeforeFirst = + sourceCode.getTokenBefore(firstBodyToken); + + if ( + astUtils.isOpeningParenToken(tokenBeforeFirst) && + astUtils.isOpeningBraceToken(firstBodyToken) + ) { + tokensToIgnore.add(firstBodyToken); + } + if ( + hasExcessParensWithPrecedence( + node.body, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.body); + } + } + }, + + AssignmentExpression(node) { + if ( + canBeAssignmentTarget(node.left) && + hasExcessParens(node.left) && + (!isAnonymousFunctionAssignmentException(node) || + isParenthesisedTwice(node.left)) + ) { + report(node.left); + } + + if ( + !isReturnAssignException(node) && + hasExcessParensWithPrecedence(node.right, precedence(node)) + ) { + report(node.right); + } + }, + + BinaryExpression(node) { + if (reportsBuffer && node.operator === "in") { + reportsBuffer.inExpressionNodes.push(node); + } + + checkBinaryLogical(node); + }, + + CallExpression: checkCallNew, + + ConditionalExpression(node) { + if (isReturnAssignException(node)) { + return; + } + + const availableTypes = new Set([ + "BinaryExpression", + "LogicalExpression", + ]); + + if ( + !( + EXCEPT_COND_TERNARY && + availableTypes.has(node.test.type) + ) && + !isCondAssignException(node) && + hasExcessParensWithPrecedence( + node.test, + precedence({ + type: "LogicalExpression", + operator: "||", + }), + ) + ) { + report(node.test); + } + + if ( + !( + EXCEPT_COND_TERNARY && + availableTypes.has(node.consequent.type) + ) && + hasExcessParensWithPrecedence( + node.consequent, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.consequent); + } + + if ( + !( + EXCEPT_COND_TERNARY && + availableTypes.has(node.alternate.type) + ) && + hasExcessParensWithPrecedence( + node.alternate, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.alternate); + } + }, + + DoWhileStatement(node) { + if ( + hasExcessParens(node.test) && + !isCondAssignException(node) + ) { + report(node.test); + } + }, + + ExportDefaultDeclaration: node => + checkExpressionOrExportStatement(node.declaration), + ExpressionStatement: node => + checkExpressionOrExportStatement(node.expression), + + ForInStatement(node) { + if (node.left.type !== "VariableDeclaration") { + const firstLeftToken = sourceCode.getFirstToken( + node.left, + astUtils.isNotOpeningParenToken, + ); + + if ( + firstLeftToken.value === "let" && + astUtils.isOpeningBracketToken( + sourceCode.getTokenAfter( + firstLeftToken, + astUtils.isNotClosingParenToken, + ), + ) + ) { + // ForInStatement#left expression cannot start with `let[`. + tokensToIgnore.add(firstLeftToken); + } + } + + if (hasExcessParens(node.left)) { + report(node.left); + } + + if (hasExcessParens(node.right)) { + report(node.right); + } + }, + + ForOfStatement(node) { + if (node.left.type !== "VariableDeclaration") { + const firstLeftToken = sourceCode.getFirstToken( + node.left, + astUtils.isNotOpeningParenToken, + ); + + if (firstLeftToken.value === "let") { + // ForOfStatement#left expression cannot start with `let`. + tokensToIgnore.add(firstLeftToken); + } + } + + if (hasExcessParens(node.left)) { + report(node.left); + } + + if ( + hasExcessParensWithPrecedence( + node.right, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.right); + } + }, + + ForStatement(node) { + if ( + node.test && + hasExcessParens(node.test) && + !isCondAssignException(node) + ) { + report(node.test); + } + + if (node.update && hasExcessParens(node.update)) { + report(node.update); + } + + if (node.init) { + if (node.init.type !== "VariableDeclaration") { + const firstToken = sourceCode.getFirstToken( + node.init, + astUtils.isNotOpeningParenToken, + ); + + if ( + firstToken.value === "let" && + astUtils.isOpeningBracketToken( + sourceCode.getTokenAfter( + firstToken, + astUtils.isNotClosingParenToken, + ), + ) + ) { + // ForStatement#init expression cannot start with `let[`. + tokensToIgnore.add(firstToken); + } + } + + startNewReportsBuffering(); + + if (hasExcessParens(node.init)) { + report(node.init); + } + } + }, + + "ForStatement > *.init:exit"(node) { + /* + * Removing parentheses around `in` expressions might change semantics and cause errors. + * + * For example, this valid for loop: + * for (let a = (b in c); ;); + * after removing parentheses would be treated as an invalid for-in loop: + * for (let a = b in c; ;); + */ + + if (reportsBuffer.reports.length) { + reportsBuffer.inExpressionNodes.forEach( + inExpressionNode => { + const path = pathToDescendant( + node, + inExpressionNode, + ); + let nodeToExclude; + + for (let i = 0; i < path.length; i++) { + const pathNode = path[i]; + + if (i < path.length - 1) { + const nextPathNode = path[i + 1]; + + if ( + isSafelyEnclosingInExpression( + pathNode, + nextPathNode, + ) + ) { + // The 'in' expression in safely enclosed by the syntax of its ancestor nodes (e.g. by '{}' or '[]'). + return; + } + } + + if (isParenthesised(pathNode)) { + if (isInCurrentReportsBuffer(pathNode)) { + // This node was supposed to be reported, but parentheses might be necessary. + + if (isParenthesisedTwice(pathNode)) { + /* + * This node is parenthesised twice, it certainly has at least one pair of `extra` parentheses. + * If the --fix option is on, the current fixing iteration will remove only one pair of parentheses. + * The remaining pair is safely enclosing the 'in' expression. + */ + return; + } + + // Exclude the outermost node only. + if (!nodeToExclude) { + nodeToExclude = pathNode; + } + + // Don't break the loop here, there might be some safe nodes or parentheses that will stay inside. + } else { + // This node will stay parenthesised, the 'in' expression in safely enclosed by '()'. + return; + } + } + } + + // Exclude the node from the list (i.e. treat parentheses as necessary) + removeFromCurrentReportsBuffer(nodeToExclude); + }, + ); + } + + endCurrentReportsBuffering(); + }, + + IfStatement(node) { + if ( + hasExcessParens(node.test) && + !isCondAssignException(node) + ) { + report(node.test); + } + }, + + ImportExpression(node) { + const { source } = node; + + if (source.type === "SequenceExpression") { + if (hasDoubleExcessParens(source)) { + report(source); + } + } else if (hasExcessParens(source)) { + report(source); + } + }, + + LogicalExpression: checkBinaryLogical, + + MemberExpression(node) { + const shouldAllowWrapOnce = + isMemberExpInNewCallee(node) && + doesMemberExpressionContainCallExpression(node); + const nodeObjHasExcessParens = shouldAllowWrapOnce + ? hasDoubleExcessParens(node.object) + : hasExcessParens(node.object) && + !( + isImmediateFunctionPrototypeMethodCall( + node.parent, + ) && + node.parent.callee === node && + IGNORE_FUNCTION_PROTOTYPE_METHODS + ); + + if ( + nodeObjHasExcessParens && + precedence(node.object) >= precedence(node) && + (node.computed || + !( + astUtils.isDecimalInteger(node.object) || + // RegExp literal is allowed to have parens (#1589) + (node.object.type === "Literal" && + node.object.regex) + )) + ) { + report(node.object); + } + + if ( + nodeObjHasExcessParens && + node.object.type === "CallExpression" + ) { + report(node.object); + } + + if ( + nodeObjHasExcessParens && + !IGNORE_NEW_IN_MEMBER_EXPR && + node.object.type === "NewExpression" && + isNewExpressionWithParens(node.object) + ) { + report(node.object); + } + + if ( + nodeObjHasExcessParens && + node.optional && + node.object.type === "ChainExpression" + ) { + report(node.object); + } + + if (node.computed && hasExcessParens(node.property)) { + report(node.property); + } + }, + + "MethodDefinition[computed=true]"(node) { + if ( + hasExcessParensWithPrecedence( + node.key, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.key); + } + }, + + NewExpression: checkCallNew, + + ObjectExpression(node) { + node.properties + .filter( + property => + property.value && + hasExcessParensWithPrecedence( + property.value, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ), + ) + .forEach(property => report(property.value)); + }, + + ObjectPattern(node) { + node.properties + .filter(property => { + const value = property.value; + + return ( + canBeAssignmentTarget(value) && + hasExcessParens(value) + ); + }) + .forEach(property => report(property.value)); + }, + + Property(node) { + if (node.computed) { + const { key } = node; + + if ( + key && + hasExcessParensWithPrecedence( + key, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(key); + } + } + }, + + PropertyDefinition(node) { + if ( + node.computed && + hasExcessParensWithPrecedence( + node.key, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.key); + } + + if ( + node.value && + hasExcessParensWithPrecedence( + node.value, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(node.value); + } + }, + + RestElement(node) { + const argument = node.argument; + + if ( + canBeAssignmentTarget(argument) && + hasExcessParens(argument) + ) { + report(argument); + } + }, + + ReturnStatement(node) { + const returnToken = sourceCode.getFirstToken(node); + + if (isReturnAssignException(node)) { + return; + } + + if ( + node.argument && + hasExcessParensNoLineTerminator( + returnToken, + node.argument, + ) && + // RegExp literal is allowed to have parens (#1589) + !(node.argument.type === "Literal" && node.argument.regex) + ) { + report(node.argument); + } + }, + + SequenceExpression(node) { + const precedenceOfNode = precedence(node); + + node.expressions + .filter(e => + hasExcessParensWithPrecedence(e, precedenceOfNode), + ) + .forEach(report); + }, + + SwitchCase(node) { + if (node.test && hasExcessParens(node.test)) { + report(node.test); + } + }, + + SwitchStatement(node) { + if (hasExcessParens(node.discriminant)) { + report(node.discriminant); + } + }, + + ThrowStatement(node) { + const throwToken = sourceCode.getFirstToken(node); + + if ( + hasExcessParensNoLineTerminator(throwToken, node.argument) + ) { + report(node.argument); + } + }, + + UnaryExpression: checkArgumentWithPrecedence, + UpdateExpression(node) { + if (node.prefix) { + checkArgumentWithPrecedence(node); + } else { + const { argument } = node; + const operatorToken = sourceCode.getLastToken(node); + + if ( + argument.loc.end.line === operatorToken.loc.start.line + ) { + checkArgumentWithPrecedence(node); + } else { + if (hasDoubleExcessParens(argument)) { + report(argument); + } + } + } + }, + AwaitExpression: checkArgumentWithPrecedence, + + VariableDeclarator(node) { + if ( + node.init && + hasExcessParensWithPrecedence( + node.init, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) && + // RegExp literal is allowed to have parens (#1589) + !(node.init.type === "Literal" && node.init.regex) + ) { + report(node.init); + } + }, + + WhileStatement(node) { + if ( + hasExcessParens(node.test) && + !isCondAssignException(node) + ) { + report(node.test); + } + }, + + WithStatement(node) { + if (hasExcessParens(node.object)) { + report(node.object); + } + }, + + YieldExpression(node) { + if (node.argument) { + const yieldToken = sourceCode.getFirstToken(node); + + if ( + (precedence(node.argument) >= precedence(node) && + hasExcessParensNoLineTerminator( + yieldToken, + node.argument, + )) || + hasDoubleExcessParens(node.argument) + ) { + report(node.argument); + } + } + }, + + ClassDeclaration: checkClass, + ClassExpression: checkClass, + + SpreadElement: checkSpreadOperator, + SpreadProperty: checkSpreadOperator, + ExperimentalSpreadProperty: checkSpreadOperator, + + TemplateLiteral(node) { + node.expressions + .filter(e => e && hasExcessParens(e)) + .forEach(report); + }, + + AssignmentPattern(node) { + const { left, right } = node; + + if (canBeAssignmentTarget(left) && hasExcessParens(left)) { + report(left); + } + + if ( + right && + hasExcessParensWithPrecedence( + right, + PRECEDENCE_OF_ASSIGNMENT_EXPR, + ) + ) { + report(right); + } + }, + }; + }, }; diff --git a/lib/rules/no-extra-semi.js b/lib/rules/no-extra-semi.js index bd7d73f8ca67..041ec7f930a6 100644 --- a/lib/rules/no-extra-semi.js +++ b/lib/rules/no-extra-semi.js @@ -19,147 +19,149 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-extra-semi", - url: "https://eslint.style/rules/js/no-extra-semi" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Disallow unnecessary semicolons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-extra-semi" - }, - - fixable: "code", - schema: [], - - messages: { - unexpected: "Unnecessary semicolon." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Checks if a node or token is fixable. - * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes. - * @param {Token} nodeOrToken The node or token to check. - * @returns {boolean} Whether or not the node is fixable. - */ - function isFixable(nodeOrToken) { - const nextToken = sourceCode.getTokenAfter(nodeOrToken); - - if (!nextToken || nextToken.type !== "String") { - return true; - } - const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]); - - return !astUtils.isTopLevelExpressionStatement(stringNode.parent); - } - - /** - * Reports an unnecessary semicolon error. - * @param {Node|Token} nodeOrToken A node or a token to be reported. - * @returns {void} - */ - function report(nodeOrToken) { - context.report({ - node: nodeOrToken, - messageId: "unexpected", - fix: isFixable(nodeOrToken) - ? fixer => - - /* - * Expand the replacement range to include the surrounding - * tokens to avoid conflicting with semi. - * https://github.com/eslint/eslint/issues/7928 - */ - new FixTracker(fixer, context.sourceCode) - .retainSurroundingTokens(nodeOrToken) - .remove(nodeOrToken) - : null - }); - } - - /** - * Checks for a part of a class body. - * This checks tokens from a specified token to a next MethodDefinition or the end of class body. - * @param {Token} firstToken The first token to check. - * @returns {void} - */ - function checkForPartOfClassBody(firstToken) { - for (let token = firstToken; - token.type === "Punctuator" && !astUtils.isClosingBraceToken(token); - token = sourceCode.getTokenAfter(token) - ) { - if (astUtils.isSemicolonToken(token)) { - report(token); - } - } - } - - return { - - /** - * Reports this empty statement, except if the parent node is a loop. - * @param {Node} node A EmptyStatement node to be reported. - * @returns {void} - */ - EmptyStatement(node) { - const parent = node.parent, - allowedParentTypes = [ - "ForStatement", - "ForInStatement", - "ForOfStatement", - "WhileStatement", - "DoWhileStatement", - "IfStatement", - "LabeledStatement", - "WithStatement" - ]; - - if (!allowedParentTypes.includes(parent.type)) { - report(node); - } - }, - - /** - * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. - * @param {Node} node A ClassBody node to check. - * @returns {void} - */ - ClassBody(node) { - checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`. - }, - - /** - * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. - * @param {Node} node A MethodDefinition node of the start point. - * @returns {void} - */ - "MethodDefinition, PropertyDefinition, StaticBlock"(node) { - checkForPartOfClassBody(sourceCode.getTokenAfter(node)); - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-extra-semi", + url: "https://eslint.style/rules/js/no-extra-semi", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: "Disallow unnecessary semicolons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-extra-semi", + }, + + fixable: "code", + schema: [], + + messages: { + unexpected: "Unnecessary semicolon.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Checks if a node or token is fixable. + * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes. + * @param {Token} nodeOrToken The node or token to check. + * @returns {boolean} Whether or not the node is fixable. + */ + function isFixable(nodeOrToken) { + const nextToken = sourceCode.getTokenAfter(nodeOrToken); + + if (!nextToken || nextToken.type !== "String") { + return true; + } + const stringNode = sourceCode.getNodeByRangeIndex( + nextToken.range[0], + ); + + return !astUtils.isTopLevelExpressionStatement(stringNode.parent); + } + + /** + * Reports an unnecessary semicolon error. + * @param {Node|Token} nodeOrToken A node or a token to be reported. + * @returns {void} + */ + function report(nodeOrToken) { + context.report({ + node: nodeOrToken, + messageId: "unexpected", + fix: isFixable(nodeOrToken) + ? fixer => + /* + * Expand the replacement range to include the surrounding + * tokens to avoid conflicting with semi. + * https://github.com/eslint/eslint/issues/7928 + */ + new FixTracker(fixer, context.sourceCode) + .retainSurroundingTokens(nodeOrToken) + .remove(nodeOrToken) + : null, + }); + } + + /** + * Checks for a part of a class body. + * This checks tokens from a specified token to a next MethodDefinition or the end of class body. + * @param {Token} firstToken The first token to check. + * @returns {void} + */ + function checkForPartOfClassBody(firstToken) { + for ( + let token = firstToken; + token.type === "Punctuator" && + !astUtils.isClosingBraceToken(token); + token = sourceCode.getTokenAfter(token) + ) { + if (astUtils.isSemicolonToken(token)) { + report(token); + } + } + } + + return { + /** + * Reports this empty statement, except if the parent node is a loop. + * @param {Node} node A EmptyStatement node to be reported. + * @returns {void} + */ + EmptyStatement(node) { + const parent = node.parent, + allowedParentTypes = [ + "ForStatement", + "ForInStatement", + "ForOfStatement", + "WhileStatement", + "DoWhileStatement", + "IfStatement", + "LabeledStatement", + "WithStatement", + ]; + + if (!allowedParentTypes.includes(parent.type)) { + report(node); + } + }, + + /** + * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. + * @param {Node} node A ClassBody node to check. + * @returns {void} + */ + ClassBody(node) { + checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`. + }, + + /** + * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. + * @param {Node} node A MethodDefinition node of the start point. + * @returns {void} + */ + "MethodDefinition, PropertyDefinition, StaticBlock"(node) { + checkForPartOfClassBody(sourceCode.getTokenAfter(node)); + }, + }; + }, }; diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index 1057aae932d0..c2854192c7a5 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -22,14 +22,13 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/iu; * @returns {boolean} True if any segment is reachable; false otherwise. */ function isAnySegmentReachable(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; } /** @@ -39,7 +38,10 @@ function isAnySegmentReachable(segments) { * @returns {boolean} `true` if the comment string is truly a fallthrough comment. */ function isFallThroughComment(comment, fallthroughCommentPattern) { - return fallthroughCommentPattern.test(comment) && !directivesPattern.test(comment.trim()); + return ( + fallthroughCommentPattern.test(comment) && + !directivesPattern.test(comment.trim()) + ); } /** @@ -50,25 +52,46 @@ function isFallThroughComment(comment, fallthroughCommentPattern) { * @param {RegExp} fallthroughCommentPattern A pattern to match comment to. * @returns {null | object} the comment if the case has a valid fallthrough comment, otherwise null */ -function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, fallthroughCommentPattern) { - const sourceCode = context.sourceCode; - - if (caseWhichFallsThrough.consequent.length === 1 && caseWhichFallsThrough.consequent[0].type === "BlockStatement") { - const trailingCloseBrace = sourceCode.getLastToken(caseWhichFallsThrough.consequent[0]); - const commentInBlock = sourceCode.getCommentsBefore(trailingCloseBrace).pop(); - - if (commentInBlock && isFallThroughComment(commentInBlock.value, fallthroughCommentPattern)) { - return commentInBlock; - } - } - - const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); - - if (comment && isFallThroughComment(comment.value, fallthroughCommentPattern)) { - return comment; - } - - return null; +function getFallthroughComment( + caseWhichFallsThrough, + subsequentCase, + context, + fallthroughCommentPattern, +) { + const sourceCode = context.sourceCode; + + if ( + caseWhichFallsThrough.consequent.length === 1 && + caseWhichFallsThrough.consequent[0].type === "BlockStatement" + ) { + const trailingCloseBrace = sourceCode.getLastToken( + caseWhichFallsThrough.consequent[0], + ); + const commentInBlock = sourceCode + .getCommentsBefore(trailingCloseBrace) + .pop(); + + if ( + commentInBlock && + isFallThroughComment( + commentInBlock.value, + fallthroughCommentPattern, + ) + ) { + return commentInBlock; + } + } + + const comment = sourceCode.getCommentsBefore(subsequentCase).pop(); + + if ( + comment && + isFallThroughComment(comment.value, fallthroughCommentPattern) + ) { + return comment; + } + + return null; } /** @@ -78,7 +101,7 @@ function getFallthroughComment(caseWhichFallsThrough, subsequentCase, context, f * @returns {boolean} `true` if there are blank lines between node and token */ function hasBlankLinesBetween(node, token) { - return token.loc.start.line > node.loc.end.line + 1; + return token.loc.start.line > node.loc.end.line + 1; } //------------------------------------------------------------------------------ @@ -87,132 +110,151 @@ function hasBlankLinesBetween(node, token) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - allowEmptyCase: false, - reportUnusedFallthroughComment: false - }], - - docs: { - description: "Disallow fallthrough of `case` statements", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-fallthrough" - }, - - schema: [ - { - type: "object", - properties: { - commentPattern: { - type: "string" - }, - allowEmptyCase: { - type: "boolean" - }, - reportUnusedFallthroughComment: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - messages: { - unusedFallthroughComment: "Found a comment that would permit fallthrough, but case cannot fall through.", - case: "Expected a 'break' statement before 'case'.", - default: "Expected a 'break' statement before 'default'." - } - }, - - create(context) { - const codePathSegments = []; - let currentCodePathSegments = new Set(); - const sourceCode = context.sourceCode; - const [{ allowEmptyCase, commentPattern, reportUnusedFallthroughComment }] = context.options; - const fallthroughCommentPattern = commentPattern - ? new RegExp(commentPattern, "u") - : DEFAULT_FALLTHROUGH_COMMENT; - - /* - * We need to use leading comments of the next SwitchCase node because - * trailing comments is wrong if semicolons are omitted. - */ - let previousCase = null; - - return { - - onCodePathStart() { - codePathSegments.push(currentCodePathSegments); - currentCodePathSegments = new Set(); - }, - - onCodePathEnd() { - currentCodePathSegments = codePathSegments.pop(); - }, - - onUnreachableCodePathSegmentStart(segment) { - currentCodePathSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - onCodePathSegmentStart(segment) { - currentCodePathSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - - SwitchCase(node) { - - /* - * Checks whether or not there is a fallthrough comment. - * And reports the previous fallthrough node if that does not exist. - */ - - if (previousCase && previousCase.node.parent === node.parent) { - const previousCaseFallthroughComment = getFallthroughComment(previousCase.node, node, context, fallthroughCommentPattern); - - if (previousCase.isFallthrough && !(previousCaseFallthroughComment)) { - context.report({ - messageId: node.test ? "case" : "default", - node - }); - } else if (reportUnusedFallthroughComment && !previousCase.isSwitchExitReachable && previousCaseFallthroughComment) { - context.report({ - messageId: "unusedFallthroughComment", - node: previousCaseFallthroughComment - }); - } - - } - previousCase = null; - }, - - "SwitchCase:exit"(node) { - const nextToken = sourceCode.getTokenAfter(node); - - /* - * `reachable` meant fall through because statements preceded by - * `break`, `return`, or `throw` are unreachable. - * And allows empty cases and the last case. - */ - const isSwitchExitReachable = isAnySegmentReachable(currentCodePathSegments); - const isFallthrough = isSwitchExitReachable && (node.consequent.length > 0 || (!allowEmptyCase && hasBlankLinesBetween(node, nextToken))) && - node.parent.cases.at(-1) !== node; - - previousCase = { - node, - isSwitchExitReachable, - isFallthrough - }; - - } - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + allowEmptyCase: false, + reportUnusedFallthroughComment: false, + }, + ], + + docs: { + description: "Disallow fallthrough of `case` statements", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-fallthrough", + }, + + schema: [ + { + type: "object", + properties: { + commentPattern: { + type: "string", + }, + allowEmptyCase: { + type: "boolean", + }, + reportUnusedFallthroughComment: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + unusedFallthroughComment: + "Found a comment that would permit fallthrough, but case cannot fall through.", + case: "Expected a 'break' statement before 'case'.", + default: "Expected a 'break' statement before 'default'.", + }, + }, + + create(context) { + const codePathSegments = []; + let currentCodePathSegments = new Set(); + const sourceCode = context.sourceCode; + const [ + { allowEmptyCase, commentPattern, reportUnusedFallthroughComment }, + ] = context.options; + const fallthroughCommentPattern = commentPattern + ? new RegExp(commentPattern, "u") + : DEFAULT_FALLTHROUGH_COMMENT; + + /* + * We need to use leading comments of the next SwitchCase node because + * trailing comments is wrong if semicolons are omitted. + */ + let previousCase = null; + + return { + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); + }, + + onCodePathEnd() { + currentCodePathSegments = codePathSegments.pop(); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + SwitchCase(node) { + /* + * Checks whether or not there is a fallthrough comment. + * And reports the previous fallthrough node if that does not exist. + */ + + if (previousCase && previousCase.node.parent === node.parent) { + const previousCaseFallthroughComment = + getFallthroughComment( + previousCase.node, + node, + context, + fallthroughCommentPattern, + ); + + if ( + previousCase.isFallthrough && + !previousCaseFallthroughComment + ) { + context.report({ + messageId: node.test ? "case" : "default", + node, + }); + } else if ( + reportUnusedFallthroughComment && + !previousCase.isSwitchExitReachable && + previousCaseFallthroughComment + ) { + context.report({ + messageId: "unusedFallthroughComment", + node: previousCaseFallthroughComment, + }); + } + } + previousCase = null; + }, + + "SwitchCase:exit"(node) { + const nextToken = sourceCode.getTokenAfter(node); + + /* + * `reachable` meant fall through because statements preceded by + * `break`, `return`, or `throw` are unreachable. + * And allows empty cases and the last case. + */ + const isSwitchExitReachable = isAnySegmentReachable( + currentCodePathSegments, + ); + const isFallthrough = + isSwitchExitReachable && + (node.consequent.length > 0 || + (!allowEmptyCase && + hasBlankLinesBetween(node, nextToken))) && + node.parent.cases.at(-1) !== node; + + previousCase = { + node, + isSwitchExitReachable, + isFallthrough, + }; + }, + }; + }, }; diff --git a/lib/rules/no-floating-decimal.js b/lib/rules/no-floating-decimal.js index 72f916b4b684..d82d58df45de 100644 --- a/lib/rules/no-floating-decimal.js +++ b/lib/rules/no-floating-decimal.js @@ -18,74 +18,82 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-floating-decimal", - url: "https://eslint.style/rules/js/no-floating-decimal" - } - } - ] - }, - type: "suggestion", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-floating-decimal", + url: "https://eslint.style/rules/js/no-floating-decimal", + }, + }, + ], + }, + type: "suggestion", - docs: { - description: "Disallow leading or trailing decimal points in numeric literals", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-floating-decimal" - }, + docs: { + description: + "Disallow leading or trailing decimal points in numeric literals", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-floating-decimal", + }, - schema: [], - fixable: "code", - messages: { - leading: "A leading decimal point can be confused with a dot.", - trailing: "A trailing decimal point can be confused with a dot." - } - }, + schema: [], + fixable: "code", + messages: { + leading: "A leading decimal point can be confused with a dot.", + trailing: "A trailing decimal point can be confused with a dot.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; + create(context) { + const sourceCode = context.sourceCode; - return { - Literal(node) { + return { + Literal(node) { + if (typeof node.value === "number") { + if (node.raw.startsWith(".")) { + context.report({ + node, + messageId: "leading", + fix(fixer) { + const tokenBefore = + sourceCode.getTokenBefore(node); + const needsSpaceBefore = + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBefore, + `0${node.raw}`, + ); - if (typeof node.value === "number") { - if (node.raw.startsWith(".")) { - context.report({ - node, - messageId: "leading", - fix(fixer) { - const tokenBefore = sourceCode.getTokenBefore(node); - const needsSpaceBefore = tokenBefore && - tokenBefore.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, `0${node.raw}`); - - return fixer.insertTextBefore(node, needsSpaceBefore ? " 0" : "0"); - } - }); - } - if (node.raw.indexOf(".") === node.raw.length - 1) { - context.report({ - node, - messageId: "trailing", - fix: fixer => fixer.insertTextAfter(node, "0") - }); - } - } - } - }; - - } + return fixer.insertTextBefore( + node, + needsSpaceBefore ? " 0" : "0", + ); + }, + }); + } + if (node.raw.indexOf(".") === node.raw.length - 1) { + context.report({ + node, + messageId: "trailing", + fix: fixer => fixer.insertTextAfter(node, "0"), + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-func-assign.js b/lib/rules/no-func-assign.js index 8084af6eb4d2..96bbcc73b80b 100644 --- a/lib/rules/no-func-assign.js +++ b/lib/rules/no-func-assign.js @@ -13,66 +13,65 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - docs: { - description: "Disallow reassigning `function` declarations", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-func-assign" - }, + docs: { + description: "Disallow reassigning `function` declarations", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-func-assign", + }, - schema: [], + schema: [], - messages: { - isAFunction: "'{{name}}' is a function." - } - }, + messages: { + isAFunction: "'{{name}}' is a function.", + }, + }, - create(context) { + create(context) { + const sourceCode = context.sourceCode; - const sourceCode = context.sourceCode; + /** + * Reports a reference if is non initializer and writable. + * @param {References} references Collection of reference to check. + * @returns {void} + */ + function checkReference(references) { + astUtils.getModifyingReferences(references).forEach(reference => { + context.report({ + node: reference.identifier, + messageId: "isAFunction", + data: { + name: reference.identifier.name, + }, + }); + }); + } - /** - * Reports a reference if is non initializer and writable. - * @param {References} references Collection of reference to check. - * @returns {void} - */ - function checkReference(references) { - astUtils.getModifyingReferences(references).forEach(reference => { - context.report({ - node: reference.identifier, - messageId: "isAFunction", - data: { - name: reference.identifier.name - } - }); - }); - } + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "FunctionName") { + checkReference(variable.references); + } + } - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.defs[0].type === "FunctionName") { - checkReference(variable.references); - } - } + /** + * Checks parameters of a given function node. + * @param {ASTNode} node A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + sourceCode.getDeclaredVariables(node).forEach(checkVariable); + } - /** - * Checks parameters of a given function node. - * @param {ASTNode} node A function node to check. - * @returns {void} - */ - function checkForFunction(node) { - sourceCode.getDeclaredVariables(node).forEach(checkVariable); - } - - return { - FunctionDeclaration: checkForFunction, - FunctionExpression: checkForFunction - }; - } + return { + FunctionDeclaration: checkForFunction, + FunctionExpression: checkForFunction, + }; + }, }; diff --git a/lib/rules/no-global-assign.js b/lib/rules/no-global-assign.js index 0a6f65eb56ef..0e933df12e57 100644 --- a/lib/rules/no-global-assign.js +++ b/lib/rules/no-global-assign.js @@ -11,86 +11,91 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - defaultOptions: [{ exceptions: [] }], + defaultOptions: [{ exceptions: [] }], - docs: { - description: "Disallow assignments to native objects or read-only global variables", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-global-assign" - }, + docs: { + description: + "Disallow assignments to native objects or read-only global variables", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-global-assign", + }, - schema: [ - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { type: "string" }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], - messages: { - globalShouldNotBeModified: "Read-only global '{{name}}' should not be modified." - } - }, + messages: { + globalShouldNotBeModified: + "Read-only global '{{name}}' should not be modified.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; - const [{ exceptions }] = context.options; + create(context) { + const sourceCode = context.sourceCode; + const [{ exceptions }] = context.options; - /** - * Reports write references. - * @param {Reference} reference A reference to check. - * @param {int} index The index of the reference in the references. - * @param {Reference[]} references The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - const identifier = reference.identifier; + /** + * Reports write references. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; - if (reference.init === false && - reference.isWrite() && + if ( + reference.init === false && + reference.isWrite() && + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + messageId: "globalShouldNotBeModified", + data: { + name: identifier.name, + }, + }); + } + } - /* - * Destructuring assignments can have multiple default value, - * so possibly there are multiple writeable references for the same identifier. - */ - (index === 0 || references[index - 1].identifier !== identifier) - ) { - context.report({ - node: identifier, - messageId: "globalShouldNotBeModified", - data: { - name: identifier.name - } - }); - } - } + /** + * Reports write references if a given variable is read-only builtin. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if ( + variable.writeable === false && + !exceptions.includes(variable.name) + ) { + variable.references.forEach(checkReference); + } + } - /** - * Reports write references if a given variable is read-only builtin. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.writeable === false && !exceptions.includes(variable.name)) { - variable.references.forEach(checkReference); - } - } + return { + Program(node) { + const globalScope = sourceCode.getScope(node); - return { - Program(node) { - const globalScope = sourceCode.getScope(node); - - globalScope.variables.forEach(checkVariable); - } - }; - } + globalScope.variables.forEach(checkVariable); + }, + }; + }, }; diff --git a/lib/rules/no-implicit-coercion.js b/lib/rules/no-implicit-coercion.js index a1eab14f4b39..68211f22b007 100644 --- a/lib/rules/no-implicit-coercion.js +++ b/lib/rules/no-implicit-coercion.js @@ -20,11 +20,11 @@ const ALLOWABLE_OPERATORS = ["~", "!!", "+", "- -", "-", "*"]; * @returns {boolean} Whether or not the node is a double logical negating. */ function isDoubleLogicalNegating(node) { - return ( - node.operator === "!" && - node.argument.type === "UnaryExpression" && - node.argument.operator === "!" - ); + return ( + node.operator === "!" && + node.argument.type === "UnaryExpression" && + node.argument.operator === "!" + ); } /** @@ -33,15 +33,15 @@ function isDoubleLogicalNegating(node) { * @returns {boolean} Whether or not the node is a binary negating of `.indexOf()` method calling. */ function isBinaryNegatingOfIndexOf(node) { - if (node.operator !== "~") { - return false; - } - const callNode = astUtils.skipChainExpression(node.argument); - - return ( - callNode.type === "CallExpression" && - astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN) - ); + if (node.operator !== "~") { + return false; + } + const callNode = astUtils.skipChainExpression(node.argument); + + return ( + callNode.type === "CallExpression" && + astUtils.isSpecificMemberAccess(callNode.callee, null, INDEX_OF_PATTERN) + ); } /** @@ -50,10 +50,11 @@ function isBinaryNegatingOfIndexOf(node) { * @returns {boolean} Whether or not the node is a multiplying by one. */ function isMultiplyByOne(node) { - return node.operator === "*" && ( - node.left.type === "Literal" && node.left.value === 1 || - node.right.type === "Literal" && node.right.value === 1 - ); + return ( + node.operator === "*" && + ((node.left.type === "Literal" && node.left.value === 1) || + (node.right.type === "Literal" && node.right.value === 1)) + ); } /** @@ -65,13 +66,16 @@ function isMultiplyByOne(node) { * @returns {boolean} Whether or not the node is a multiplying by a fraction of `1`. */ function isMultiplyByFractionOfOne(node, sourceCode) { - return node.type === "BinaryExpression" && - node.operator === "*" && - (node.right.type === "Literal" && node.right.value === 1) && - node.parent.type === "BinaryExpression" && - node.parent.operator === "/" && - node.parent.left === node && - !astUtils.isParenthesised(sourceCode, node); + return ( + node.type === "BinaryExpression" && + node.operator === "*" && + node.right.type === "Literal" && + node.right.value === 1 && + node.parent.type === "BinaryExpression" && + node.parent.operator === "/" && + node.parent.left === node && + !astUtils.isParenthesised(sourceCode, node) + ); } /** @@ -80,14 +84,13 @@ function isMultiplyByFractionOfOne(node, sourceCode) { * @returns {boolean} true if the node is a number literal or a `Number()`, `parseInt` or `parseFloat` call */ function isNumeric(node) { - return ( - node.type === "Literal" && typeof node.value === "number" || - node.type === "CallExpression" && ( - node.callee.name === "Number" || - node.callee.name === "parseInt" || - node.callee.name === "parseFloat" - ) - ); + return ( + (node.type === "Literal" && typeof node.value === "number") || + (node.type === "CallExpression" && + (node.callee.name === "Number" || + node.callee.name === "parseInt" || + node.callee.name === "parseFloat")) + ); } /** @@ -98,18 +101,18 @@ function isNumeric(node) { * @returns {ASTNode|null} The first non-numeric item in the BinaryExpression tree or null */ function getNonNumericOperand(node) { - const left = node.left, - right = node.right; + const left = node.left, + right = node.right; - if (right.type !== "BinaryExpression" && !isNumeric(right)) { - return right; - } + if (right.type !== "BinaryExpression" && !isNumeric(right)) { + return right; + } - if (left.type !== "BinaryExpression" && !isNumeric(left)) { - return left; - } + if (left.type !== "BinaryExpression" && !isNumeric(left)) { + return left; + } - return null; + return null; } /** @@ -118,12 +121,12 @@ function getNonNumericOperand(node) { * @returns {boolean} Whether or not the expression evaluates to a string. */ function isStringType(node) { - return astUtils.isStringLiteral(node) || - ( - node.type === "CallExpression" && - node.callee.type === "Identifier" && - node.callee.name === "String" - ); + return ( + astUtils.isStringLiteral(node) || + (node.type === "CallExpression" && + node.callee.type === "Identifier" && + node.callee.name === "String") + ); } /** @@ -133,7 +136,13 @@ function isStringType(node) { * empty string literal or not. */ function isEmptyString(node) { - return astUtils.isStringLiteral(node) && (node.value === "" || (node.type === "TemplateLiteral" && node.quasis.length === 1 && node.quasis[0].value.cooked === "")); + return ( + astUtils.isStringLiteral(node) && + (node.value === "" || + (node.type === "TemplateLiteral" && + node.quasis.length === 1 && + node.quasis[0].value.cooked === "")) + ); } /** @@ -142,10 +151,11 @@ function isEmptyString(node) { * @returns {boolean} Whether or not the node is a concatenating with an empty string. */ function isConcatWithEmptyString(node) { - return node.operator === "+" && ( - (isEmptyString(node.left) && !isStringType(node.right)) || - (isEmptyString(node.right) && !isStringType(node.left)) - ); + return ( + node.operator === "+" && + ((isEmptyString(node.left) && !isStringType(node.right)) || + (isEmptyString(node.right) && !isStringType(node.left))) + ); } /** @@ -154,7 +164,7 @@ function isConcatWithEmptyString(node) { * @returns {boolean} Whether or not the node is appended with an empty string. */ function isAppendEmptyString(node) { - return node.operator === "+=" && isEmptyString(node.right); + return node.operator === "+=" && isEmptyString(node.right); } /** @@ -163,7 +173,7 @@ function isAppendEmptyString(node) { * @returns {ASTNode} The operand that is not an empty string from a flagged BinaryExpression. */ function getNonEmptyOperand(node) { - return isEmptyString(node.left) ? node.right : node.left; + return isEmptyString(node.left) ? node.right : node.left; } //------------------------------------------------------------------------------ @@ -172,241 +182,287 @@ function getNonEmptyOperand(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - - schema: [{ - type: "object", - properties: { - boolean: { - type: "boolean" - }, - number: { - type: "boolean" - }, - string: { - type: "boolean" - }, - disallowTemplateShorthand: { - type: "boolean" - }, - allow: { - type: "array", - items: { - enum: ALLOWABLE_OPERATORS - }, - uniqueItems: true - } - }, - additionalProperties: false - }], - - defaultOptions: [{ - allow: [], - boolean: true, - disallowTemplateShorthand: false, - number: true, - string: true - }], - - messages: { - implicitCoercion: "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.", - useRecommendation: "Use `{{recommendation}}` instead." - } - }, - - create(context) { - const [options] = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports an error and autofixes the node - * @param {ASTNode} node An ast node to report the error on. - * @param {string} recommendation The recommended code for the issue - * @param {bool} shouldSuggest Whether this report should offer a suggestion - * @param {bool} shouldFix Whether this report should fix the node - * @returns {void} - */ - function report(node, recommendation, shouldSuggest, shouldFix) { - - /** - * Fix function - * @param {RuleFixer} fixer The fixer to fix. - * @returns {Fix} The fix object. - */ - function fix(fixer) { - const tokenBefore = sourceCode.getTokenBefore(node); - - if ( - tokenBefore?.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, recommendation) - ) { - return fixer.replaceText(node, ` ${recommendation}`); - } - - return fixer.replaceText(node, recommendation); - } - - context.report({ - node, - messageId: "implicitCoercion", - data: { recommendation }, - fix(fixer) { - if (!shouldFix) { - return null; - } - - return fix(fixer); - }, - suggest: [ - { - messageId: "useRecommendation", - data: { recommendation }, - fix(fixer) { - if (shouldFix || !shouldSuggest) { - return null; - } - - return fix(fixer); - } - } - ] - }); - } - - return { - UnaryExpression(node) { - let operatorAllowed; - - // !!foo - operatorAllowed = options.allow.includes("!!"); - if (!operatorAllowed && options.boolean && isDoubleLogicalNegating(node)) { - const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; - const variable = astUtils.getVariableByName(sourceCode.getScope(node), "Boolean"); - const booleanExists = variable?.identifiers.length === 0; - - report(node, recommendation, true, booleanExists); - } - - // ~foo.indexOf(bar) - operatorAllowed = options.allow.includes("~"); - if (!operatorAllowed && options.boolean && isBinaryNegatingOfIndexOf(node)) { - - // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case. - const comparison = node.argument.type === "ChainExpression" ? ">= 0" : "!== -1"; - const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`; - - report(node, recommendation, false, false); - } - - // +foo - operatorAllowed = options.allow.includes("+"); - if (!operatorAllowed && options.number && node.operator === "+" && !isNumeric(node.argument)) { - const recommendation = `Number(${sourceCode.getText(node.argument)})`; - - report(node, recommendation, true, false); - } - - // -(-foo) - operatorAllowed = options.allow.includes("- -"); - if (!operatorAllowed && options.number && node.operator === "-" && node.argument.type === "UnaryExpression" && node.argument.operator === "-" && !isNumeric(node.argument.argument)) { - const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`; - - report(node, recommendation, true, false); - } - }, - - // Use `:exit` to prevent double reporting - "BinaryExpression:exit"(node) { - let operatorAllowed; - - // 1 * foo - operatorAllowed = options.allow.includes("*"); - const nonNumericOperand = !operatorAllowed && options.number && isMultiplyByOne(node) && !isMultiplyByFractionOfOne(node, sourceCode) && - getNonNumericOperand(node); - - if (nonNumericOperand) { - const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; - - report(node, recommendation, true, false); - } - - // foo - 0 - operatorAllowed = options.allow.includes("-"); - if (!operatorAllowed && options.number && node.operator === "-" && node.right.type === "Literal" && node.right.value === 0 && !isNumeric(node.left)) { - const recommendation = `Number(${sourceCode.getText(node.left)})`; - - report(node, recommendation, true, false); - } - - // "" + foo - operatorAllowed = options.allow.includes("+"); - if (!operatorAllowed && options.string && isConcatWithEmptyString(node)) { - const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`; - - report(node, recommendation, true, false); - } - }, - - AssignmentExpression(node) { - - // foo += "" - const operatorAllowed = options.allow.includes("+"); - - if (!operatorAllowed && options.string && isAppendEmptyString(node)) { - const code = sourceCode.getText(getNonEmptyOperand(node)); - const recommendation = `${code} = String(${code})`; - - report(node, recommendation, true, false); - } - }, - - TemplateLiteral(node) { - if (!options.disallowTemplateShorthand) { - return; - } - - // tag`${foo}` - if (node.parent.type === "TaggedTemplateExpression") { - return; - } - - // `` or `${foo}${bar}` - if (node.expressions.length !== 1) { - return; - } - - - // `prefix${foo}` - if (node.quasis[0].value.cooked !== "") { - return; - } - - // `${foo}postfix` - if (node.quasis[1].value.cooked !== "") { - return; - } - - // if the expression is already a string, then this isn't a coercion - if (isStringType(node.expressions[0])) { - return; - } - - const code = sourceCode.getText(node.expressions[0]); - const recommendation = `String(${code})`; - - report(node, recommendation, true, false); - } - }; - } + meta: { + 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", + + schema: [ + { + type: "object", + properties: { + boolean: { + type: "boolean", + }, + number: { + type: "boolean", + }, + string: { + type: "boolean", + }, + disallowTemplateShorthand: { + type: "boolean", + }, + allow: { + type: "array", + items: { + enum: ALLOWABLE_OPERATORS, + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [ + { + allow: [], + boolean: true, + disallowTemplateShorthand: false, + number: true, + string: true, + }, + ], + + messages: { + implicitCoercion: + "Unexpected implicit coercion encountered. Use `{{recommendation}}` instead.", + useRecommendation: "Use `{{recommendation}}` instead.", + }, + }, + + create(context) { + const [options] = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports an error and autofixes the node + * @param {ASTNode} node An ast node to report the error on. + * @param {string} recommendation The recommended code for the issue + * @param {bool} shouldSuggest Whether this report should offer a suggestion + * @param {bool} shouldFix Whether this report should fix the node + * @returns {void} + */ + function report(node, recommendation, shouldSuggest, shouldFix) { + /** + * Fix function + * @param {RuleFixer} fixer The fixer to fix. + * @returns {Fix} The fix object. + */ + function fix(fixer) { + const tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore?.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, recommendation) + ) { + return fixer.replaceText(node, ` ${recommendation}`); + } + + return fixer.replaceText(node, recommendation); + } + + context.report({ + node, + messageId: "implicitCoercion", + data: { recommendation }, + fix(fixer) { + if (!shouldFix) { + return null; + } + + return fix(fixer); + }, + suggest: [ + { + messageId: "useRecommendation", + data: { recommendation }, + fix(fixer) { + if (shouldFix || !shouldSuggest) { + return null; + } + + return fix(fixer); + }, + }, + ], + }); + } + + return { + UnaryExpression(node) { + let operatorAllowed; + + // !!foo + operatorAllowed = options.allow.includes("!!"); + if ( + !operatorAllowed && + options.boolean && + isDoubleLogicalNegating(node) + ) { + const recommendation = `Boolean(${sourceCode.getText(node.argument.argument)})`; + const variable = astUtils.getVariableByName( + sourceCode.getScope(node), + "Boolean", + ); + const booleanExists = variable?.identifiers.length === 0; + + report(node, recommendation, true, booleanExists); + } + + // ~foo.indexOf(bar) + operatorAllowed = options.allow.includes("~"); + if ( + !operatorAllowed && + options.boolean && + isBinaryNegatingOfIndexOf(node) + ) { + // `foo?.indexOf(bar) !== -1` will be true (== found) if the `foo` is nullish. So use `>= 0` in that case. + const comparison = + node.argument.type === "ChainExpression" + ? ">= 0" + : "!== -1"; + const recommendation = `${sourceCode.getText(node.argument)} ${comparison}`; + + report(node, recommendation, false, false); + } + + // +foo + operatorAllowed = options.allow.includes("+"); + if ( + !operatorAllowed && + options.number && + node.operator === "+" && + !isNumeric(node.argument) + ) { + const recommendation = `Number(${sourceCode.getText(node.argument)})`; + + report(node, recommendation, true, false); + } + + // -(-foo) + operatorAllowed = options.allow.includes("- -"); + if ( + !operatorAllowed && + options.number && + node.operator === "-" && + node.argument.type === "UnaryExpression" && + node.argument.operator === "-" && + !isNumeric(node.argument.argument) + ) { + const recommendation = `Number(${sourceCode.getText(node.argument.argument)})`; + + report(node, recommendation, true, false); + } + }, + + // Use `:exit` to prevent double reporting + "BinaryExpression:exit"(node) { + let operatorAllowed; + + // 1 * foo + operatorAllowed = options.allow.includes("*"); + const nonNumericOperand = + !operatorAllowed && + options.number && + isMultiplyByOne(node) && + !isMultiplyByFractionOfOne(node, sourceCode) && + getNonNumericOperand(node); + + if (nonNumericOperand) { + const recommendation = `Number(${sourceCode.getText(nonNumericOperand)})`; + + report(node, recommendation, true, false); + } + + // foo - 0 + operatorAllowed = options.allow.includes("-"); + if ( + !operatorAllowed && + options.number && + node.operator === "-" && + node.right.type === "Literal" && + node.right.value === 0 && + !isNumeric(node.left) + ) { + const recommendation = `Number(${sourceCode.getText(node.left)})`; + + report(node, recommendation, true, false); + } + + // "" + foo + operatorAllowed = options.allow.includes("+"); + if ( + !operatorAllowed && + options.string && + isConcatWithEmptyString(node) + ) { + const recommendation = `String(${sourceCode.getText(getNonEmptyOperand(node))})`; + + report(node, recommendation, true, false); + } + }, + + AssignmentExpression(node) { + // foo += "" + const operatorAllowed = options.allow.includes("+"); + + if ( + !operatorAllowed && + options.string && + isAppendEmptyString(node) + ) { + const code = sourceCode.getText(getNonEmptyOperand(node)); + const recommendation = `${code} = String(${code})`; + + report(node, recommendation, true, false); + } + }, + + TemplateLiteral(node) { + if (!options.disallowTemplateShorthand) { + return; + } + + // tag`${foo}` + if (node.parent.type === "TaggedTemplateExpression") { + return; + } + + // `` or `${foo}${bar}` + if (node.expressions.length !== 1) { + return; + } + + // `prefix${foo}` + if (node.quasis[0].value.cooked !== "") { + return; + } + + // `${foo}postfix` + if (node.quasis[1].value.cooked !== "") { + return; + } + + // if the expression is already a string, then this isn't a coercion + if (isStringType(node.expressions[0])) { + return; + } + + const code = sourceCode.getText(node.expressions[0]); + const recommendation = `String(${code})`; + + report(node, recommendation, true, false); + }, + }; + }, }; diff --git a/lib/rules/no-implicit-globals.js b/lib/rules/no-implicit-globals.js index 0ed96e8aa491..fc2e9e991a74 100644 --- a/lib/rules/no-implicit-globals.js +++ b/lib/rules/no-implicit-globals.js @@ -11,138 +11,161 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - lexicalBindings: false - }], - - docs: { - description: "Disallow declarations in the global scope", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-implicit-globals" - }, - - schema: [{ - type: "object", - properties: { - lexicalBindings: { - type: "boolean" - } - }, - additionalProperties: false - }], - - messages: { - globalNonLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.", - globalLexicalBinding: "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.", - globalVariableLeak: "Global variable leak, declare the variable if it is intended to be local.", - assignmentToReadonlyGlobal: "Unexpected assignment to read-only global variable.", - redeclarationOfReadonlyGlobal: "Unexpected redeclaration of read-only global variable." - } - }, - - create(context) { - const [{ lexicalBindings: checkLexicalBindings }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Reports the node. - * @param {ASTNode} node Node to report. - * @param {string} messageId Id of the message to report. - * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class. - * @returns {void} - */ - function report(node, messageId, kind) { - context.report({ - node, - messageId, - data: { - kind - } - }); - } - - return { - Program(node) { - const scope = sourceCode.getScope(node); - - scope.variables.forEach(variable => { - - // Only ESLint global variables have the `writable` key. - const isReadonlyEslintGlobalVariable = variable.writeable === false; - const isWritableEslintGlobalVariable = variable.writeable === true; - - if (isWritableEslintGlobalVariable) { - - // Everything is allowed with writable ESLint global variables. - return; - } - - // Variables exported by "exported" block comments - if (variable.eslintExported) { - return; - } - - variable.defs.forEach(def => { - const defNode = def.node; - - if (def.type === "FunctionName" || (def.type === "Variable" && def.parent.kind === "var")) { - if (isReadonlyEslintGlobalVariable) { - report(defNode, "redeclarationOfReadonlyGlobal"); - } else { - report( - defNode, - "globalNonLexicalBinding", - def.type === "FunctionName" ? "function" : `'${def.parent.kind}'` - ); - } - } - - if (checkLexicalBindings) { - if (def.type === "ClassName" || - (def.type === "Variable" && (def.parent.kind === "let" || def.parent.kind === "const"))) { - if (isReadonlyEslintGlobalVariable) { - report(defNode, "redeclarationOfReadonlyGlobal"); - } else { - report( - defNode, - "globalLexicalBinding", - def.type === "ClassName" ? "class" : `'${def.parent.kind}'` - ); - } - } - } - }); - }); - - // Undeclared assigned variables. - scope.implicit.variables.forEach(variable => { - const scopeVariable = scope.set.get(variable.name); - let messageId; - - if (scopeVariable) { - - // ESLint global variable - if (scopeVariable.writeable) { - return; - } - messageId = "assignmentToReadonlyGlobal"; - - } else { - - // Reference to an unknown variable, possible global leak. - messageId = "globalVariableLeak"; - } - - // def.node is an AssignmentExpression, ForInStatement or ForOfStatement. - variable.defs.forEach(def => { - report(def.node, messageId); - }); - }); - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + lexicalBindings: false, + }, + ], + + docs: { + description: "Disallow declarations in the global scope", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-implicit-globals", + }, + + schema: [ + { + type: "object", + properties: { + lexicalBindings: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + globalNonLexicalBinding: + "Unexpected {{kind}} declaration in the global scope, wrap in an IIFE for a local variable, assign as global property for a global variable.", + globalLexicalBinding: + "Unexpected {{kind}} declaration in the global scope, wrap in a block or in an IIFE.", + globalVariableLeak: + "Global variable leak, declare the variable if it is intended to be local.", + assignmentToReadonlyGlobal: + "Unexpected assignment to read-only global variable.", + redeclarationOfReadonlyGlobal: + "Unexpected redeclaration of read-only global variable.", + }, + }, + + create(context) { + const [{ lexicalBindings: checkLexicalBindings }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Reports the node. + * @param {ASTNode} node Node to report. + * @param {string} messageId Id of the message to report. + * @param {string|undefined} kind Declaration kind, can be 'var', 'const', 'let', function or class. + * @returns {void} + */ + function report(node, messageId, kind) { + context.report({ + node, + messageId, + data: { + kind, + }, + }); + } + + return { + Program(node) { + const scope = sourceCode.getScope(node); + + scope.variables.forEach(variable => { + // Only ESLint global variables have the `writable` key. + const isReadonlyEslintGlobalVariable = + variable.writeable === false; + const isWritableEslintGlobalVariable = + variable.writeable === true; + + if (isWritableEslintGlobalVariable) { + // Everything is allowed with writable ESLint global variables. + return; + } + + // Variables exported by "exported" block comments + if (variable.eslintExported) { + return; + } + + variable.defs.forEach(def => { + const defNode = def.node; + + if ( + def.type === "FunctionName" || + (def.type === "Variable" && + def.parent.kind === "var") + ) { + if (isReadonlyEslintGlobalVariable) { + report( + defNode, + "redeclarationOfReadonlyGlobal", + ); + } else { + report( + defNode, + "globalNonLexicalBinding", + def.type === "FunctionName" + ? "function" + : `'${def.parent.kind}'`, + ); + } + } + + if (checkLexicalBindings) { + if ( + def.type === "ClassName" || + (def.type === "Variable" && + (def.parent.kind === "let" || + def.parent.kind === "const")) + ) { + if (isReadonlyEslintGlobalVariable) { + report( + defNode, + "redeclarationOfReadonlyGlobal", + ); + } else { + report( + defNode, + "globalLexicalBinding", + def.type === "ClassName" + ? "class" + : `'${def.parent.kind}'`, + ); + } + } + } + }); + }); + + // Undeclared assigned variables. + scope.implicit.variables.forEach(variable => { + const scopeVariable = scope.set.get(variable.name); + let messageId; + + if (scopeVariable) { + // ESLint global variable + if (scopeVariable.writeable) { + return; + } + messageId = "assignmentToReadonlyGlobal"; + } else { + // Reference to an unknown variable, possible global leak. + messageId = "globalVariableLeak"; + } + + // def.node is an AssignmentExpression, ForInStatement or ForOfStatement. + variable.defs.forEach(def => { + report(def.node, messageId); + }); + }); + }, + }; + }, }; diff --git a/lib/rules/no-implied-eval.js b/lib/rules/no-implied-eval.js index 9a84f8cbaf14..26fdd643f18d 100644 --- a/lib/rules/no-implied-eval.js +++ b/lib/rules/no-implied-eval.js @@ -18,115 +18,143 @@ const { getStaticValue } = require("@eslint-community/eslint-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of `eval()`-like methods", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-implied-eval" - }, - - schema: [], - - messages: { - impliedEval: "Implied eval. Consider passing a function instead of a string." - } - }, - - create(context) { - const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]); - const EVAL_LIKE_FUNC_PATTERN = /^(?:set(?:Interval|Timeout)|execScript)$/u; - const sourceCode = context.sourceCode; - - /** - * Checks whether a node is evaluated as a string or not. - * @param {ASTNode} node A node to check. - * @returns {boolean} True if the node is evaluated as a string. - */ - function isEvaluatedString(node) { - if ( - (node.type === "Literal" && typeof node.value === "string") || - node.type === "TemplateLiteral" - ) { - return true; - } - if (node.type === "BinaryExpression" && node.operator === "+") { - return isEvaluatedString(node.left) || isEvaluatedString(node.right); - } - return false; - } - - /** - * Reports if the `CallExpression` node has evaluated argument. - * @param {ASTNode} node A CallExpression to check. - * @returns {void} - */ - function reportImpliedEvalCallExpression(node) { - const [firstArgument] = node.arguments; - - if (firstArgument) { - - const staticValue = getStaticValue(firstArgument, sourceCode.getScope(node)); - const isStaticString = staticValue && typeof staticValue.value === "string"; - const isString = isStaticString || isEvaluatedString(firstArgument); - - if (isString) { - context.report({ - node, - messageId: "impliedEval" - }); - } - } - - } - - /** - * Reports calls of `implied eval` via the global references. - * @param {Variable} globalVar A global variable to check. - * @returns {void} - */ - function reportImpliedEvalViaGlobal(globalVar) { - const { references, name } = globalVar; - - references.forEach(ref => { - const identifier = ref.identifier; - let node = identifier.parent; - - while (astUtils.isSpecificMemberAccess(node, null, name)) { - node = node.parent; - } - - if (astUtils.isSpecificMemberAccess(node, null, EVAL_LIKE_FUNC_PATTERN)) { - const calleeNode = node.parent.type === "ChainExpression" ? node.parent : node; - const parent = calleeNode.parent; - - if (parent.type === "CallExpression" && parent.callee === calleeNode) { - reportImpliedEvalCallExpression(parent); - } - } - }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - CallExpression(node) { - if (astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN)) { - reportImpliedEvalCallExpression(node); - } - }, - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - - GLOBAL_CANDIDATES - .map(candidate => astUtils.getVariableByName(globalScope, candidate)) - .filter(globalVar => !!globalVar && globalVar.defs.length === 0) - .forEach(reportImpliedEvalViaGlobal); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow the use of `eval()`-like methods", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-implied-eval", + }, + + schema: [], + + messages: { + impliedEval: + "Implied eval. Consider passing a function instead of a string.", + }, + }, + + create(context) { + const GLOBAL_CANDIDATES = Object.freeze([ + "global", + "window", + "globalThis", + ]); + const EVAL_LIKE_FUNC_PATTERN = + /^(?:set(?:Interval|Timeout)|execScript)$/u; + const sourceCode = context.sourceCode; + + /** + * Checks whether a node is evaluated as a string or not. + * @param {ASTNode} node A node to check. + * @returns {boolean} True if the node is evaluated as a string. + */ + function isEvaluatedString(node) { + if ( + (node.type === "Literal" && typeof node.value === "string") || + node.type === "TemplateLiteral" + ) { + return true; + } + if (node.type === "BinaryExpression" && node.operator === "+") { + return ( + isEvaluatedString(node.left) || + isEvaluatedString(node.right) + ); + } + return false; + } + + /** + * Reports if the `CallExpression` node has evaluated argument. + * @param {ASTNode} node A CallExpression to check. + * @returns {void} + */ + function reportImpliedEvalCallExpression(node) { + const [firstArgument] = node.arguments; + + if (firstArgument) { + const staticValue = getStaticValue( + firstArgument, + sourceCode.getScope(node), + ); + const isStaticString = + staticValue && typeof staticValue.value === "string"; + const isString = + isStaticString || isEvaluatedString(firstArgument); + + if (isString) { + context.report({ + node, + messageId: "impliedEval", + }); + } + } + } + + /** + * Reports calls of `implied eval` via the global references. + * @param {Variable} globalVar A global variable to check. + * @returns {void} + */ + function reportImpliedEvalViaGlobal(globalVar) { + const { references, name } = globalVar; + + references.forEach(ref => { + const identifier = ref.identifier; + let node = identifier.parent; + + while (astUtils.isSpecificMemberAccess(node, null, name)) { + node = node.parent; + } + + if ( + astUtils.isSpecificMemberAccess( + node, + null, + EVAL_LIKE_FUNC_PATTERN, + ) + ) { + const calleeNode = + node.parent.type === "ChainExpression" + ? node.parent + : node; + const parent = calleeNode.parent; + + if ( + parent.type === "CallExpression" && + parent.callee === calleeNode + ) { + reportImpliedEvalCallExpression(parent); + } + } + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + CallExpression(node) { + if ( + astUtils.isSpecificId(node.callee, EVAL_LIKE_FUNC_PATTERN) + ) { + reportImpliedEvalCallExpression(node); + } + }, + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + GLOBAL_CANDIDATES.map(candidate => + astUtils.getVariableByName(globalScope, candidate), + ) + .filter( + globalVar => !!globalVar && globalVar.defs.length === 0, + ) + .forEach(reportImpliedEvalViaGlobal); + }, + }; + }, }; diff --git a/lib/rules/no-import-assign.js b/lib/rules/no-import-assign.js index c69988666ab8..327903995d36 100644 --- a/lib/rules/no-import-assign.js +++ b/lib/rules/no-import-assign.js @@ -13,8 +13,8 @@ const { findVariable } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils"); const WellKnownMutationFunctions = { - Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u, - Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u + Object: /^(?:assign|definePropert(?:y|ies)|freeze|setPrototypeOf)$/u, + Reflect: /^(?:(?:define|delete)Property|set(?:PrototypeOf)?)$/u, }; /** @@ -23,27 +23,18 @@ const WellKnownMutationFunctions = { * @returns {boolean} `true` if the node is LHS. */ function isAssignmentLeft(node) { - const { parent } = node; - - return ( - ( - parent.type === "AssignmentExpression" && - parent.left === node - ) || - - // Destructuring assignments - parent.type === "ArrayPattern" || - ( - parent.type === "Property" && - parent.value === node && - parent.parent.type === "ObjectPattern" - ) || - parent.type === "RestElement" || - ( - parent.type === "AssignmentPattern" && - parent.left === node - ) - ); + const { parent } = node; + + return ( + (parent.type === "AssignmentExpression" && parent.left === node) || + // Destructuring assignments + parent.type === "ArrayPattern" || + (parent.type === "Property" && + parent.value === node && + parent.parent.type === "ObjectPattern") || + parent.type === "RestElement" || + (parent.type === "AssignmentPattern" && parent.left === node) + ); } /** @@ -52,22 +43,17 @@ function isAssignmentLeft(node) { * @returns {boolean} `true` if the node is the operand of mutation unary operator. */ function isOperandOfMutationUnaryOperator(node) { - const argumentNode = node.parent.type === "ChainExpression" - ? node.parent - : node; - const { parent } = argumentNode; - - return ( - ( - parent.type === "UpdateExpression" && - parent.argument === argumentNode - ) || - ( - parent.type === "UnaryExpression" && - parent.operator === "delete" && - parent.argument === argumentNode - ) - ); + const argumentNode = + node.parent.type === "ChainExpression" ? node.parent : node; + const { parent } = argumentNode; + + return ( + (parent.type === "UpdateExpression" && + parent.argument === argumentNode) || + (parent.type === "UnaryExpression" && + parent.operator === "delete" && + parent.argument === argumentNode) + ); } /** @@ -76,18 +62,12 @@ function isOperandOfMutationUnaryOperator(node) { * @returns {boolean} `true` if the node is the iteration variable. */ function isIterationVariable(node) { - const { parent } = node; - - return ( - ( - parent.type === "ForInStatement" && - parent.left === node - ) || - ( - parent.type === "ForOfStatement" && - parent.left === node - ) - ); + const { parent } = node; + + return ( + (parent.type === "ForInStatement" && parent.left === node) || + (parent.type === "ForOfStatement" && parent.left === node) + ); } /** @@ -106,22 +86,30 @@ function isIterationVariable(node) { * @returns {boolean} `true` if the node is at the first argument of a well-known mutation function. */ function isArgumentOfWellKnownMutationFunction(node, scope) { - const { parent } = node; - - if (parent.type !== "CallExpression" || parent.arguments[0] !== node) { - return false; - } - const callee = astUtils.skipChainExpression(parent.callee); - - if ( - !astUtils.isSpecificMemberAccess(callee, "Object", WellKnownMutationFunctions.Object) && - !astUtils.isSpecificMemberAccess(callee, "Reflect", WellKnownMutationFunctions.Reflect) - ) { - return false; - } - const variable = findVariable(scope, callee.object); - - return variable !== null && variable.scope.type === "global"; + const { parent } = node; + + if (parent.type !== "CallExpression" || parent.arguments[0] !== node) { + return false; + } + const callee = astUtils.skipChainExpression(parent.callee); + + if ( + !astUtils.isSpecificMemberAccess( + callee, + "Object", + WellKnownMutationFunctions.Object, + ) && + !astUtils.isSpecificMemberAccess( + callee, + "Reflect", + WellKnownMutationFunctions.Reflect, + ) + ) { + return false; + } + const variable = findVariable(scope, callee.object); + + return variable !== null && variable.scope.type === "global"; } /** @@ -131,20 +119,16 @@ function isArgumentOfWellKnownMutationFunction(node, scope) { * @returns {boolean} `true` if the member of `id` was updated. */ function isMemberWrite(id, scope) { - const { parent } = id; - - return ( - ( - parent.type === "MemberExpression" && - parent.object === id && - ( - isAssignmentLeft(parent) || - isOperandOfMutationUnaryOperator(parent) || - isIterationVariable(parent) - ) - ) || - isArgumentOfWellKnownMutationFunction(id, scope) - ); + const { parent } = id; + + return ( + (parent.type === "MemberExpression" && + parent.object === id && + (isAssignmentLeft(parent) || + isOperandOfMutationUnaryOperator(parent) || + isIterationVariable(parent))) || + isArgumentOfWellKnownMutationFunction(id, scope) + ); } /** @@ -153,21 +137,21 @@ function isMemberWrite(id, scope) { * @returns {ASTNode} The mutation node. */ function getWriteNode(id) { - let node = id.parent; - - while ( - node && - node.type !== "AssignmentExpression" && - node.type !== "UpdateExpression" && - node.type !== "UnaryExpression" && - node.type !== "CallExpression" && - node.type !== "ForInStatement" && - node.type !== "ForOfStatement" - ) { - node = node.parent; - } - - return node || id; + let node = id.parent; + + while ( + node && + node.type !== "AssignmentExpression" && + node.type !== "UpdateExpression" && + node.type !== "UnaryExpression" && + node.type !== "CallExpression" && + node.type !== "ForInStatement" && + node.type !== "ForOfStatement" + ) { + node = node.parent; + } + + return node || id; } //------------------------------------------------------------------------------ @@ -176,66 +160,68 @@ function getWriteNode(id) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow assigning to imported bindings", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-import-assign" - }, - - schema: [], - - messages: { - readonly: "'{{name}}' is read-only.", - readonlyMember: "The members of '{{name}}' are read-only." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - ImportDeclaration(node) { - const scope = sourceCode.getScope(node); - - for (const variable of sourceCode.getDeclaredVariables(node)) { - const shouldCheckMembers = variable.defs.some( - d => d.node.type === "ImportNamespaceSpecifier" - ); - let prevIdNode = null; - - for (const reference of variable.references) { - const idNode = reference.identifier; - - /* - * AssignmentPattern (e.g. `[a = 0] = b`) makes two write - * references for the same identifier. This should skip - * the one of the two in order to prevent redundant reports. - */ - if (idNode === prevIdNode) { - continue; - } - prevIdNode = idNode; - - if (reference.isWrite()) { - context.report({ - node: getWriteNode(idNode), - messageId: "readonly", - data: { name: idNode.name } - }); - } else if (shouldCheckMembers && isMemberWrite(idNode, scope)) { - context.report({ - node: getWriteNode(idNode), - messageId: "readonlyMember", - data: { name: idNode.name } - }); - } - } - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow assigning to imported bindings", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-import-assign", + }, + + schema: [], + + messages: { + readonly: "'{{name}}' is read-only.", + readonlyMember: "The members of '{{name}}' are read-only.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + ImportDeclaration(node) { + const scope = sourceCode.getScope(node); + + for (const variable of sourceCode.getDeclaredVariables(node)) { + const shouldCheckMembers = variable.defs.some( + d => d.node.type === "ImportNamespaceSpecifier", + ); + let prevIdNode = null; + + for (const reference of variable.references) { + const idNode = reference.identifier; + + /* + * AssignmentPattern (e.g. `[a = 0] = b`) makes two write + * references for the same identifier. This should skip + * the one of the two in order to prevent redundant reports. + */ + if (idNode === prevIdNode) { + continue; + } + prevIdNode = idNode; + + if (reference.isWrite()) { + context.report({ + node: getWriteNode(idNode), + messageId: "readonly", + data: { name: idNode.name }, + }); + } else if ( + shouldCheckMembers && + isMemberWrite(idNode, scope) + ) { + context.report({ + node: getWriteNode(idNode), + messageId: "readonlyMember", + data: { name: idNode.name }, + }); + } + } + } + }, + }; + }, }; diff --git a/lib/rules/no-inline-comments.js b/lib/rules/no-inline-comments.js index cc34dac2a8d3..e392c5d67bf0 100644 --- a/lib/rules/no-inline-comments.js +++ b/lib/rules/no-inline-comments.js @@ -12,98 +12,104 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{}], - - docs: { - description: "Disallow inline comments after code", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-inline-comments" - }, - - schema: [ - { - type: "object", - properties: { - ignorePattern: { - type: "string" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedInlineComment: "Unexpected comment inline with code." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ ignorePattern }] = context.options; - const customIgnoreRegExp = ignorePattern && new RegExp(ignorePattern, "u"); - - /** - * Will check that comments are not on lines starting with or ending with code - * @param {ASTNode} node The comment node to check - * @private - * @returns {void} - */ - function testCodeAroundComment(node) { - - const startLine = String(sourceCode.lines[node.loc.start.line - 1]), - endLine = String(sourceCode.lines[node.loc.end.line - 1]), - preamble = startLine.slice(0, node.loc.start.column).trim(), - postamble = endLine.slice(node.loc.end.column).trim(), - isPreambleEmpty = !preamble, - isPostambleEmpty = !postamble; - - // Nothing on both sides - if (isPreambleEmpty && isPostambleEmpty) { - return; - } - - // Matches the ignore pattern - if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) { - return; - } - - // JSX Exception - if ( - (isPreambleEmpty || preamble === "{") && - (isPostambleEmpty || postamble === "}") - ) { - const enclosingNode = sourceCode.getNodeByRangeIndex(node.range[0]); - - if (enclosingNode && enclosingNode.type === "JSXEmptyExpression") { - return; - } - } - - // Don't report ESLint directive comments - if (astUtils.isDirectiveComment(node)) { - return; - } - - context.report({ - node, - messageId: "unexpectedInlineComment" - }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program() { - sourceCode.getAllComments() - .filter(token => token.type !== "Shebang") - .forEach(testCodeAroundComment); - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{}], + + docs: { + description: "Disallow inline comments after code", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-inline-comments", + }, + + schema: [ + { + type: "object", + properties: { + ignorePattern: { + type: "string", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedInlineComment: "Unexpected comment inline with code.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ ignorePattern }] = context.options; + const customIgnoreRegExp = + ignorePattern && new RegExp(ignorePattern, "u"); + + /** + * Will check that comments are not on lines starting with or ending with code + * @param {ASTNode} node The comment node to check + * @private + * @returns {void} + */ + function testCodeAroundComment(node) { + const startLine = String(sourceCode.lines[node.loc.start.line - 1]), + endLine = String(sourceCode.lines[node.loc.end.line - 1]), + preamble = startLine.slice(0, node.loc.start.column).trim(), + postamble = endLine.slice(node.loc.end.column).trim(), + isPreambleEmpty = !preamble, + isPostambleEmpty = !postamble; + + // Nothing on both sides + if (isPreambleEmpty && isPostambleEmpty) { + return; + } + + // Matches the ignore pattern + if (customIgnoreRegExp && customIgnoreRegExp.test(node.value)) { + return; + } + + // JSX Exception + if ( + (isPreambleEmpty || preamble === "{") && + (isPostambleEmpty || postamble === "}") + ) { + const enclosingNode = sourceCode.getNodeByRangeIndex( + node.range[0], + ); + + if ( + enclosingNode && + enclosingNode.type === "JSXEmptyExpression" + ) { + return; + } + } + + // Don't report ESLint directive comments + if (astUtils.isDirectiveComment(node)) { + return; + } + + context.report({ + node, + messageId: "unexpectedInlineComment", + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + sourceCode + .getAllComments() + .filter(token => token.type !== "Shebang") + .forEach(testCodeAroundComment); + }, + }; + }, }; diff --git a/lib/rules/no-inner-declarations.js b/lib/rules/no-inner-declarations.js index c90b9fa68c4b..1bcd273c711e 100644 --- a/lib/rules/no-inner-declarations.js +++ b/lib/rules/no-inner-declarations.js @@ -15,8 +15,17 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -const validParent = new Set(["Program", "StaticBlock", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); -const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]); +const validParent = new Set([ + "Program", + "StaticBlock", + "ExportNamedDeclaration", + "ExportDefaultDeclaration", +]); +const validBlockStatementParent = new Set([ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", +]); /** * Finds the nearest enclosing context where this rule allows declarations and returns its description. @@ -24,110 +33,115 @@ const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpre * @returns {string} Description. One of "program", "function body", "class static block body". */ function getAllowedBodyDescription(node) { - let { parent } = node; + let { parent } = node; - while (parent) { + while (parent) { + if (parent.type === "StaticBlock") { + return "class static block body"; + } - if (parent.type === "StaticBlock") { - return "class static block body"; - } + if (astUtils.isFunction(parent)) { + return "function body"; + } - if (astUtils.isFunction(parent)) { - return "function body"; - } + ({ parent } = parent); + } - ({ parent } = parent); - } - - return "program"; + return "program"; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: ["functions", { blockScopedFunctions: "allow" }], - - docs: { - description: "Disallow variable or `function` declarations in nested blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-inner-declarations" - }, - - schema: [ - { - enum: ["functions", "both"] - }, - { - type: "object", - properties: { - blockScopedFunctions: { - enum: ["allow", "disallow"] - } - }, - additionalProperties: false - } - ], - - messages: { - moveDeclToRoot: "Move {{type}} declaration to {{body}} root." - } - }, - - create(context) { - const both = context.options[0] === "both"; - const { blockScopedFunctions } = context.options[1]; - - const sourceCode = context.sourceCode; - const ecmaVersion = context.languageOptions.ecmaVersion; - - /** - * Ensure that a given node is at a program or function body's root. - * @param {ASTNode} node Declaration node to check. - * @returns {void} - */ - function check(node) { - const parent = node.parent; - - if ( - parent.type === "BlockStatement" && validBlockStatementParent.has(parent.parent.type) - ) { - return; - } - - if (validParent.has(parent.type)) { - return; - } - - context.report({ - node, - messageId: "moveDeclToRoot", - data: { - type: (node.type === "FunctionDeclaration" ? "function" : "variable"), - body: getAllowedBodyDescription(node) - } - }); - } - - return { - - FunctionDeclaration(node) { - const isInStrictCode = sourceCode.getScope(node).upper.isStrict; - - if (blockScopedFunctions === "allow" && ecmaVersion >= 2015 && isInStrictCode) { - return; - } - - check(node); - }, - VariableDeclaration(node) { - if (both && node.kind === "var") { - check(node); - } - } - - }; - - } + meta: { + type: "problem", + + defaultOptions: ["functions", { blockScopedFunctions: "allow" }], + + docs: { + description: + "Disallow variable or `function` declarations in nested blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-inner-declarations", + }, + + schema: [ + { + enum: ["functions", "both"], + }, + { + type: "object", + properties: { + blockScopedFunctions: { + enum: ["allow", "disallow"], + }, + }, + additionalProperties: false, + }, + ], + + messages: { + moveDeclToRoot: "Move {{type}} declaration to {{body}} root.", + }, + }, + + create(context) { + const both = context.options[0] === "both"; + const { blockScopedFunctions } = context.options[1]; + + const sourceCode = context.sourceCode; + const ecmaVersion = context.languageOptions.ecmaVersion; + + /** + * Ensure that a given node is at a program or function body's root. + * @param {ASTNode} node Declaration node to check. + * @returns {void} + */ + function check(node) { + const parent = node.parent; + + if ( + parent.type === "BlockStatement" && + validBlockStatementParent.has(parent.parent.type) + ) { + return; + } + + if (validParent.has(parent.type)) { + return; + } + + context.report({ + node, + messageId: "moveDeclToRoot", + data: { + type: + node.type === "FunctionDeclaration" + ? "function" + : "variable", + body: getAllowedBodyDescription(node), + }, + }); + } + + return { + FunctionDeclaration(node) { + const isInStrictCode = sourceCode.getScope(node).upper.isStrict; + + if ( + blockScopedFunctions === "allow" && + ecmaVersion >= 2015 && + isInStrictCode + ) { + return; + } + + check(node); + }, + VariableDeclaration(node) { + if (both && node.kind === "var") { + check(node); + } + }, + }; + }, }; diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js index 635bd6667d31..7731c615be95 100644 --- a/lib/rules/no-invalid-regexp.js +++ b/lib/rules/no-invalid-regexp.js @@ -19,193 +19,225 @@ const undefined1 = void 0; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{}], - - docs: { - description: "Disallow invalid regular expression strings in `RegExp` constructors", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-invalid-regexp" - }, - - schema: [{ - type: "object", - properties: { - allowConstructorFlags: { - type: "array", - items: { - type: "string" - } - } - }, - additionalProperties: false - }], - - messages: { - regexMessage: "{{message}}." - } - }, - - create(context) { - const [{ allowConstructorFlags }] = context.options; - let allowedFlags = []; - - if (allowConstructorFlags) { - const temp = allowConstructorFlags.join("").replace(new RegExp(`[${validFlags}]`, "gu"), ""); - - if (temp) { - allowedFlags = [...new Set(temp)]; - } - } - - /** - * Reports error with the provided message. - * @param {ASTNode} node The node holding the invalid RegExp - * @param {string} message The message to report. - * @returns {void} - */ - function report(node, message) { - context.report({ - node, - messageId: "regexMessage", - data: { message } - }); - } - - /** - * Check if node is a string - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if its a string - * @private - */ - function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } - - /** - * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call - * Examples: - * new RegExp(".") // => "" - * new RegExp(".", "gu") // => "gu" - * new RegExp(".", flags) // => null - * @param {ASTNode} node `CallExpression` or `NewExpression` node - * @returns {string|null} flags if they can be determined, `null` otherwise - * @private - */ - function getFlags(node) { - if (node.arguments.length < 2) { - return ""; - } - - if (isString(node.arguments[1])) { - return node.arguments[1].value; - } - - return null; - } - - /** - * Check syntax error in a given pattern. - * @param {string} pattern The RegExp pattern to validate. - * @param {Object} flags The RegExp flags to validate. - * @param {boolean} [flags.unicode] The Unicode flag. - * @param {boolean} [flags.unicodeSets] The UnicodeSets flag. - * @returns {string|null} The syntax error. - */ - function validateRegExpPattern(pattern, flags) { - try { - validator.validatePattern(pattern, undefined1, undefined1, flags); - return null; - } catch (err) { - return err.message; - } - } - - /** - * Check syntax error in a given flags. - * @param {string|null} flags The RegExp flags to validate. - * @param {string|null} flagsToCheck The RegExp invalid flags. - * @param {string} allFlags all valid and allowed flags. - * @returns {string|null} The syntax error. - */ - function validateRegExpFlags(flags, flagsToCheck, allFlags) { - const duplicateFlags = []; - - if (typeof flagsToCheck === "string") { - for (const flag of flagsToCheck) { - if (allFlags.includes(flag)) { - duplicateFlags.push(flag); - } - } - } - - /* - * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, - * but this rule may check only the flag when the pattern is unidentifiable, so check it here. - * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern - */ - if (flags && flags.includes("u") && flags.includes("v")) { - return "Regex 'u' and 'v' flags cannot be used together"; - } - - if (duplicateFlags.length > 0) { - return `Duplicate flags ('${duplicateFlags.join("")}') supplied to RegExp constructor`; - } - - if (!flagsToCheck) { - return null; - } - - return `Invalid flags supplied to RegExp constructor '${flagsToCheck}'`; - } - - return { - "CallExpression, NewExpression"(node) { - if (node.callee.type !== "Identifier" || node.callee.name !== "RegExp") { - return; - } - - const flags = getFlags(node); - let flagsToCheck = flags; - const allFlags = allowedFlags.length > 0 ? validFlags.split("").concat(allowedFlags) : validFlags.split(""); - - if (flags) { - allFlags.forEach(flag => { - flagsToCheck = flagsToCheck.replace(flag, ""); - }); - } - - let message = validateRegExpFlags(flags, flagsToCheck, allFlags); - - if (message) { - report(node, message); - return; - } - - if (!isString(node.arguments[0])) { - return; - } - - const pattern = node.arguments[0].value; - - message = ( - - // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag - flags === null - ? ( - validateRegExpPattern(pattern, { unicode: true, unicodeSets: false }) && - validateRegExpPattern(pattern, { unicode: false, unicodeSets: true }) && - validateRegExpPattern(pattern, { unicode: false, unicodeSets: false }) - ) - : validateRegExpPattern(pattern, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }) - ); - - if (message) { - report(node, message); - } - } - }; - } + meta: { + type: "problem", + + defaultOptions: [{}], + + docs: { + description: + "Disallow invalid regular expression strings in `RegExp` constructors", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-invalid-regexp", + }, + + schema: [ + { + type: "object", + properties: { + allowConstructorFlags: { + type: "array", + items: { + type: "string", + }, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + regexMessage: "{{message}}.", + }, + }, + + create(context) { + const [{ allowConstructorFlags }] = context.options; + let allowedFlags = []; + + if (allowConstructorFlags) { + const temp = allowConstructorFlags + .join("") + .replace(new RegExp(`[${validFlags}]`, "gu"), ""); + + if (temp) { + allowedFlags = [...new Set(temp)]; + } + } + + /** + * Reports error with the provided message. + * @param {ASTNode} node The node holding the invalid RegExp + * @param {string} message The message to report. + * @returns {void} + */ + function report(node, message) { + context.report({ + node, + messageId: "regexMessage", + data: { message }, + }); + } + + /** + * Check if node is a string + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if its a string + * @private + */ + function isString(node) { + return ( + node && + node.type === "Literal" && + typeof node.value === "string" + ); + } + + /** + * Gets flags of a regular expression created by the given `RegExp()` or `new RegExp()` call + * Examples: + * new RegExp(".") // => "" + * new RegExp(".", "gu") // => "gu" + * new RegExp(".", flags) // => null + * @param {ASTNode} node `CallExpression` or `NewExpression` node + * @returns {string|null} flags if they can be determined, `null` otherwise + * @private + */ + function getFlags(node) { + if (node.arguments.length < 2) { + return ""; + } + + if (isString(node.arguments[1])) { + return node.arguments[1].value; + } + + return null; + } + + /** + * Check syntax error in a given pattern. + * @param {string} pattern The RegExp pattern to validate. + * @param {Object} flags The RegExp flags to validate. + * @param {boolean} [flags.unicode] The Unicode flag. + * @param {boolean} [flags.unicodeSets] The UnicodeSets flag. + * @returns {string|null} The syntax error. + */ + function validateRegExpPattern(pattern, flags) { + try { + validator.validatePattern( + pattern, + undefined1, + undefined1, + flags, + ); + return null; + } catch (err) { + return err.message; + } + } + + /** + * Check syntax error in a given flags. + * @param {string|null} flags The RegExp flags to validate. + * @param {string|null} flagsToCheck The RegExp invalid flags. + * @param {string} allFlags all valid and allowed flags. + * @returns {string|null} The syntax error. + */ + function validateRegExpFlags(flags, flagsToCheck, allFlags) { + const duplicateFlags = []; + + if (typeof flagsToCheck === "string") { + for (const flag of flagsToCheck) { + if (allFlags.includes(flag)) { + duplicateFlags.push(flag); + } + } + } + + /* + * `regexpp` checks the combination of `u` and `v` flags when parsing `Pattern` according to `ecma262`, + * but this rule may check only the flag when the pattern is unidentifiable, so check it here. + * https://tc39.es/ecma262/multipage/text-processing.html#sec-parsepattern + */ + if (flags && flags.includes("u") && flags.includes("v")) { + return "Regex 'u' and 'v' flags cannot be used together"; + } + + if (duplicateFlags.length > 0) { + return `Duplicate flags ('${duplicateFlags.join("")}') supplied to RegExp constructor`; + } + + if (!flagsToCheck) { + return null; + } + + return `Invalid flags supplied to RegExp constructor '${flagsToCheck}'`; + } + + return { + "CallExpression, NewExpression"(node) { + if ( + node.callee.type !== "Identifier" || + node.callee.name !== "RegExp" + ) { + return; + } + + const flags = getFlags(node); + let flagsToCheck = flags; + const allFlags = + allowedFlags.length > 0 + ? validFlags.split("").concat(allowedFlags) + : validFlags.split(""); + + if (flags) { + allFlags.forEach(flag => { + flagsToCheck = flagsToCheck.replace(flag, ""); + }); + } + + let message = validateRegExpFlags( + flags, + flagsToCheck, + allFlags, + ); + + if (message) { + report(node, message); + return; + } + + if (!isString(node.arguments[0])) { + return; + } + + const pattern = node.arguments[0].value; + + message = + // If flags are unknown, report the regex only if its pattern is invalid both with and without the "u" flag + flags === null + ? validateRegExpPattern(pattern, { + unicode: true, + unicodeSets: false, + }) && + validateRegExpPattern(pattern, { + unicode: false, + unicodeSets: true, + }) && + validateRegExpPattern(pattern, { + unicode: false, + unicodeSets: false, + }) + : validateRegExpPattern(pattern, { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v"), + }); + + if (message) { + report(node, message); + } + }, + }; + }, }; diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js index 0a7b9e408bc3..0e1bc30af8de 100644 --- a/lib/rules/no-invalid-this.js +++ b/lib/rules/no-invalid-this.js @@ -23,7 +23,10 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if it is a code path with lexical `this` binding. */ function isCodePathWithLexicalThis(codePath, node) { - return codePath.origin === "function" && node.type === "ArrowFunctionExpression"; + return ( + codePath.origin === "function" && + node.type === "ArrowFunctionExpression" + ); } //------------------------------------------------------------------------------ @@ -32,119 +35,122 @@ function isCodePathWithLexicalThis(codePath, node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ capIsConstructor: true }], - - docs: { - description: "Disallow use of `this` in contexts where the value of `this` is `undefined`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-invalid-this" - }, - - schema: [ - { - type: "object", - properties: { - capIsConstructor: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedThis: "Unexpected 'this'." - } - }, - - create(context) { - const [{ capIsConstructor }] = context.options; - const stack = [], - sourceCode = context.sourceCode; - - /** - * Gets the current checking context. - * - * The return value has a flag that whether or not `this` keyword is valid. - * The flag is initialized when got at the first time. - * @returns {{valid: boolean}} - * an object which has a flag that whether or not `this` keyword is valid. - */ - stack.getCurrent = function() { - const current = this.at(-1); - - if (!current.init) { - current.init = true; - current.valid = !astUtils.isDefaultThisBinding( - current.node, - sourceCode, - { capIsConstructor } - ); - } - return current; - }; - - return { - - onCodePathStart(codePath, node) { - if (isCodePathWithLexicalThis(codePath, node)) { - return; - } - - if (codePath.origin === "program") { - const scope = sourceCode.getScope(node); - const features = context.languageOptions.parserOptions.ecmaFeatures || {}; - - // `this` at the top level of scripts always refers to the global object - stack.push({ - init: true, - node, - valid: !( - node.sourceType === "module" || - (features.globalReturn && scope.childScopes[0].isStrict) - ) - }); - - return; - } - - /* - * `init: false` means that `valid` isn't determined yet. - * Most functions don't use `this`, and the calculation for `valid` - * is relatively costly, so we'll calculate it lazily when the first - * `this` within the function is traversed. A special case are non-strict - * functions, because `this` refers to the global object and therefore is - * always valid, so we can set `init: true` right away. - */ - stack.push({ - init: !sourceCode.getScope(node).isStrict, - node, - valid: true - }); - }, - - onCodePathEnd(codePath, node) { - if (isCodePathWithLexicalThis(codePath, node)) { - return; - } - - stack.pop(); - }, - - // Reports if `this` of the current context is invalid. - ThisExpression(node) { - const current = stack.getCurrent(); - - if (current && !current.valid) { - context.report({ - node, - messageId: "unexpectedThis" - }); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{ capIsConstructor: true }], + + docs: { + description: + "Disallow use of `this` in contexts where the value of `this` is `undefined`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-invalid-this", + }, + + schema: [ + { + type: "object", + properties: { + capIsConstructor: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedThis: "Unexpected 'this'.", + }, + }, + + create(context) { + const [{ capIsConstructor }] = context.options; + const stack = [], + sourceCode = context.sourceCode; + + /** + * Gets the current checking context. + * + * The return value has a flag that whether or not `this` keyword is valid. + * The flag is initialized when got at the first time. + * @returns {{valid: boolean}} + * an object which has a flag that whether or not `this` keyword is valid. + */ + stack.getCurrent = function () { + const current = this.at(-1); + + if (!current.init) { + current.init = true; + current.valid = !astUtils.isDefaultThisBinding( + current.node, + sourceCode, + { capIsConstructor }, + ); + } + return current; + }; + + return { + onCodePathStart(codePath, node) { + if (isCodePathWithLexicalThis(codePath, node)) { + return; + } + + if (codePath.origin === "program") { + const scope = sourceCode.getScope(node); + const features = + context.languageOptions.parserOptions.ecmaFeatures || + {}; + + // `this` at the top level of scripts always refers to the global object + stack.push({ + init: true, + node, + valid: !( + node.sourceType === "module" || + (features.globalReturn && + scope.childScopes[0].isStrict) + ), + }); + + return; + } + + /* + * `init: false` means that `valid` isn't determined yet. + * Most functions don't use `this`, and the calculation for `valid` + * is relatively costly, so we'll calculate it lazily when the first + * `this` within the function is traversed. A special case are non-strict + * functions, because `this` refers to the global object and therefore is + * always valid, so we can set `init: true` right away. + */ + stack.push({ + init: !sourceCode.getScope(node).isStrict, + node, + valid: true, + }); + }, + + onCodePathEnd(codePath, node) { + if (isCodePathWithLexicalThis(codePath, node)) { + return; + } + + stack.pop(); + }, + + // Reports if `this` of the current context is invalid. + ThisExpression(node) { + const current = stack.getCurrent(); + + if (current && !current.valid) { + context.report({ + node, + messageId: "unexpectedThis", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js index 3069db181378..85d502fdd598 100644 --- a/lib/rules/no-irregular-whitespace.js +++ b/lib/rules/no-irregular-whitespace.js @@ -16,9 +16,11 @@ const astUtils = require("./utils/ast-utils"); // Constants //------------------------------------------------------------------------------ -const ALL_IRREGULARS = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u; -const IRREGULAR_WHITESPACE = /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mgu; -const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/mgu; +const ALL_IRREGULARS = + /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000\u2028\u2029]/u; +const IRREGULAR_WHITESPACE = + /[\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/gmu; +const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gmu; const LINE_BREAK = astUtils.createGlobalLinebreakMatcher(); //------------------------------------------------------------------------------ @@ -27,252 +29,264 @@ const LINE_BREAK = astUtils.createGlobalLinebreakMatcher(); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - skipComments: false, - skipJSXText: false, - skipRegExps: false, - skipStrings: true, - skipTemplates: false - }], - - docs: { - description: "Disallow irregular whitespace", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-irregular-whitespace" - }, - - schema: [ - { - type: "object", - properties: { - skipComments: { - type: "boolean" - }, - skipStrings: { - type: "boolean" - }, - skipTemplates: { - type: "boolean" - }, - skipRegExps: { - type: "boolean" - }, - skipJSXText: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - noIrregularWhitespace: "Irregular whitespace not allowed." - } - }, - - create(context) { - const [{ - skipComments, - skipStrings, - skipRegExps, - skipTemplates, - skipJSXText - }] = context.options; - - const sourceCode = context.sourceCode; - const commentNodes = sourceCode.getAllComments(); - - // Module store of errors that we have found - let errors = []; - - /** - * Removes errors that occur inside the given node - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeWhitespaceError(node) { - const locStart = node.loc.start; - const locEnd = node.loc.end; - - errors = errors.filter(({ loc: { start: errorLocStart } }) => ( - errorLocStart.line < locStart.line || - errorLocStart.line === locStart.line && errorLocStart.column < locStart.column || - errorLocStart.line === locEnd.line && errorLocStart.column >= locEnd.column || - errorLocStart.line > locEnd.line - )); - } - - /** - * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInLiteral(node) { - const shouldCheckStrings = skipStrings && (typeof node.value === "string"); - const shouldCheckRegExps = skipRegExps && Boolean(node.regex); - - if (shouldCheckStrings || shouldCheckRegExps) { - - // If we have irregular characters remove them from the errors list - if (ALL_IRREGULARS.test(node.raw)) { - removeWhitespaceError(node); - } - } - } - - /** - * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInTemplateLiteral(node) { - if (typeof node.value.raw === "string") { - if (ALL_IRREGULARS.test(node.value.raw)) { - removeWhitespaceError(node); - } - } - } - - /** - * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInComment(node) { - if (ALL_IRREGULARS.test(node.value)) { - removeWhitespaceError(node); - } - } - - /** - * Checks JSX nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors - * @param {ASTNode} node to check for matching errors. - * @returns {void} - * @private - */ - function removeInvalidNodeErrorsInJSXText(node) { - if (ALL_IRREGULARS.test(node.raw)) { - removeWhitespaceError(node); - } - } - - /** - * Checks the program source for irregular whitespace - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularWhitespace(node) { - const sourceLines = sourceCode.lines; - - sourceLines.forEach((sourceLine, lineIndex) => { - const lineNumber = lineIndex + 1; - let match; - - while ((match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null) { - errors.push({ - node, - messageId: "noIrregularWhitespace", - loc: { - start: { - line: lineNumber, - column: match.index - }, - end: { - line: lineNumber, - column: match.index + match[0].length - } - } - }); - } - }); - } - - /** - * Checks the program source for irregular line terminators - * @param {ASTNode} node The program node - * @returns {void} - * @private - */ - function checkForIrregularLineTerminators(node) { - const source = sourceCode.getText(), - sourceLines = sourceCode.lines, - linebreaks = source.match(LINE_BREAK); - let lastLineIndex = -1, - match; - - while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) { - const lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; - - errors.push({ - node, - messageId: "noIrregularWhitespace", - loc: { - start: { - line: lineIndex + 1, - column: sourceLines[lineIndex].length - }, - end: { - line: lineIndex + 2, - column: 0 - } - } - }); - - lastLineIndex = lineIndex; - } - } - - /** - * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. - * @returns {void} - * @private - */ - function noop() { } - - const nodes = {}; - - if (ALL_IRREGULARS.test(sourceCode.getText())) { - nodes.Program = function(node) { - - /* - * As we can easily fire warnings for all white space issues with - * all the source its simpler to fire them here. - * This means we can check all the application code without having - * to worry about issues caused in the parser tokens. - * When writing this code also evaluating per node was missing out - * connecting tokens in some cases. - * We can later filter the errors when they are found to be not an - * issue in nodes we don't care about. - */ - checkForIrregularWhitespace(node); - checkForIrregularLineTerminators(node); - }; - - nodes.Literal = removeInvalidNodeErrorsInLiteral; - nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop; - nodes.JSXText = skipJSXText ? removeInvalidNodeErrorsInJSXText : noop; - nodes["Program:exit"] = function() { - if (skipComments) { - - // First strip errors occurring in comment nodes. - commentNodes.forEach(removeInvalidNodeErrorsInComment); - } - - // If we have any errors remaining report on them - errors.forEach(error => context.report(error)); - }; - } else { - nodes.Program = noop; - } - - return nodes; - } + meta: { + type: "problem", + + defaultOptions: [ + { + skipComments: false, + skipJSXText: false, + skipRegExps: false, + skipStrings: true, + skipTemplates: false, + }, + ], + + docs: { + description: "Disallow irregular whitespace", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-irregular-whitespace", + }, + + schema: [ + { + type: "object", + properties: { + skipComments: { + type: "boolean", + }, + skipStrings: { + type: "boolean", + }, + skipTemplates: { + type: "boolean", + }, + skipRegExps: { + type: "boolean", + }, + skipJSXText: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + noIrregularWhitespace: "Irregular whitespace not allowed.", + }, + }, + + create(context) { + const [ + { + skipComments, + skipStrings, + skipRegExps, + skipTemplates, + skipJSXText, + }, + ] = context.options; + + const sourceCode = context.sourceCode; + const commentNodes = sourceCode.getAllComments(); + + // Module store of errors that we have found + let errors = []; + + /** + * Removes errors that occur inside the given node + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeWhitespaceError(node) { + const locStart = node.loc.start; + const locEnd = node.loc.end; + + errors = errors.filter( + ({ loc: { start: errorLocStart } }) => + errorLocStart.line < locStart.line || + (errorLocStart.line === locStart.line && + errorLocStart.column < locStart.column) || + (errorLocStart.line === locEnd.line && + errorLocStart.column >= locEnd.column) || + errorLocStart.line > locEnd.line, + ); + } + + /** + * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInLiteral(node) { + const shouldCheckStrings = + skipStrings && typeof node.value === "string"; + const shouldCheckRegExps = skipRegExps && Boolean(node.regex); + + if (shouldCheckStrings || shouldCheckRegExps) { + // If we have irregular characters remove them from the errors list + if (ALL_IRREGULARS.test(node.raw)) { + removeWhitespaceError(node); + } + } + } + + /** + * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInTemplateLiteral(node) { + if (typeof node.value.raw === "string") { + if (ALL_IRREGULARS.test(node.value.raw)) { + removeWhitespaceError(node); + } + } + } + + /** + * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInComment(node) { + if (ALL_IRREGULARS.test(node.value)) { + removeWhitespaceError(node); + } + } + + /** + * Checks JSX nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors + * @param {ASTNode} node to check for matching errors. + * @returns {void} + * @private + */ + function removeInvalidNodeErrorsInJSXText(node) { + if (ALL_IRREGULARS.test(node.raw)) { + removeWhitespaceError(node); + } + } + + /** + * Checks the program source for irregular whitespace + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularWhitespace(node) { + const sourceLines = sourceCode.lines; + + sourceLines.forEach((sourceLine, lineIndex) => { + const lineNumber = lineIndex + 1; + let match; + + while ( + (match = IRREGULAR_WHITESPACE.exec(sourceLine)) !== null + ) { + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: { + start: { + line: lineNumber, + column: match.index, + }, + end: { + line: lineNumber, + column: match.index + match[0].length, + }, + }, + }); + } + }); + } + + /** + * Checks the program source for irregular line terminators + * @param {ASTNode} node The program node + * @returns {void} + * @private + */ + function checkForIrregularLineTerminators(node) { + const source = sourceCode.getText(), + sourceLines = sourceCode.lines, + linebreaks = source.match(LINE_BREAK); + let lastLineIndex = -1, + match; + + while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) { + const lineIndex = + linebreaks.indexOf(match[0], lastLineIndex + 1) || 0; + + errors.push({ + node, + messageId: "noIrregularWhitespace", + loc: { + start: { + line: lineIndex + 1, + column: sourceLines[lineIndex].length, + }, + end: { + line: lineIndex + 2, + column: 0, + }, + }, + }); + + lastLineIndex = lineIndex; + } + } + + /** + * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`. + * @returns {void} + * @private + */ + function noop() {} + + const nodes = {}; + + if (ALL_IRREGULARS.test(sourceCode.getText())) { + nodes.Program = function (node) { + /* + * As we can easily fire warnings for all white space issues with + * all the source its simpler to fire them here. + * This means we can check all the application code without having + * to worry about issues caused in the parser tokens. + * When writing this code also evaluating per node was missing out + * connecting tokens in some cases. + * We can later filter the errors when they are found to be not an + * issue in nodes we don't care about. + */ + checkForIrregularWhitespace(node); + checkForIrregularLineTerminators(node); + }; + + nodes.Literal = removeInvalidNodeErrorsInLiteral; + nodes.TemplateElement = skipTemplates + ? removeInvalidNodeErrorsInTemplateLiteral + : noop; + nodes.JSXText = skipJSXText + ? removeInvalidNodeErrorsInJSXText + : noop; + nodes["Program:exit"] = function () { + if (skipComments) { + // First strip errors occurring in comment nodes. + commentNodes.forEach(removeInvalidNodeErrorsInComment); + } + + // If we have any errors remaining report on them + errors.forEach(error => context.report(error)); + }; + } else { + nodes.Program = noop; + } + + return nodes; + }, }; diff --git a/lib/rules/no-iterator.js b/lib/rules/no-iterator.js index dcd9683b455e..6d8d8e50c5b7 100644 --- a/lib/rules/no-iterator.js +++ b/lib/rules/no-iterator.js @@ -17,36 +17,32 @@ const { getStaticPropertyName } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of the `__iterator__` property", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-iterator" - }, - - schema: [], - - messages: { - noIterator: "Reserved name '__iterator__'." - } - }, - - create(context) { - - return { - - MemberExpression(node) { - - if (getStaticPropertyName(node) === "__iterator__") { - context.report({ - node, - messageId: "noIterator" - }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow the use of the `__iterator__` property", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-iterator", + }, + + schema: [], + + messages: { + noIterator: "Reserved name '__iterator__'.", + }, + }, + + create(context) { + return { + MemberExpression(node) { + if (getStaticPropertyName(node) === "__iterator__") { + context.report({ + node, + messageId: "noIterator", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-label-var.js b/lib/rules/no-label-var.js index 31dee3b4c7b7..85c28c58393a 100644 --- a/lib/rules/no-label-var.js +++ b/lib/rules/no-label-var.js @@ -17,65 +17,62 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [], - - messages: { - identifierClashWithLabel: "Found identifier with same name as label." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Check if the identifier is present inside current scope - * @param {Object} scope current scope - * @param {string} name To evaluate - * @returns {boolean} True if its present - * @private - */ - function findIdentifier(scope, name) { - return astUtils.getVariableByName(scope, name) !== null; - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - LabeledStatement(node) { - - // Fetch the innermost scope. - const scope = sourceCode.getScope(node); - - /* - * Recursively find the identifier walking up the scope, starting - * with the innermost scope. - */ - if (findIdentifier(scope, node.label.name)) { - context.report({ - node, - messageId: "identifierClashWithLabel" - }); - } - } - - }; - - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [], + + messages: { + identifierClashWithLabel: + "Found identifier with same name as label.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Check if the identifier is present inside current scope + * @param {Object} scope current scope + * @param {string} name To evaluate + * @returns {boolean} True if its present + * @private + */ + function findIdentifier(scope, name) { + return astUtils.getVariableByName(scope, name) !== null; + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + LabeledStatement(node) { + // Fetch the innermost scope. + const scope = sourceCode.getScope(node); + + /* + * Recursively find the identifier walking up the scope, starting + * with the innermost scope. + */ + if (findIdentifier(scope, node.label.name)) { + context.report({ + node, + messageId: "identifierClashWithLabel", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-labels.js b/lib/rules/no-labels.js index 2b96c928a99a..cac3f1a3782d 100644 --- a/lib/rules/no-labels.js +++ b/lib/rules/no-labels.js @@ -16,136 +16,141 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - allowLoop: { - type: "boolean" - }, - allowSwitch: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedLabel: "Unexpected labeled statement.", - unexpectedLabelInBreak: "Unexpected label in break statement.", - unexpectedLabelInContinue: "Unexpected label in continue statement." - } - }, - - create(context) { - const [{ allowLoop, allowSwitch }] = context.options; - let scopeInfo = null; - - /** - * Gets the kind of a given node. - * @param {ASTNode} node A node to get. - * @returns {string} The kind of the node. - */ - function getBodyKind(node) { - if (astUtils.isLoop(node)) { - return "loop"; - } - if (node.type === "SwitchStatement") { - return "switch"; - } - return "other"; - } - - /** - * Checks whether the label of a given kind is allowed or not. - * @param {string} kind A kind to check. - * @returns {boolean} `true` if the kind is allowed. - */ - function isAllowed(kind) { - switch (kind) { - case "loop": return allowLoop; - case "switch": return allowSwitch; - default: return false; - } - } - - /** - * Checks whether a given name is a label of a loop or not. - * @param {string} label A name of a label to check. - * @returns {boolean} `true` if the name is a label of a loop. - */ - function getKind(label) { - let info = scopeInfo; - - while (info) { - if (info.label === label) { - return info.kind; - } - info = info.upper; - } - - /* c8 ignore next */ - return "other"; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - LabeledStatement(node) { - scopeInfo = { - label: node.label.name, - kind: getBodyKind(node.body), - upper: scopeInfo - }; - }, - - "LabeledStatement:exit"(node) { - if (!isAllowed(scopeInfo.kind)) { - context.report({ - node, - messageId: "unexpectedLabel" - }); - } - - scopeInfo = scopeInfo.upper; - }, - - BreakStatement(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node, - messageId: "unexpectedLabelInBreak" - }); - } - }, - - ContinueStatement(node) { - if (node.label && !isAllowed(getKind(node.label.name))) { - context.report({ - node, - messageId: "unexpectedLabelInContinue" - }); - } - } - }; - - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + allowLoop: { + type: "boolean", + }, + allowSwitch: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedLabel: "Unexpected labeled statement.", + unexpectedLabelInBreak: "Unexpected label in break statement.", + unexpectedLabelInContinue: + "Unexpected label in continue statement.", + }, + }, + + create(context) { + const [{ allowLoop, allowSwitch }] = context.options; + let scopeInfo = null; + + /** + * Gets the kind of a given node. + * @param {ASTNode} node A node to get. + * @returns {string} The kind of the node. + */ + function getBodyKind(node) { + if (astUtils.isLoop(node)) { + return "loop"; + } + if (node.type === "SwitchStatement") { + return "switch"; + } + return "other"; + } + + /** + * Checks whether the label of a given kind is allowed or not. + * @param {string} kind A kind to check. + * @returns {boolean} `true` if the kind is allowed. + */ + function isAllowed(kind) { + switch (kind) { + case "loop": + return allowLoop; + case "switch": + return allowSwitch; + default: + return false; + } + } + + /** + * Checks whether a given name is a label of a loop or not. + * @param {string} label A name of a label to check. + * @returns {boolean} `true` if the name is a label of a loop. + */ + function getKind(label) { + let info = scopeInfo; + + while (info) { + if (info.label === label) { + return info.kind; + } + info = info.upper; + } + + /* c8 ignore next */ + return "other"; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + LabeledStatement(node) { + scopeInfo = { + label: node.label.name, + kind: getBodyKind(node.body), + upper: scopeInfo, + }; + }, + + "LabeledStatement:exit"(node) { + if (!isAllowed(scopeInfo.kind)) { + context.report({ + node, + messageId: "unexpectedLabel", + }); + } + + scopeInfo = scopeInfo.upper; + }, + + BreakStatement(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node, + messageId: "unexpectedLabelInBreak", + }); + } + }, + + ContinueStatement(node) { + if (node.label && !isAllowed(getKind(node.label.name))) { + context.report({ + node, + messageId: "unexpectedLabelInContinue", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-lone-blocks.js b/lib/rules/no-lone-blocks.js index 2e27089269be..9694ffbba953 100644 --- a/lib/rules/no-lone-blocks.js +++ b/lib/rules/no-lone-blocks.js @@ -11,126 +11,130 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary nested blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-lone-blocks" - }, - - schema: [], - - messages: { - redundantBlock: "Block is redundant.", - redundantNestedBlock: "Nested block is redundant." - } - }, - - create(context) { - - // A stack of lone blocks to be checked for block-level bindings - const loneBlocks = []; - let ruleDef; - const sourceCode = context.sourceCode; - - /** - * Reports a node as invalid. - * @param {ASTNode} node The node to be reported. - * @returns {void} - */ - function report(node) { - const messageId = node.parent.type === "BlockStatement" || node.parent.type === "StaticBlock" - ? "redundantNestedBlock" - : "redundantBlock"; - - context.report({ - node, - messageId - }); - } - - /** - * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear - * @param {ASTNode} node The node to check - * @returns {boolean} True if the node is a lone block. - */ - function isLoneBlock(node) { - return node.parent.type === "BlockStatement" || - node.parent.type === "StaticBlock" || - node.parent.type === "Program" || - - // Don't report blocks in switch cases if the block is the only statement of the case. - node.parent.type === "SwitchCase" && !(node.parent.consequent[0] === node && node.parent.consequent.length === 1); - } - - /** - * Checks the enclosing block of the current node for block-level bindings, - * and "marks it" as valid if any. - * @param {ASTNode} node The current node to check. - * @returns {void} - */ - function markLoneBlock(node) { - if (loneBlocks.length === 0) { - return; - } - - const block = node.parent; - - if (loneBlocks.at(-1) === block) { - loneBlocks.pop(); - } - } - - // Default rule definition: report all lone blocks - ruleDef = { - BlockStatement(node) { - if (isLoneBlock(node)) { - report(node); - } - } - }; - - // ES6: report blocks without block-level bindings, or that's only child of another block - if (context.languageOptions.ecmaVersion >= 2015) { - ruleDef = { - BlockStatement(node) { - if (isLoneBlock(node)) { - loneBlocks.push(node); - } - }, - "BlockStatement:exit"(node) { - if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) { - loneBlocks.pop(); - report(node); - } else if ( - ( - node.parent.type === "BlockStatement" || - node.parent.type === "StaticBlock" - ) && - node.parent.body.length === 1 - ) { - report(node); - } - } - }; - - ruleDef.VariableDeclaration = function(node) { - if (node.kind !== "var") { - markLoneBlock(node); - } - }; - - ruleDef.FunctionDeclaration = function(node) { - if (sourceCode.getScope(node).isStrict) { - markLoneBlock(node); - } - }; - - ruleDef.ClassDeclaration = markLoneBlock; - } - - return ruleDef; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unnecessary nested blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-lone-blocks", + }, + + schema: [], + + messages: { + redundantBlock: "Block is redundant.", + redundantNestedBlock: "Nested block is redundant.", + }, + }, + + create(context) { + // A stack of lone blocks to be checked for block-level bindings + const loneBlocks = []; + let ruleDef; + const sourceCode = context.sourceCode; + + /** + * Reports a node as invalid. + * @param {ASTNode} node The node to be reported. + * @returns {void} + */ + function report(node) { + const messageId = + node.parent.type === "BlockStatement" || + node.parent.type === "StaticBlock" + ? "redundantNestedBlock" + : "redundantBlock"; + + context.report({ + node, + messageId, + }); + } + + /** + * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear + * @param {ASTNode} node The node to check + * @returns {boolean} True if the node is a lone block. + */ + function isLoneBlock(node) { + return ( + node.parent.type === "BlockStatement" || + node.parent.type === "StaticBlock" || + node.parent.type === "Program" || + // Don't report blocks in switch cases if the block is the only statement of the case. + (node.parent.type === "SwitchCase" && + !( + node.parent.consequent[0] === node && + node.parent.consequent.length === 1 + )) + ); + } + + /** + * Checks the enclosing block of the current node for block-level bindings, + * and "marks it" as valid if any. + * @param {ASTNode} node The current node to check. + * @returns {void} + */ + function markLoneBlock(node) { + if (loneBlocks.length === 0) { + return; + } + + const block = node.parent; + + if (loneBlocks.at(-1) === block) { + loneBlocks.pop(); + } + } + + // Default rule definition: report all lone blocks + ruleDef = { + BlockStatement(node) { + if (isLoneBlock(node)) { + report(node); + } + }, + }; + + // ES6: report blocks without block-level bindings, or that's only child of another block + if (context.languageOptions.ecmaVersion >= 2015) { + ruleDef = { + BlockStatement(node) { + if (isLoneBlock(node)) { + loneBlocks.push(node); + } + }, + "BlockStatement:exit"(node) { + if (loneBlocks.length > 0 && loneBlocks.at(-1) === node) { + loneBlocks.pop(); + report(node); + } else if ( + (node.parent.type === "BlockStatement" || + node.parent.type === "StaticBlock") && + node.parent.body.length === 1 + ) { + report(node); + } + }, + }; + + ruleDef.VariableDeclaration = function (node) { + if (node.kind !== "var") { + markLoneBlock(node); + } + }; + + ruleDef.FunctionDeclaration = function (node) { + if (sourceCode.getScope(node).isStrict) { + markLoneBlock(node); + } + }; + + ruleDef.ClassDeclaration = markLoneBlock; + } + + return ruleDef; + }, }; diff --git a/lib/rules/no-lonely-if.js b/lib/rules/no-lonely-if.js index f66b794b6b3d..3abb06e710da 100644 --- a/lib/rules/no-lonely-if.js +++ b/lib/rules/no-lonely-if.js @@ -16,80 +16,111 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [], - fixable: "code", - - messages: { - unexpectedLonelyIf: "Unexpected if as the only statement in an else block." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - IfStatement(node) { - const parent = node.parent, - grandparent = parent.parent; - - if (parent && parent.type === "BlockStatement" && - parent.body.length === 1 && !astUtils.areBracesNecessary(parent, sourceCode) && - grandparent && grandparent.type === "IfStatement" && - parent === grandparent.alternate) { - context.report({ - node, - messageId: "unexpectedLonelyIf", - fix(fixer) { - const openingElseCurly = sourceCode.getFirstToken(parent); - const closingElseCurly = sourceCode.getLastToken(parent); - const elseKeyword = sourceCode.getTokenBefore(openingElseCurly); - const tokenAfterElseBlock = sourceCode.getTokenAfter(closingElseCurly); - const lastIfToken = sourceCode.getLastToken(node.consequent); - const sourceText = sourceCode.getText(); - - if (sourceText.slice(openingElseCurly.range[1], - node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) { - - // Don't fix if there are any non-whitespace characters interfering (e.g. comments) - return null; - } - - if ( - node.consequent.type !== "BlockStatement" && lastIfToken.value !== ";" && tokenAfterElseBlock && - ( - node.consequent.loc.end.line === tokenAfterElseBlock.loc.start.line || - /^[([/+`-]/u.test(tokenAfterElseBlock.value) || - lastIfToken.value === "++" || - lastIfToken.value === "--" - ) - ) { - - /* - * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing - * the issue would not change semantics due to ASI. If this would happen, don't do a fix. - */ - return null; - } - - return fixer.replaceTextRange( - [openingElseCurly.range[0], closingElseCurly.range[1]], - (elseKeyword.range[1] === openingElseCurly.range[0] ? " " : "") + sourceCode.getText(node) - ); - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedLonelyIf: + "Unexpected if as the only statement in an else block.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + IfStatement(node) { + const parent = node.parent, + grandparent = parent.parent; + + if ( + parent && + parent.type === "BlockStatement" && + parent.body.length === 1 && + !astUtils.areBracesNecessary(parent, sourceCode) && + grandparent && + grandparent.type === "IfStatement" && + parent === grandparent.alternate + ) { + context.report({ + node, + messageId: "unexpectedLonelyIf", + fix(fixer) { + const openingElseCurly = + sourceCode.getFirstToken(parent); + const closingElseCurly = + sourceCode.getLastToken(parent); + const elseKeyword = + sourceCode.getTokenBefore(openingElseCurly); + const tokenAfterElseBlock = + sourceCode.getTokenAfter(closingElseCurly); + const lastIfToken = sourceCode.getLastToken( + node.consequent, + ); + const sourceText = sourceCode.getText(); + + if ( + sourceText + .slice( + openingElseCurly.range[1], + node.range[0], + ) + .trim() || + sourceText + .slice( + node.range[1], + closingElseCurly.range[0], + ) + .trim() + ) { + // Don't fix if there are any non-whitespace characters interfering (e.g. comments) + return null; + } + + if ( + node.consequent.type !== "BlockStatement" && + lastIfToken.value !== ";" && + tokenAfterElseBlock && + (node.consequent.loc.end.line === + tokenAfterElseBlock.loc.start.line || + /^[([/+`-]/u.test( + tokenAfterElseBlock.value, + ) || + lastIfToken.value === "++" || + lastIfToken.value === "--") + ) { + /* + * If the `if` statement has no block, and is not followed by a semicolon, make sure that fixing + * the issue would not change semantics due to ASI. If this would happen, don't do a fix. + */ + return null; + } + + return fixer.replaceTextRange( + [ + openingElseCurly.range[0], + closingElseCurly.range[1], + ], + (elseKeyword.range[1] === + openingElseCurly.range[0] + ? " " + : "") + sourceCode.getText(node), + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-loop-func.js b/lib/rules/no-loop-func.js index ba372dbb5a80..7df3cee22216 100644 --- a/lib/rules/no-loop-func.js +++ b/lib/rules/no-loop-func.js @@ -9,230 +9,251 @@ // Helpers //------------------------------------------------------------------------------ - /** * Identifies is a node is a FunctionExpression which is part of an IIFE * @param {ASTNode} node Node to test * @returns {boolean} True if it's an IIFE */ function isIIFE(node) { - return (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && node.parent && node.parent.type === "CallExpression" && node.parent.callee === node; + return ( + (node.type === "FunctionExpression" || + node.type === "ArrowFunctionExpression") && + node.parent && + node.parent.type === "CallExpression" && + node.parent.callee === node + ); } - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow function declarations that contain unsafe references inside loop statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-loop-func" - }, - - schema: [], - - messages: { - unsafeRefs: "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}." - } - }, - - create(context) { - - const SKIPPED_IIFE_NODES = new Set(); - const sourceCode = context.sourceCode; - - /** - * Gets the containing loop node of a specified node. - * - * We don't need to check nested functions, so this ignores those, with the exception of IIFE. - * `Scope.through` contains references of nested functions. - * @param {ASTNode} node An AST node to get. - * @returns {ASTNode|null} The containing loop node of the specified node, or - * `null`. - */ - function getContainingLoopNode(node) { - for (let currentNode = node; currentNode.parent; currentNode = currentNode.parent) { - const parent = currentNode.parent; - - switch (parent.type) { - case "WhileStatement": - case "DoWhileStatement": - return parent; - - case "ForStatement": - - // `init` is outside of the loop. - if (parent.init !== currentNode) { - return parent; - } - break; - - case "ForInStatement": - case "ForOfStatement": - - // `right` is outside of the loop. - if (parent.right !== currentNode) { - return parent; - } - break; - - case "ArrowFunctionExpression": - case "FunctionExpression": - case "FunctionDeclaration": - - // We need to check nested functions only in case of IIFE. - if (SKIPPED_IIFE_NODES.has(parent)) { - break; - } - - return null; - default: - break; - } - } - - return null; - } - - /** - * Gets the containing loop node of a given node. - * If the loop was nested, this returns the most outer loop. - * @param {ASTNode} node A node to get. This is a loop node. - * @param {ASTNode|null} excludedNode A node that the result node should not - * include. - * @returns {ASTNode} The most outer loop node. - */ - function getTopLoopNode(node, excludedNode) { - const border = excludedNode ? excludedNode.range[1] : 0; - let retv = node; - let containingLoopNode = node; - - while (containingLoopNode && containingLoopNode.range[0] >= border) { - retv = containingLoopNode; - containingLoopNode = getContainingLoopNode(containingLoopNode); - } - - return retv; - } - - /** - * Checks whether a given reference which refers to an upper scope's variable is - * safe or not. - * @param {ASTNode} loopNode A containing loop node. - * @param {eslint-scope.Reference} reference A reference to check. - * @returns {boolean} `true` if the reference is safe or not. - */ - function isSafe(loopNode, reference) { - const variable = reference.resolved; - const definition = variable && variable.defs[0]; - const declaration = definition && definition.parent; - const kind = (declaration && declaration.type === "VariableDeclaration") - ? declaration.kind - : ""; - - // Variables which are declared by `const` is safe. - if (kind === "const") { - return true; - } - - /* - * Variables which are declared by `let` in the loop is safe. - * It's a different instance from the next loop step's. - */ - if (kind === "let" && - declaration.range[0] > loopNode.range[0] && - declaration.range[1] < loopNode.range[1] - ) { - return true; - } - - /* - * WriteReferences which exist after this border are unsafe because those - * can modify the variable. - */ - const border = getTopLoopNode( - loopNode, - (kind === "let") ? declaration : null - ).range[0]; - - /** - * Checks whether a given reference is safe or not. - * The reference is every reference of the upper scope's variable we are - * looking now. - * - * It's safe if the reference matches one of the following condition. - * - is readonly. - * - doesn't exist inside a local function and after the border. - * @param {eslint-scope.Reference} upperRef A reference to check. - * @returns {boolean} `true` if the reference is safe. - */ - function isSafeReference(upperRef) { - const id = upperRef.identifier; - - return ( - !upperRef.isWrite() || - variable.scope.variableScope === upperRef.from.variableScope && - id.range[0] < border - ); - } - - return Boolean(variable) && variable.references.every(isSafeReference); - } - - /** - * Reports functions which match the following condition: - * - * - has a loop node in ancestors. - * - has any references which refers to an unsafe variable. - * @param {ASTNode} node The AST node to check. - * @returns {void} - */ - function checkForLoops(node) { - const loopNode = getContainingLoopNode(node); - - if (!loopNode) { - return; - } - - const references = sourceCode.getScope(node).through; - - // Check if the function is not asynchronous or a generator function - if (!(node.async || node.generator)) { - if (isIIFE(node)) { - - const isFunctionExpression = node.type === "FunctionExpression"; - - // Check if the function is referenced elsewhere in the code - const isFunctionReferenced = isFunctionExpression && node.id ? references.some(r => r.identifier.name === node.id.name) : false; - - if (!isFunctionReferenced) { - SKIPPED_IIFE_NODES.add(node); - return; - } - } - } - - const unsafeRefs = references.filter(r => r.resolved && !isSafe(loopNode, r)).map(r => r.identifier.name); - - if (unsafeRefs.length > 0) { - context.report({ - node, - messageId: "unsafeRefs", - data: { varNames: `'${unsafeRefs.join("', '")}'` } - }); - } - } - - return { - ArrowFunctionExpression: checkForLoops, - FunctionExpression: checkForLoops, - FunctionDeclaration: checkForLoops - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow function declarations that contain unsafe references inside loop statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-loop-func", + }, + + schema: [], + + messages: { + unsafeRefs: + "Function declared in a loop contains unsafe references to variable(s) {{ varNames }}.", + }, + }, + + create(context) { + const SKIPPED_IIFE_NODES = new Set(); + const sourceCode = context.sourceCode; + + /** + * Gets the containing loop node of a specified node. + * + * We don't need to check nested functions, so this ignores those, with the exception of IIFE. + * `Scope.through` contains references of nested functions. + * @param {ASTNode} node An AST node to get. + * @returns {ASTNode|null} The containing loop node of the specified node, or + * `null`. + */ + function getContainingLoopNode(node) { + for ( + let currentNode = node; + currentNode.parent; + currentNode = currentNode.parent + ) { + const parent = currentNode.parent; + + switch (parent.type) { + case "WhileStatement": + case "DoWhileStatement": + return parent; + + case "ForStatement": + // `init` is outside of the loop. + if (parent.init !== currentNode) { + return parent; + } + break; + + case "ForInStatement": + case "ForOfStatement": + // `right` is outside of the loop. + if (parent.right !== currentNode) { + return parent; + } + break; + + case "ArrowFunctionExpression": + case "FunctionExpression": + case "FunctionDeclaration": + // We need to check nested functions only in case of IIFE. + if (SKIPPED_IIFE_NODES.has(parent)) { + break; + } + + return null; + default: + break; + } + } + + return null; + } + + /** + * Gets the containing loop node of a given node. + * If the loop was nested, this returns the most outer loop. + * @param {ASTNode} node A node to get. This is a loop node. + * @param {ASTNode|null} excludedNode A node that the result node should not + * include. + * @returns {ASTNode} The most outer loop node. + */ + function getTopLoopNode(node, excludedNode) { + const border = excludedNode ? excludedNode.range[1] : 0; + let retv = node; + let containingLoopNode = node; + + while ( + containingLoopNode && + containingLoopNode.range[0] >= border + ) { + retv = containingLoopNode; + containingLoopNode = getContainingLoopNode(containingLoopNode); + } + + return retv; + } + + /** + * Checks whether a given reference which refers to an upper scope's variable is + * safe or not. + * @param {ASTNode} loopNode A containing loop node. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is safe or not. + */ + function isSafe(loopNode, reference) { + const variable = reference.resolved; + const definition = variable && variable.defs[0]; + const declaration = definition && definition.parent; + const kind = + declaration && declaration.type === "VariableDeclaration" + ? declaration.kind + : ""; + + // Variables which are declared by `const` is safe. + if (kind === "const") { + return true; + } + + /* + * Variables which are declared by `let` in the loop is safe. + * It's a different instance from the next loop step's. + */ + if ( + kind === "let" && + declaration.range[0] > loopNode.range[0] && + declaration.range[1] < loopNode.range[1] + ) { + return true; + } + + /* + * WriteReferences which exist after this border are unsafe because those + * can modify the variable. + */ + const border = getTopLoopNode( + loopNode, + kind === "let" ? declaration : null, + ).range[0]; + + /** + * Checks whether a given reference is safe or not. + * The reference is every reference of the upper scope's variable we are + * looking now. + * + * It's safe if the reference matches one of the following condition. + * - is readonly. + * - doesn't exist inside a local function and after the border. + * @param {eslint-scope.Reference} upperRef A reference to check. + * @returns {boolean} `true` if the reference is safe. + */ + function isSafeReference(upperRef) { + const id = upperRef.identifier; + + return ( + !upperRef.isWrite() || + (variable.scope.variableScope === + upperRef.from.variableScope && + id.range[0] < border) + ); + } + + return ( + Boolean(variable) && variable.references.every(isSafeReference) + ); + } + + /** + * Reports functions which match the following condition: + * + * - has a loop node in ancestors. + * - has any references which refers to an unsafe variable. + * @param {ASTNode} node The AST node to check. + * @returns {void} + */ + function checkForLoops(node) { + const loopNode = getContainingLoopNode(node); + + if (!loopNode) { + return; + } + + const references = sourceCode.getScope(node).through; + + // Check if the function is not asynchronous or a generator function + if (!(node.async || node.generator)) { + if (isIIFE(node)) { + const isFunctionExpression = + node.type === "FunctionExpression"; + + // Check if the function is referenced elsewhere in the code + const isFunctionReferenced = + isFunctionExpression && node.id + ? references.some( + r => r.identifier.name === node.id.name, + ) + : false; + + if (!isFunctionReferenced) { + SKIPPED_IIFE_NODES.add(node); + return; + } + } + } + + const unsafeRefs = references + .filter(r => r.resolved && !isSafe(loopNode, r)) + .map(r => r.identifier.name); + + if (unsafeRefs.length > 0) { + context.report({ + node, + messageId: "unsafeRefs", + data: { varNames: `'${unsafeRefs.join("', '")}'` }, + }); + } + } + + return { + ArrowFunctionExpression: checkForLoops, + FunctionExpression: checkForLoops, + FunctionDeclaration: checkForLoops, + }; + }, }; diff --git a/lib/rules/no-loss-of-precision.js b/lib/rules/no-loss-of-precision.js index c50d8a890555..120342bc998f 100644 --- a/lib/rules/no-loss-of-precision.js +++ b/lib/rules/no-loss-of-precision.js @@ -11,204 +11,219 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow literal numbers that lose precision", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-loss-of-precision" - }, - schema: [], - messages: { - noLossOfPrecision: "This number literal will lose precision at runtime." - } - }, - - create(context) { - - /** - * Returns whether the node is number literal - * @param {Node} node the node literal being evaluated - * @returns {boolean} true if the node is a number literal - */ - function isNumber(node) { - return typeof node.value === "number"; - } - - /** - * Gets the source code of the given number literal. Removes `_` numeric separators from the result. - * @param {Node} node the number `Literal` node - * @returns {string} raw source code of the literal, without numeric separators - */ - function getRaw(node) { - return node.raw.replace(/_/gu, ""); - } - - /** - * Checks whether the number is base ten - * @param {ASTNode} node the node being evaluated - * @returns {boolean} true if the node is in base ten - */ - function isBaseTen(node) { - const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"]; - - return prefixes.every(prefix => !node.raw.startsWith(prefix)) && - !/^0[0-7]+$/u.test(node.raw); - } - - /** - * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type - * @param {Node} node the node being evaluated - * @returns {boolean} true if they do not match - */ - function notBaseTenLosesPrecision(node) { - const rawString = getRaw(node).toUpperCase(); - let base; - - if (rawString.startsWith("0B")) { - base = 2; - } else if (rawString.startsWith("0X")) { - base = 16; - } else { - base = 8; - } - - return !rawString.endsWith(node.value.toString(base).toUpperCase()); - } - - /** - * Adds a decimal point to the numeric string at index 1 - * @param {string} stringNumber the numeric string without any decimal point - * @returns {string} the numeric string with a decimal point in the proper place - */ - function addDecimalPointToNumber(stringNumber) { - return `${stringNumber[0]}.${stringNumber.slice(1)}`; - } - - /** - * Returns the number stripped of leading zeros - * @param {string} numberAsString the string representation of the number - * @returns {string} the stripped string - */ - function removeLeadingZeros(numberAsString) { - for (let i = 0; i < numberAsString.length; i++) { - if (numberAsString[i] !== "0") { - return numberAsString.slice(i); - } - } - return numberAsString; - } - - /** - * Returns the number stripped of trailing zeros - * @param {string} numberAsString the string representation of the number - * @returns {string} the stripped string - */ - function removeTrailingZeros(numberAsString) { - for (let i = numberAsString.length - 1; i >= 0; i--) { - if (numberAsString[i] !== "0") { - return numberAsString.slice(0, i + 1); - } - } - return numberAsString; - } - - /** - * Converts an integer to an object containing the integer's coefficient and order of magnitude - * @param {string} stringInteger the string representation of the integer being converted - * @returns {Object} the object containing the integer's coefficient and order of magnitude - */ - function normalizeInteger(stringInteger) { - const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger)); - - return { - magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1, - coefficient: addDecimalPointToNumber(significantDigits) - }; - } - - /** - * - * Converts a float to an object containing the floats's coefficient and order of magnitude - * @param {string} stringFloat the string representation of the float being converted - * @returns {Object} the object containing the integer's coefficient and order of magnitude - */ - function normalizeFloat(stringFloat) { - const trimmedFloat = removeLeadingZeros(stringFloat); - - if (trimmedFloat.startsWith(".")) { - const decimalDigits = trimmedFloat.slice(1); - const significantDigits = removeLeadingZeros(decimalDigits); - - return { - magnitude: significantDigits.length - decimalDigits.length - 1, - coefficient: addDecimalPointToNumber(significantDigits) - }; - - } - return { - magnitude: trimmedFloat.indexOf(".") - 1, - coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", "")) - - }; - } - - /** - * Converts a base ten number to proper scientific notation - * @param {string} stringNumber the string representation of the base ten number to be converted - * @returns {string} the number converted to scientific notation - */ - function convertNumberToScientificNotation(stringNumber) { - const splitNumber = stringNumber.replace("E", "e").split("e"); - const originalCoefficient = splitNumber[0]; - const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient) - : normalizeInteger(originalCoefficient); - const normalizedCoefficient = normalizedNumber.coefficient; - const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude) - : normalizedNumber.magnitude; - - return `${normalizedCoefficient}e${magnitude}`; - } - - /** - * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type - * @param {Node} node the node being evaluated - * @returns {boolean} true if they do not match - */ - function baseTenLosesPrecision(node) { - const normalizedRawNumber = convertNumberToScientificNotation(getRaw(node)); - const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length; - - if (requestedPrecision > 100) { - return true; - } - const storedNumber = node.value.toPrecision(requestedPrecision); - const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber); - - return normalizedRawNumber !== normalizedStoredNumber; - } - - - /** - * Checks that the user-intended number equals the actual number after is has been converted to the Number type - * @param {Node} node the node being evaluated - * @returns {boolean} true if they do not match - */ - function losesPrecision(node) { - return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node); - } - - - return { - Literal(node) { - if (node.value && isNumber(node) && losesPrecision(node)) { - context.report({ - messageId: "noLossOfPrecision", - node - }); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow literal numbers that lose precision", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-loss-of-precision", + }, + schema: [], + messages: { + noLossOfPrecision: + "This number literal will lose precision at runtime.", + }, + }, + + create(context) { + /** + * Returns whether the node is number literal + * @param {Node} node the node literal being evaluated + * @returns {boolean} true if the node is a number literal + */ + function isNumber(node) { + return typeof node.value === "number"; + } + + /** + * Gets the source code of the given number literal. Removes `_` numeric separators from the result. + * @param {Node} node the number `Literal` node + * @returns {string} raw source code of the literal, without numeric separators + */ + function getRaw(node) { + return node.raw.replace(/_/gu, ""); + } + + /** + * Checks whether the number is base ten + * @param {ASTNode} node the node being evaluated + * @returns {boolean} true if the node is in base ten + */ + function isBaseTen(node) { + const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"]; + + return ( + prefixes.every(prefix => !node.raw.startsWith(prefix)) && + !/^0[0-7]+$/u.test(node.raw) + ); + } + + /** + * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type + * @param {Node} node the node being evaluated + * @returns {boolean} true if they do not match + */ + function notBaseTenLosesPrecision(node) { + const rawString = getRaw(node).toUpperCase(); + let base; + + if (rawString.startsWith("0B")) { + base = 2; + } else if (rawString.startsWith("0X")) { + base = 16; + } else { + base = 8; + } + + return !rawString.endsWith(node.value.toString(base).toUpperCase()); + } + + /** + * Adds a decimal point to the numeric string at index 1 + * @param {string} stringNumber the numeric string without any decimal point + * @returns {string} the numeric string with a decimal point in the proper place + */ + function addDecimalPointToNumber(stringNumber) { + return `${stringNumber[0]}.${stringNumber.slice(1)}`; + } + + /** + * Returns the number stripped of leading zeros + * @param {string} numberAsString the string representation of the number + * @returns {string} the stripped string + */ + function removeLeadingZeros(numberAsString) { + for (let i = 0; i < numberAsString.length; i++) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(i); + } + } + return numberAsString; + } + + /** + * Returns the number stripped of trailing zeros + * @param {string} numberAsString the string representation of the number + * @returns {string} the stripped string + */ + function removeTrailingZeros(numberAsString) { + for (let i = numberAsString.length - 1; i >= 0; i--) { + if (numberAsString[i] !== "0") { + return numberAsString.slice(0, i + 1); + } + } + return numberAsString; + } + + /** + * Converts an integer to an object containing the integer's coefficient and order of magnitude + * @param {string} stringInteger the string representation of the integer being converted + * @returns {Object} the object containing the integer's coefficient and order of magnitude + */ + function normalizeInteger(stringInteger) { + const significantDigits = removeTrailingZeros( + removeLeadingZeros(stringInteger), + ); + + return { + magnitude: stringInteger.startsWith("0") + ? stringInteger.length - 2 + : stringInteger.length - 1, + coefficient: addDecimalPointToNumber(significantDigits), + }; + } + + /** + * + * Converts a float to an object containing the floats's coefficient and order of magnitude + * @param {string} stringFloat the string representation of the float being converted + * @returns {Object} the object containing the integer's coefficient and order of magnitude + */ + function normalizeFloat(stringFloat) { + const trimmedFloat = removeLeadingZeros(stringFloat); + + if (trimmedFloat.startsWith(".")) { + const decimalDigits = trimmedFloat.slice(1); + const significantDigits = removeLeadingZeros(decimalDigits); + + return { + magnitude: + significantDigits.length - decimalDigits.length - 1, + coefficient: addDecimalPointToNumber(significantDigits), + }; + } + return { + magnitude: trimmedFloat.indexOf(".") - 1, + coefficient: addDecimalPointToNumber( + trimmedFloat.replace(".", ""), + ), + }; + } + + /** + * Converts a base ten number to proper scientific notation + * @param {string} stringNumber the string representation of the base ten number to be converted + * @returns {string} the number converted to scientific notation + */ + function convertNumberToScientificNotation(stringNumber) { + const splitNumber = stringNumber.replace("E", "e").split("e"); + const originalCoefficient = splitNumber[0]; + const normalizedNumber = stringNumber.includes(".") + ? normalizeFloat(originalCoefficient) + : normalizeInteger(originalCoefficient); + const normalizedCoefficient = normalizedNumber.coefficient; + const magnitude = + splitNumber.length > 1 + ? parseInt(splitNumber[1], 10) + normalizedNumber.magnitude + : normalizedNumber.magnitude; + + return `${normalizedCoefficient}e${magnitude}`; + } + + /** + * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type + * @param {Node} node the node being evaluated + * @returns {boolean} true if they do not match + */ + function baseTenLosesPrecision(node) { + const normalizedRawNumber = convertNumberToScientificNotation( + getRaw(node), + ); + const requestedPrecision = normalizedRawNumber + .split("e")[0] + .replace(".", "").length; + + if (requestedPrecision > 100) { + return true; + } + const storedNumber = node.value.toPrecision(requestedPrecision); + const normalizedStoredNumber = + convertNumberToScientificNotation(storedNumber); + + return normalizedRawNumber !== normalizedStoredNumber; + } + + /** + * Checks that the user-intended number equals the actual number after is has been converted to the Number type + * @param {Node} node the node being evaluated + * @returns {boolean} true if they do not match + */ + function losesPrecision(node) { + return isBaseTen(node) + ? baseTenLosesPrecision(node) + : notBaseTenLosesPrecision(node); + } + + return { + Literal(node) { + if (node.value && isNumber(node) && losesPrecision(node)) { + context.report({ + messageId: "noLossOfPrecision", + node, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-magic-numbers.js b/lib/rules/no-magic-numbers.js index 4cda74dd886d..7e30ab312c16 100644 --- a/lib/rules/no-magic-numbers.js +++ b/lib/rules/no-magic-numbers.js @@ -20,225 +20,253 @@ const MAX_ARRAY_LENGTH = 2 ** 32 - 1; * @returns {bigint|number} The normalized value. */ function normalizeIgnoreValue(x) { - if (typeof x === "string") { - return BigInt(x.slice(0, -1)); - } - return x; + if (typeof x === "string") { + return BigInt(x.slice(0, -1)); + } + return x; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow magic numbers", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-magic-numbers" - }, - - schema: [{ - type: "object", - properties: { - detectObjects: { - type: "boolean", - default: false - }, - enforceConst: { - type: "boolean", - default: false - }, - ignore: { - type: "array", - items: { - anyOf: [ - { type: "number" }, - { type: "string", pattern: "^[+-]?(?:0|[1-9][0-9]*)n$" } - ] - }, - uniqueItems: true - }, - ignoreArrayIndexes: { - type: "boolean", - default: false - }, - ignoreDefaultValues: { - type: "boolean", - default: false - }, - ignoreClassFieldInitialValues: { - type: "boolean", - default: false - } - }, - additionalProperties: false - }], - - messages: { - useConst: "Number constants declarations must use 'const'.", - noMagic: "No magic number: {{raw}}." - } - }, - - create(context) { - const config = context.options[0] || {}, - detectObjects = !!config.detectObjects, - enforceConst = !!config.enforceConst, - ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)), - ignoreArrayIndexes = !!config.ignoreArrayIndexes, - ignoreDefaultValues = !!config.ignoreDefaultValues, - ignoreClassFieldInitialValues = !!config.ignoreClassFieldInitialValues; - - const okTypes = detectObjects ? [] : ["ObjectExpression", "Property", "AssignmentExpression"]; - - /** - * Returns whether the rule is configured to ignore the given value - * @param {bigint|number} value The value to check - * @returns {boolean} true if the value is ignored - */ - function isIgnoredValue(value) { - return ignore.has(value); - } - - /** - * Returns whether the number is a default value assignment. - * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node - * @returns {boolean} true if the number is a default value - */ - function isDefaultValue(fullNumberNode) { - const parent = fullNumberNode.parent; - - return parent.type === "AssignmentPattern" && parent.right === fullNumberNode; - } - - /** - * Returns whether the number is the initial value of a class field. - * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node - * @returns {boolean} true if the number is the initial value of a class field. - */ - function isClassFieldInitialValue(fullNumberNode) { - const parent = fullNumberNode.parent; - - return parent.type === "PropertyDefinition" && parent.value === fullNumberNode; - } - - /** - * Returns whether the given node is used as a radix within parseInt() or Number.parseInt() - * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node - * @returns {boolean} true if the node is radix - */ - function isParseIntRadix(fullNumberNode) { - const parent = fullNumberNode.parent; - - return parent.type === "CallExpression" && fullNumberNode === parent.arguments[1] && - ( - astUtils.isSpecificId(parent.callee, "parseInt") || - astUtils.isSpecificMemberAccess(parent.callee, "Number", "parseInt") - ); - } - - /** - * Returns whether the given node is a direct child of a JSX node. - * In particular, it aims to detect numbers used as prop values in JSX tags. - * Example: - * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node - * @returns {boolean} true if the node is a JSX number - */ - function isJSXNumber(fullNumberNode) { - return fullNumberNode.parent.type.indexOf("JSX") === 0; - } - - /** - * Returns whether the given node is used as an array index. - * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294". - * - * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties, - * which can be created and accessed on an array in addition to the array index properties, - * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc. - * - * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295, - * thus the maximum valid index is 2 ** 32 - 2 = 4294967294. - * - * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294". - * - * Valid examples: - * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n] - * a[-0] (same as a[0] because -0 coerces to "0") - * a[-0n] (-0n evaluates to 0n) - * - * Invalid examples: - * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1] - * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"]) - * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"]) - * a[1e310] (same as a["Infinity"]) - * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node - * @param {bigint|number} value Value expressed by the fullNumberNode - * @returns {boolean} true if the node is a valid array index - */ - function isArrayIndex(fullNumberNode, value) { - const parent = fullNumberNode.parent; - - return parent.type === "MemberExpression" && parent.property === fullNumberNode && - (Number.isInteger(value) || typeof value === "bigint") && - value >= 0 && value < MAX_ARRAY_LENGTH; - } - - return { - Literal(node) { - if (!astUtils.isNumericLiteral(node)) { - return; - } - - let fullNumberNode; - let value; - let raw; - - // Treat unary minus as a part of the number - if (node.parent.type === "UnaryExpression" && node.parent.operator === "-") { - fullNumberNode = node.parent; - value = -node.value; - raw = `-${node.raw}`; - } else { - fullNumberNode = node; - value = node.value; - raw = node.raw; - } - - const parent = fullNumberNode.parent; - - // Always allow radix arguments and JSX props - if ( - isIgnoredValue(value) || - (ignoreDefaultValues && isDefaultValue(fullNumberNode)) || - (ignoreClassFieldInitialValues && isClassFieldInitialValue(fullNumberNode)) || - isParseIntRadix(fullNumberNode) || - isJSXNumber(fullNumberNode) || - (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value)) - ) { - return; - } - - if (parent.type === "VariableDeclarator") { - if (enforceConst && parent.parent.kind !== "const") { - context.report({ - node: fullNumberNode, - messageId: "useConst" - }); - } - } else if ( - !okTypes.includes(parent.type) || - (parent.type === "AssignmentExpression" && parent.left.type === "Identifier") - ) { - context.report({ - node: fullNumberNode, - messageId: "noMagic", - data: { - raw - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow magic numbers", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-magic-numbers", + }, + + schema: [ + { + type: "object", + properties: { + detectObjects: { + type: "boolean", + default: false, + }, + enforceConst: { + type: "boolean", + default: false, + }, + ignore: { + type: "array", + items: { + anyOf: [ + { type: "number" }, + { + type: "string", + pattern: "^[+-]?(?:0|[1-9][0-9]*)n$", + }, + ], + }, + uniqueItems: true, + }, + ignoreArrayIndexes: { + type: "boolean", + default: false, + }, + ignoreDefaultValues: { + type: "boolean", + default: false, + }, + ignoreClassFieldInitialValues: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + useConst: "Number constants declarations must use 'const'.", + noMagic: "No magic number: {{raw}}.", + }, + }, + + create(context) { + const config = context.options[0] || {}, + detectObjects = !!config.detectObjects, + enforceConst = !!config.enforceConst, + ignore = new Set((config.ignore || []).map(normalizeIgnoreValue)), + ignoreArrayIndexes = !!config.ignoreArrayIndexes, + ignoreDefaultValues = !!config.ignoreDefaultValues, + ignoreClassFieldInitialValues = + !!config.ignoreClassFieldInitialValues; + + const okTypes = detectObjects + ? [] + : ["ObjectExpression", "Property", "AssignmentExpression"]; + + /** + * Returns whether the rule is configured to ignore the given value + * @param {bigint|number} value The value to check + * @returns {boolean} true if the value is ignored + */ + function isIgnoredValue(value) { + return ignore.has(value); + } + + /** + * Returns whether the number is a default value assignment. + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the number is a default value + */ + function isDefaultValue(fullNumberNode) { + const parent = fullNumberNode.parent; + + return ( + parent.type === "AssignmentPattern" && + parent.right === fullNumberNode + ); + } + + /** + * Returns whether the number is the initial value of a class field. + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the number is the initial value of a class field. + */ + function isClassFieldInitialValue(fullNumberNode) { + const parent = fullNumberNode.parent; + + return ( + parent.type === "PropertyDefinition" && + parent.value === fullNumberNode + ); + } + + /** + * Returns whether the given node is used as a radix within parseInt() or Number.parseInt() + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the node is radix + */ + function isParseIntRadix(fullNumberNode) { + const parent = fullNumberNode.parent; + + return ( + parent.type === "CallExpression" && + fullNumberNode === parent.arguments[1] && + (astUtils.isSpecificId(parent.callee, "parseInt") || + astUtils.isSpecificMemberAccess( + parent.callee, + "Number", + "parseInt", + )) + ); + } + + /** + * Returns whether the given node is a direct child of a JSX node. + * In particular, it aims to detect numbers used as prop values in JSX tags. + * Example: + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @returns {boolean} true if the node is a JSX number + */ + function isJSXNumber(fullNumberNode) { + return fullNumberNode.parent.type.indexOf("JSX") === 0; + } + + /** + * Returns whether the given node is used as an array index. + * Value must coerce to a valid array index name: "0", "1", "2" ... "4294967294". + * + * All other values, like "-1", "2.5", or "4294967295", are just "normal" object properties, + * which can be created and accessed on an array in addition to the array index properties, + * but they don't affect array's length and are not considered by methods such as .map(), .forEach() etc. + * + * The maximum array length by the specification is 2 ** 32 - 1 = 4294967295, + * thus the maximum valid index is 2 ** 32 - 2 = 4294967294. + * + * All notations are allowed, as long as the value coerces to one of "0", "1", "2" ... "4294967294". + * + * Valid examples: + * a[0], a[1], a[1.2e1], a[0xAB], a[0n], a[1n] + * a[-0] (same as a[0] because -0 coerces to "0") + * a[-0n] (-0n evaluates to 0n) + * + * Invalid examples: + * a[-1], a[-0xAB], a[-1n], a[2.5], a[1.23e1], a[12e-1] + * a[4294967295] (above the max index, it's an access to a regular property a["4294967295"]) + * a[999999999999999999999] (even if it wasn't above the max index, it would be a["1e+21"]) + * a[1e310] (same as a["Infinity"]) + * @param {ASTNode} fullNumberNode `Literal` or `UnaryExpression` full number node + * @param {bigint|number} value Value expressed by the fullNumberNode + * @returns {boolean} true if the node is a valid array index + */ + function isArrayIndex(fullNumberNode, value) { + const parent = fullNumberNode.parent; + + return ( + parent.type === "MemberExpression" && + parent.property === fullNumberNode && + (Number.isInteger(value) || typeof value === "bigint") && + value >= 0 && + value < MAX_ARRAY_LENGTH + ); + } + + return { + Literal(node) { + if (!astUtils.isNumericLiteral(node)) { + return; + } + + let fullNumberNode; + let value; + let raw; + + // Treat unary minus as a part of the number + if ( + node.parent.type === "UnaryExpression" && + node.parent.operator === "-" + ) { + fullNumberNode = node.parent; + value = -node.value; + raw = `-${node.raw}`; + } else { + fullNumberNode = node; + value = node.value; + raw = node.raw; + } + + const parent = fullNumberNode.parent; + + // Always allow radix arguments and JSX props + if ( + isIgnoredValue(value) || + (ignoreDefaultValues && isDefaultValue(fullNumberNode)) || + (ignoreClassFieldInitialValues && + isClassFieldInitialValue(fullNumberNode)) || + isParseIntRadix(fullNumberNode) || + isJSXNumber(fullNumberNode) || + (ignoreArrayIndexes && isArrayIndex(fullNumberNode, value)) + ) { + return; + } + + if (parent.type === "VariableDeclarator") { + if (enforceConst && parent.parent.kind !== "const") { + context.report({ + node: fullNumberNode, + messageId: "useConst", + }); + } + } else if ( + !okTypes.includes(parent.type) || + (parent.type === "AssignmentExpression" && + parent.left.type === "Identifier") + ) { + context.report({ + node: fullNumberNode, + messageId: "noMagic", + data: { + raw, + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-misleading-character-class.js b/lib/rules/no-misleading-character-class.js index d10e0812a83a..38f62e2acb34 100644 --- a/lib/rules/no-misleading-character-class.js +++ b/lib/rules/no-misleading-character-class.js @@ -4,17 +4,25 @@ "use strict"; const { - CALL, - CONSTRUCT, - ReferenceTracker, - getStaticValue, - getStringIfConstant + CALL, + CONSTRUCT, + ReferenceTracker, + getStaticValue, + getStringIfConstant, } = require("@eslint-community/eslint-utils"); const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); -const { isCombiningCharacter, isEmojiModifier, isRegionalIndicatorSymbol, isSurrogatePair } = require("./utils/unicode"); +const { + isCombiningCharacter, + isEmojiModifier, + isRegionalIndicatorSymbol, + isSurrogatePair, +} = require("./utils/unicode"); const astUtils = require("./utils/ast-utils.js"); const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); -const { parseStringLiteral, parseTemplateToken } = require("./utils/char-source"); +const { + parseStringLiteral, + parseTemplateToken, +} = require("./utils/char-source"); //------------------------------------------------------------------------------ // Helpers @@ -33,40 +41,39 @@ const { parseStringLiteral, parseTemplateToken } = require("./utils/char-source" * @param {CharacterClassElement[]} nodes The node list to iterate character sequences. * @returns {IterableIterator} The list of character sequences. */ -function *iterateCharacterSequence(nodes) { - - /** @type {Character[]} */ - let seq = []; - - for (const node of nodes) { - switch (node.type) { - case "Character": - seq.push(node); - break; - - case "CharacterClassRange": - seq.push(node.min); - yield seq; - seq = [node.max]; - break; - - case "CharacterSet": - case "CharacterClass": // [[]] nesting character class - case "ClassStringDisjunction": // \q{...} - case "ExpressionCharacterClass": // [A--B] - if (seq.length > 0) { - yield seq; - seq = []; - } - break; - - // no default - } - } - - if (seq.length > 0) { - yield seq; - } +function* iterateCharacterSequence(nodes) { + /** @type {Character[]} */ + let seq = []; + + for (const node of nodes) { + switch (node.type) { + case "Character": + seq.push(node); + break; + + case "CharacterClassRange": + seq.push(node.min); + yield seq; + seq = [node.max]; + break; + + case "CharacterSet": + case "CharacterClass": // [[]] nesting character class + case "ClassStringDisjunction": // \q{...} + case "ExpressionCharacterClass": // [A--B] + if (seq.length > 0) { + yield seq; + seq = []; + } + break; + + // no default + } + } + + if (seq.length > 0) { + yield seq; + } } /** @@ -75,7 +82,7 @@ function *iterateCharacterSequence(nodes) { * @returns {boolean} `true` if the character node is a Unicode code point escape. */ function isUnicodeCodePointEscape(char) { - return /^\\u\{[\da-f]+\}$/iu.test(char.raw); + return /^\\u\{[\da-f]+\}$/iu.test(char.raw); } /** @@ -83,115 +90,119 @@ function isUnicodeCodePointEscape(char) { * @type {Record IterableIterator>} */ const findCharacterSequences = { - *surrogatePairWithoutUFlag(chars) { - for (const [index, char] of chars.entries()) { - const previous = chars[index - 1]; - - if ( - previous && char && - isSurrogatePair(previous.value, char.value) && - !isUnicodeCodePointEscape(previous) && - !isUnicodeCodePointEscape(char) - ) { - yield [previous, char]; - } - } - }, - - *surrogatePair(chars) { - for (const [index, char] of chars.entries()) { - const previous = chars[index - 1]; - - if ( - previous && char && - isSurrogatePair(previous.value, char.value) && - ( - isUnicodeCodePointEscape(previous) || - isUnicodeCodePointEscape(char) - ) - ) { - yield [previous, char]; - } - } - }, - - *combiningClass(chars, unfilteredChars) { - - /* - * When `allowEscape` is `true`, a combined character should only be allowed if the combining mark appears as an escape sequence. - * This means that the base character should be considered even if it's escaped. - */ - for (const [index, char] of chars.entries()) { - const previous = unfilteredChars[index - 1]; - - if ( - previous && char && - isCombiningCharacter(char.value) && - !isCombiningCharacter(previous.value) - ) { - yield [previous, char]; - } - } - }, - - *emojiModifier(chars) { - for (const [index, char] of chars.entries()) { - const previous = chars[index - 1]; - - if ( - previous && char && - isEmojiModifier(char.value) && - !isEmojiModifier(previous.value) - ) { - yield [previous, char]; - } - } - }, - - *regionalIndicatorSymbol(chars) { - for (const [index, char] of chars.entries()) { - const previous = chars[index - 1]; - - if ( - previous && char && - isRegionalIndicatorSymbol(char.value) && - isRegionalIndicatorSymbol(previous.value) - ) { - yield [previous, char]; - } - } - }, - - *zwj(chars) { - let sequence = null; - - for (const [index, char] of chars.entries()) { - const previous = chars[index - 1]; - const next = chars[index + 1]; - - if ( - previous && char && next && - char.value === 0x200d && - previous.value !== 0x200d && - next.value !== 0x200d - ) { - if (sequence) { - if (sequence.at(-1) === previous) { - sequence.push(char, next); // append to the sequence - } else { - yield sequence; - sequence = chars.slice(index - 1, index + 2); - } - } else { - sequence = chars.slice(index - 1, index + 2); - } - } - } - - if (sequence) { - yield sequence; - } - } + *surrogatePairWithoutUFlag(chars) { + for (const [index, char] of chars.entries()) { + const previous = chars[index - 1]; + + if ( + previous && + char && + isSurrogatePair(previous.value, char.value) && + !isUnicodeCodePointEscape(previous) && + !isUnicodeCodePointEscape(char) + ) { + yield [previous, char]; + } + } + }, + + *surrogatePair(chars) { + for (const [index, char] of chars.entries()) { + const previous = chars[index - 1]; + + if ( + previous && + char && + isSurrogatePair(previous.value, char.value) && + (isUnicodeCodePointEscape(previous) || + isUnicodeCodePointEscape(char)) + ) { + yield [previous, char]; + } + } + }, + + *combiningClass(chars, unfilteredChars) { + /* + * When `allowEscape` is `true`, a combined character should only be allowed if the combining mark appears as an escape sequence. + * This means that the base character should be considered even if it's escaped. + */ + for (const [index, char] of chars.entries()) { + const previous = unfilteredChars[index - 1]; + + if ( + previous && + char && + isCombiningCharacter(char.value) && + !isCombiningCharacter(previous.value) + ) { + yield [previous, char]; + } + } + }, + + *emojiModifier(chars) { + for (const [index, char] of chars.entries()) { + const previous = chars[index - 1]; + + if ( + previous && + char && + isEmojiModifier(char.value) && + !isEmojiModifier(previous.value) + ) { + yield [previous, char]; + } + } + }, + + *regionalIndicatorSymbol(chars) { + for (const [index, char] of chars.entries()) { + const previous = chars[index - 1]; + + if ( + previous && + char && + isRegionalIndicatorSymbol(char.value) && + isRegionalIndicatorSymbol(previous.value) + ) { + yield [previous, char]; + } + } + }, + + *zwj(chars) { + let sequence = null; + + for (const [index, char] of chars.entries()) { + const previous = chars[index - 1]; + const next = chars[index + 1]; + + if ( + previous && + char && + next && + char.value === 0x200d && + previous.value !== 0x200d && + next.value !== 0x200d + ) { + if (sequence) { + if (sequence.at(-1) === previous) { + sequence.push(char, next); // append to the sequence + } else { + yield sequence; + sequence = chars.slice(index - 1, index + 2); + } + } else { + sequence = chars.slice(index - 1, index + 2); + } + } + } + + if (sequence) { + yield sequence; + } + }, }; const kinds = Object.keys(findCharacterSequences); @@ -208,19 +219,19 @@ const kinds = Object.keys(findCharacterSequences); * @returns {{ value: any } | { regex: { pattern: string, flags: string } } | null} The static value of the node, or `null`. */ function getStaticValueOrRegex(node, initialScope) { - if (!node) { - return null; - } - if (node.type === "Literal" && node.regex) { - return { regex: node.regex }; - } - - const staticValue = getStaticValue(node, initialScope); - - if (staticValue?.value instanceof RegExp) { - return null; - } - return staticValue; + if (!node) { + return null; + } + if (node.type === "Literal" && node.regex) { + return { regex: node.regex }; + } + + const staticValue = getStaticValue(node, initialScope); + + if (staticValue?.value instanceof RegExp) { + return null; + } + return staticValue; } /** @@ -231,12 +242,12 @@ function getStaticValueOrRegex(node, initialScope) { * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. */ function checkForAcceptableEscape(char, charSource) { - if (!charSource.startsWith("\\")) { - return false; - } - const match = /(?<=^\\+).$/su.exec(charSource); + if (!charSource.startsWith("\\")) { + return false; + } + const match = /(?<=^\\+).$/su.exec(charSource); - return match?.[0] !== String.fromCodePoint(char.value); + return match?.[0] !== String.fromCodePoint(char.value); } /** @@ -249,13 +260,13 @@ function checkForAcceptableEscape(char, charSource) { * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. */ function checkForAcceptableEscapeInString(char, nodeSource, codeUnits) { - const firstIndex = char.start; - const lastIndex = char.end - 1; - const start = codeUnits[firstIndex].start; - const end = codeUnits[lastIndex].end; - const charSource = nodeSource.slice(start, end); + const firstIndex = char.start; + const lastIndex = char.end - 1; + const start = codeUnits[firstIndex].start; + const end = codeUnits[lastIndex].end; + const charSource = nodeSource.slice(start, end); - return checkForAcceptableEscape(char, charSource); + return checkForAcceptableEscape(char, charSource); } //------------------------------------------------------------------------------ @@ -264,274 +275,316 @@ function checkForAcceptableEscapeInString(char, nodeSource, codeUnits) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow characters which are made with multiple code points in character class syntax", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-misleading-character-class" - }, - - hasSuggestions: true, - - schema: [ - { - type: "object", - properties: { - allowEscape: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - messages: { - surrogatePairWithoutUFlag: "Unexpected surrogate pair in character class. Use 'u' flag.", - surrogatePair: "Unexpected surrogate pair in character class.", - combiningClass: "Unexpected combined character in character class.", - emojiModifier: "Unexpected modified Emoji in character class.", - regionalIndicatorSymbol: "Unexpected national flag in character class.", - zwj: "Unexpected joined character sequence in character class.", - suggestUnicodeFlag: "Add unicode 'u' flag to regex." - } - }, - create(context) { - const allowEscape = context.options[0]?.allowEscape; - const sourceCode = context.sourceCode; - const parser = new RegExpParser(); - const checkedPatternNodes = new Set(); - - /** - * Verify a given regular expression. - * @param {Node} node The node to report. - * @param {string} pattern The regular expression pattern to verify. - * @param {string} flags The flags of the regular expression. - * @param {Function} unicodeFixer Fixer for missing "u" flag. - * @returns {void} - */ - function verify(node, pattern, flags, unicodeFixer) { - let patternNode; - - try { - patternNode = parser.parsePattern( - pattern, - 0, - pattern.length, - { - unicode: flags.includes("u"), - unicodeSets: flags.includes("v") - } - ); - } catch { - - // Ignore regular expressions with syntax errors - return; - } - - let codeUnits = null; - - /** - * Checks whether a specified regexpp character is represented as an acceptable escape sequence. - * For the purposes of this rule, an escape sequence is considered acceptable if it consists of one or more backslashes followed by the character being escaped. - * @param {Character} char Character to check. - * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. - */ - function isAcceptableEscapeSequence(char) { - if (node.type === "Literal" && node.regex) { - return checkForAcceptableEscape(char, char.raw); - } - if (node.type === "Literal" && typeof node.value === "string") { - const nodeSource = node.raw; - - codeUnits ??= parseStringLiteral(nodeSource); - - return checkForAcceptableEscapeInString(char, nodeSource, codeUnits); - } - if (astUtils.isStaticTemplateLiteral(node)) { - const nodeSource = sourceCode.getText(node); - - codeUnits ??= parseTemplateToken(nodeSource); - - return checkForAcceptableEscapeInString(char, nodeSource, codeUnits); - } - return false; - } - - const foundKindMatches = new Map(); - - visitRegExpAST(patternNode, { - onCharacterClassEnter(ccNode) { - for (const unfilteredChars of iterateCharacterSequence(ccNode.elements)) { - let chars; - - if (allowEscape) { - - // Replace escape sequences with null to avoid having them flagged. - chars = unfilteredChars.map(char => (isAcceptableEscapeSequence(char) ? null : char)); - } else { - chars = unfilteredChars; - } - for (const kind of kinds) { - const matches = findCharacterSequences[kind](chars, unfilteredChars); - - if (foundKindMatches.has(kind)) { - foundKindMatches.get(kind).push(...matches); - } else { - foundKindMatches.set(kind, [...matches]); - } - } - } - } - }); - - /** - * Finds the report loc(s) for a range of matches. - * Only literals and expression-less templates generate granular errors. - * @param {Character[][]} matches Lists of individual characters being reported on. - * @returns {Location[]} locs for context.report. - * @see https://github.com/eslint/eslint/pull/17515 - */ - function getNodeReportLocations(matches) { - if (!astUtils.isStaticTemplateLiteral(node) && node.type !== "Literal") { - return matches.length ? [node.loc] : []; - } - return matches.map(chars => { - const firstIndex = chars[0].start; - const lastIndex = chars.at(-1).end - 1; - let start; - let end; - - if (node.type === "TemplateLiteral") { - const source = sourceCode.getText(node); - const offset = node.range[0]; - - codeUnits ??= parseTemplateToken(source); - start = offset + codeUnits[firstIndex].start; - end = offset + codeUnits[lastIndex].end; - } else if (typeof node.value === "string") { // String Literal - const source = node.raw; - const offset = node.range[0]; - - codeUnits ??= parseStringLiteral(source); - start = offset + codeUnits[firstIndex].start; - end = offset + codeUnits[lastIndex].end; - } else { // RegExp Literal - const offset = node.range[0] + 1; // Add 1 to skip the leading slash. - - start = offset + firstIndex; - end = offset + lastIndex + 1; - } - - return { - start: sourceCode.getLocFromIndex(start), - end: sourceCode.getLocFromIndex(end) - }; - }); - } - - for (const [kind, matches] of foundKindMatches) { - let suggest; - - if (kind === "surrogatePairWithoutUFlag") { - suggest = [{ - messageId: "suggestUnicodeFlag", - fix: unicodeFixer - }]; - } - - const locs = getNodeReportLocations(matches); - - for (const loc of locs) { - context.report({ - node, - loc, - messageId: kind, - suggest - }); - } - } - } - - return { - "Literal[regex]"(node) { - if (checkedPatternNodes.has(node)) { - return; - } - verify(node, node.regex.pattern, node.regex.flags, fixer => { - if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern)) { - return null; - } - - return fixer.insertTextAfter(node, "u"); - }); - }, - "Program"(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - - /* - * Iterate calls of RegExp. - * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, - * `const {RegExp: a} = window; new a()`, etc... - */ - for (const { node: refNode } of tracker.iterateGlobalReferences({ - RegExp: { [CALL]: true, [CONSTRUCT]: true } - })) { - let pattern, flags; - const [patternNode, flagsNode] = refNode.arguments; - const evaluatedPattern = getStaticValueOrRegex(patternNode, scope); - - if (!evaluatedPattern) { - continue; - } - if (flagsNode) { - if (evaluatedPattern.regex) { - pattern = evaluatedPattern.regex.pattern; - checkedPatternNodes.add(patternNode); - } else { - pattern = String(evaluatedPattern.value); - } - flags = getStringIfConstant(flagsNode, scope); - } else { - if (evaluatedPattern.regex) { - continue; - } - pattern = String(evaluatedPattern.value); - flags = ""; - } - - if (typeof flags === "string") { - verify(patternNode, pattern, flags, fixer => { - - if (!isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern)) { - return null; - } - - if (refNode.arguments.length === 1) { - const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis - - return fixer.insertTextAfter( - penultimateToken, - astUtils.isCommaToken(penultimateToken) - ? ' "u",' - : ', "u"' - ); - } - - if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { - const range = [flagsNode.range[0], flagsNode.range[1] - 1]; - - return fixer.insertTextAfterRange(range, "u"); - } - - return null; - }); - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow characters which are made with multiple code points in character class syntax", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-misleading-character-class", + }, + + hasSuggestions: true, + + schema: [ + { + type: "object", + properties: { + allowEscape: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + surrogatePairWithoutUFlag: + "Unexpected surrogate pair in character class. Use 'u' flag.", + surrogatePair: "Unexpected surrogate pair in character class.", + combiningClass: "Unexpected combined character in character class.", + emojiModifier: "Unexpected modified Emoji in character class.", + regionalIndicatorSymbol: + "Unexpected national flag in character class.", + zwj: "Unexpected joined character sequence in character class.", + suggestUnicodeFlag: "Add unicode 'u' flag to regex.", + }, + }, + create(context) { + const allowEscape = context.options[0]?.allowEscape; + const sourceCode = context.sourceCode; + const parser = new RegExpParser(); + const checkedPatternNodes = new Set(); + + /** + * Verify a given regular expression. + * @param {Node} node The node to report. + * @param {string} pattern The regular expression pattern to verify. + * @param {string} flags The flags of the regular expression. + * @param {Function} unicodeFixer Fixer for missing "u" flag. + * @returns {void} + */ + function verify(node, pattern, flags, unicodeFixer) { + let patternNode; + + try { + patternNode = parser.parsePattern(pattern, 0, pattern.length, { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v"), + }); + } catch { + // Ignore regular expressions with syntax errors + return; + } + + let codeUnits = null; + + /** + * Checks whether a specified regexpp character is represented as an acceptable escape sequence. + * For the purposes of this rule, an escape sequence is considered acceptable if it consists of one or more backslashes followed by the character being escaped. + * @param {Character} char Character to check. + * @returns {boolean} Whether the specified regexpp character is represented as an acceptable escape sequence. + */ + function isAcceptableEscapeSequence(char) { + if (node.type === "Literal" && node.regex) { + return checkForAcceptableEscape(char, char.raw); + } + if (node.type === "Literal" && typeof node.value === "string") { + const nodeSource = node.raw; + + codeUnits ??= parseStringLiteral(nodeSource); + + return checkForAcceptableEscapeInString( + char, + nodeSource, + codeUnits, + ); + } + if (astUtils.isStaticTemplateLiteral(node)) { + const nodeSource = sourceCode.getText(node); + + codeUnits ??= parseTemplateToken(nodeSource); + + return checkForAcceptableEscapeInString( + char, + nodeSource, + codeUnits, + ); + } + return false; + } + + const foundKindMatches = new Map(); + + visitRegExpAST(patternNode, { + onCharacterClassEnter(ccNode) { + for (const unfilteredChars of iterateCharacterSequence( + ccNode.elements, + )) { + let chars; + + if (allowEscape) { + // Replace escape sequences with null to avoid having them flagged. + chars = unfilteredChars.map(char => + isAcceptableEscapeSequence(char) ? null : char, + ); + } else { + chars = unfilteredChars; + } + for (const kind of kinds) { + const matches = findCharacterSequences[kind]( + chars, + unfilteredChars, + ); + + if (foundKindMatches.has(kind)) { + foundKindMatches.get(kind).push(...matches); + } else { + foundKindMatches.set(kind, [...matches]); + } + } + } + }, + }); + + /** + * Finds the report loc(s) for a range of matches. + * Only literals and expression-less templates generate granular errors. + * @param {Character[][]} matches Lists of individual characters being reported on. + * @returns {Location[]} locs for context.report. + * @see https://github.com/eslint/eslint/pull/17515 + */ + function getNodeReportLocations(matches) { + if ( + !astUtils.isStaticTemplateLiteral(node) && + node.type !== "Literal" + ) { + return matches.length ? [node.loc] : []; + } + return matches.map(chars => { + const firstIndex = chars[0].start; + const lastIndex = chars.at(-1).end - 1; + let start; + let end; + + if (node.type === "TemplateLiteral") { + const source = sourceCode.getText(node); + const offset = node.range[0]; + + codeUnits ??= parseTemplateToken(source); + start = offset + codeUnits[firstIndex].start; + end = offset + codeUnits[lastIndex].end; + } else if (typeof node.value === "string") { + // String Literal + const source = node.raw; + const offset = node.range[0]; + + codeUnits ??= parseStringLiteral(source); + start = offset + codeUnits[firstIndex].start; + end = offset + codeUnits[lastIndex].end; + } else { + // RegExp Literal + const offset = node.range[0] + 1; // Add 1 to skip the leading slash. + + start = offset + firstIndex; + end = offset + lastIndex + 1; + } + + return { + start: sourceCode.getLocFromIndex(start), + end: sourceCode.getLocFromIndex(end), + }; + }); + } + + for (const [kind, matches] of foundKindMatches) { + let suggest; + + if (kind === "surrogatePairWithoutUFlag") { + suggest = [ + { + messageId: "suggestUnicodeFlag", + fix: unicodeFixer, + }, + ]; + } + + const locs = getNodeReportLocations(matches); + + for (const loc of locs) { + context.report({ + node, + loc, + messageId: kind, + suggest, + }); + } + } + } + + return { + "Literal[regex]"(node) { + if (checkedPatternNodes.has(node)) { + return; + } + verify(node, node.regex.pattern, node.regex.flags, fixer => { + if ( + !isValidWithUnicodeFlag( + context.languageOptions.ecmaVersion, + node.regex.pattern, + ) + ) { + return null; + } + + return fixer.insertTextAfter(node, "u"); + }); + }, + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + + /* + * Iterate calls of RegExp. + * E.g., `new RegExp()`, `RegExp()`, `new window.RegExp()`, + * `const {RegExp: a} = window; new a()`, etc... + */ + for (const { node: refNode } of tracker.iterateGlobalReferences( + { + RegExp: { [CALL]: true, [CONSTRUCT]: true }, + }, + )) { + let pattern, flags; + const [patternNode, flagsNode] = refNode.arguments; + const evaluatedPattern = getStaticValueOrRegex( + patternNode, + scope, + ); + + if (!evaluatedPattern) { + continue; + } + if (flagsNode) { + if (evaluatedPattern.regex) { + pattern = evaluatedPattern.regex.pattern; + checkedPatternNodes.add(patternNode); + } else { + pattern = String(evaluatedPattern.value); + } + flags = getStringIfConstant(flagsNode, scope); + } else { + if (evaluatedPattern.regex) { + continue; + } + pattern = String(evaluatedPattern.value); + flags = ""; + } + + if (typeof flags === "string") { + verify(patternNode, pattern, flags, fixer => { + if ( + !isValidWithUnicodeFlag( + context.languageOptions.ecmaVersion, + pattern, + ) + ) { + return null; + } + + if (refNode.arguments.length === 1) { + const penultimateToken = + sourceCode.getLastToken(refNode, { + skip: 1, + }); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken(penultimateToken) + ? ' "u",' + : ', "u"', + ); + } + + if ( + (flagsNode.type === "Literal" && + typeof flagsNode.value === "string") || + flagsNode.type === "TemplateLiteral" + ) { + const range = [ + flagsNode.range[0], + flagsNode.range[1] - 1, + ]; + + return fixer.insertTextAfterRange(range, "u"); + } + + return null; + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-mixed-operators.js b/lib/rules/no-mixed-operators.js index e2c888366e01..45d730fa52e9 100644 --- a/lib/rules/no-mixed-operators.js +++ b/lib/rules/no-mixed-operators.js @@ -24,20 +24,20 @@ const RELATIONAL_OPERATORS = ["in", "instanceof"]; const TERNARY_OPERATOR = ["?:"]; const COALESCE_OPERATOR = ["??"]; const ALL_OPERATORS = [].concat( - ARITHMETIC_OPERATORS, - BITWISE_OPERATORS, - COMPARISON_OPERATORS, - LOGICAL_OPERATORS, - RELATIONAL_OPERATORS, - TERNARY_OPERATOR, - COALESCE_OPERATOR + ARITHMETIC_OPERATORS, + BITWISE_OPERATORS, + COMPARISON_OPERATORS, + LOGICAL_OPERATORS, + RELATIONAL_OPERATORS, + TERNARY_OPERATOR, + COALESCE_OPERATOR, ); const DEFAULT_GROUPS = [ - ARITHMETIC_OPERATORS, - BITWISE_OPERATORS, - COMPARISON_OPERATORS, - LOGICAL_OPERATORS, - RELATIONAL_OPERATORS + ARITHMETIC_OPERATORS, + BITWISE_OPERATORS, + COMPARISON_OPERATORS, + LOGICAL_OPERATORS, + RELATIONAL_OPERATORS, ]; const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u; @@ -47,14 +47,14 @@ const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u; * @returns {Object} Normalized option object. */ function normalizeOptions(options = {}) { - const hasGroups = options.groups && options.groups.length > 0; - const groups = hasGroups ? options.groups : DEFAULT_GROUPS; - const allowSamePrecedence = options.allowSamePrecedence !== false; - - return { - groups, - allowSamePrecedence - }; + const hasGroups = options.groups && options.groups.length > 0; + const groups = hasGroups ? options.groups : DEFAULT_GROUPS; + const allowSamePrecedence = options.allowSamePrecedence !== false; + + return { + groups, + allowSamePrecedence, + }; } /** @@ -65,7 +65,7 @@ function normalizeOptions(options = {}) { * @returns {boolean} `true` if such group existed. */ function includesBothInAGroup(groups, left, right) { - return groups.some(group => group.includes(left) && group.includes(right)); + return groups.some(group => group.includes(left) && group.includes(right)); } /** @@ -76,7 +76,7 @@ function includesBothInAGroup(groups, left, right) { * @returns {ASTNode} node the appropriate node(left or test). */ function getChildNode(node) { - return node.type === "ConditionalExpression" ? node.test : node.left; + return node.type === "ConditionalExpression" ? node.test : node.left; } //------------------------------------------------------------------------------ @@ -85,163 +85,169 @@ function getChildNode(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-mixed-operators", - url: "https://eslint.style/rules/js/no-mixed-operators" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Disallow mixed binary operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-mixed-operators" - }, - - schema: [ - { - type: "object", - properties: { - groups: { - type: "array", - items: { - type: "array", - items: { enum: ALL_OPERATORS }, - minItems: 2, - uniqueItems: true - }, - uniqueItems: true - }, - allowSamePrecedence: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedMixedOperator: "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const options = normalizeOptions(context.options[0]); - - /** - * Checks whether a given node should be ignored by options or not. - * @param {ASTNode} node A node to check. This is a BinaryExpression - * node or a LogicalExpression node. This parent node is one of - * them, too. - * @returns {boolean} `true` if the node should be ignored. - */ - function shouldIgnore(node) { - const a = node; - const b = node.parent; - - return ( - !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) || - ( - options.allowSamePrecedence && - astUtils.getPrecedence(a) === astUtils.getPrecedence(b) - ) - ); - } - - /** - * Checks whether the operator of a given node is mixed with parent - * node's operator or not. - * @param {ASTNode} node A node to check. This is a BinaryExpression - * node or a LogicalExpression node. This parent node is one of - * them, too. - * @returns {boolean} `true` if the node was mixed. - */ - function isMixedWithParent(node) { - - return ( - node.operator !== node.parent.operator && - !astUtils.isParenthesised(sourceCode, node) - ); - } - - /** - * Gets the operator token of a given node. - * @param {ASTNode} node A node to check. This is a BinaryExpression - * node or a LogicalExpression node. - * @returns {Token} The operator token of the node. - */ - function getOperatorToken(node) { - return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken); - } - - /** - * Reports both the operator of a given node and the operator of the - * parent node. - * @param {ASTNode} node A node to check. This is a BinaryExpression - * node or a LogicalExpression node. This parent node is one of - * them, too. - * @returns {void} - */ - function reportBothOperators(node) { - const parent = node.parent; - const left = (getChildNode(parent) === node) ? node : parent; - const right = (getChildNode(parent) !== node) ? node : parent; - const data = { - leftOperator: left.operator || "?:", - rightOperator: right.operator || "?:" - }; - - context.report({ - node: left, - loc: getOperatorToken(left).loc, - messageId: "unexpectedMixedOperator", - data - }); - context.report({ - node: right, - loc: getOperatorToken(right).loc, - messageId: "unexpectedMixedOperator", - data - }); - } - - /** - * Checks between the operator of this node and the operator of the - * parent node. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function check(node) { - if ( - TARGET_NODE_TYPE.test(node.parent.type) && - isMixedWithParent(node) && - !shouldIgnore(node) - ) { - reportBothOperators(node); - } - } - - return { - BinaryExpression: check, - LogicalExpression: check - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-mixed-operators", + url: "https://eslint.style/rules/js/no-mixed-operators", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: "Disallow mixed binary operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-mixed-operators", + }, + + schema: [ + { + type: "object", + properties: { + groups: { + type: "array", + items: { + type: "array", + items: { enum: ALL_OPERATORS }, + minItems: 2, + uniqueItems: true, + }, + uniqueItems: true, + }, + allowSamePrecedence: { + type: "boolean", + default: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedMixedOperator: + "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'. Use parentheses to clarify the intended order of operations.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const options = normalizeOptions(context.options[0]); + + /** + * Checks whether a given node should be ignored by options or not. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {boolean} `true` if the node should be ignored. + */ + function shouldIgnore(node) { + const a = node; + const b = node.parent; + + return ( + !includesBothInAGroup( + options.groups, + a.operator, + b.type === "ConditionalExpression" ? "?:" : b.operator, + ) || + (options.allowSamePrecedence && + astUtils.getPrecedence(a) === astUtils.getPrecedence(b)) + ); + } + + /** + * Checks whether the operator of a given node is mixed with parent + * node's operator or not. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {boolean} `true` if the node was mixed. + */ + function isMixedWithParent(node) { + return ( + node.operator !== node.parent.operator && + !astUtils.isParenthesised(sourceCode, node) + ); + } + + /** + * Gets the operator token of a given node. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. + * @returns {Token} The operator token of the node. + */ + function getOperatorToken(node) { + return sourceCode.getTokenAfter( + getChildNode(node), + astUtils.isNotClosingParenToken, + ); + } + + /** + * Reports both the operator of a given node and the operator of the + * parent node. + * @param {ASTNode} node A node to check. This is a BinaryExpression + * node or a LogicalExpression node. This parent node is one of + * them, too. + * @returns {void} + */ + function reportBothOperators(node) { + const parent = node.parent; + const left = getChildNode(parent) === node ? node : parent; + const right = getChildNode(parent) !== node ? node : parent; + const data = { + leftOperator: left.operator || "?:", + rightOperator: right.operator || "?:", + }; + + context.report({ + node: left, + loc: getOperatorToken(left).loc, + messageId: "unexpectedMixedOperator", + data, + }); + context.report({ + node: right, + loc: getOperatorToken(right).loc, + messageId: "unexpectedMixedOperator", + data, + }); + } + + /** + * Checks between the operator of this node and the operator of the + * parent node. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function check(node) { + if ( + TARGET_NODE_TYPE.test(node.parent.type) && + isMixedWithParent(node) && + !shouldIgnore(node) + ) { + reportBothOperators(node); + } + } + + return { + BinaryExpression: check, + LogicalExpression: check, + }; + }, }; diff --git a/lib/rules/no-mixed-requires.js b/lib/rules/no-mixed-requires.js index 9665c1dd18a6..bf44ffa0899c 100644 --- a/lib/rules/no-mixed-requires.js +++ b/lib/rules/no-mixed-requires.js @@ -12,243 +12,256 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-mixed-requires" - }, - - schema: [ - { - oneOf: [ - { - type: "boolean" - }, - { - type: "object", - properties: { - grouping: { - type: "boolean" - }, - allowCall: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - noMixRequire: "Do not mix 'require' and other declarations.", - noMixCoreModuleFileComputed: "Do not mix core, module, file and computed requires." - } - }, - - create(context) { - - const options = context.options[0]; - let grouping = false, - allowCall = false; - - if (typeof options === "object") { - grouping = options.grouping; - allowCall = options.allowCall; - } else { - grouping = !!options; - } - - /** - * Returns the list of built-in modules. - * @returns {string[]} An array of built-in Node.js modules. - */ - function getBuiltinModules() { - - /* - * This list is generated using: - * `require("repl")._builtinLibs.concat('repl').sort()` - * This particular list is as per nodejs v0.12.2 and iojs v0.7.1 - */ - return [ - "assert", "buffer", "child_process", "cluster", "crypto", - "dgram", "dns", "domain", "events", "fs", "http", "https", - "net", "os", "path", "punycode", "querystring", "readline", - "repl", "smalloc", "stream", "string_decoder", "tls", "tty", - "url", "util", "v8", "vm", "zlib" - ]; - } - - const BUILTIN_MODULES = getBuiltinModules(); - - const DECL_REQUIRE = "require", - DECL_UNINITIALIZED = "uninitialized", - DECL_OTHER = "other"; - - const REQ_CORE = "core", - REQ_FILE = "file", - REQ_MODULE = "module", - REQ_COMPUTED = "computed"; - - /** - * Determines the type of a declaration statement. - * @param {ASTNode} initExpression The init node of the VariableDeclarator. - * @returns {string} The type of declaration represented by the expression. - */ - function getDeclarationType(initExpression) { - if (!initExpression) { - - // "var x;" - return DECL_UNINITIALIZED; - } - - if (initExpression.type === "CallExpression" && - initExpression.callee.type === "Identifier" && - initExpression.callee.name === "require" - ) { - - // "var x = require('util');" - return DECL_REQUIRE; - } - if (allowCall && - initExpression.type === "CallExpression" && - initExpression.callee.type === "CallExpression" - ) { - - // "var x = require('diagnose')('sub-module');" - return getDeclarationType(initExpression.callee); - } - if (initExpression.type === "MemberExpression") { - - // "var x = require('glob').Glob;" - return getDeclarationType(initExpression.object); - } - - // "var x = 42;" - return DECL_OTHER; - } - - /** - * Determines the type of module that is loaded via require. - * @param {ASTNode} initExpression The init node of the VariableDeclarator. - * @returns {string} The module type. - */ - function inferModuleType(initExpression) { - if (initExpression.type === "MemberExpression") { - - // "var x = require('glob').Glob;" - return inferModuleType(initExpression.object); - } - if (initExpression.arguments.length === 0) { - - // "var x = require();" - return REQ_COMPUTED; - } - - const arg = initExpression.arguments[0]; - - if (arg.type !== "Literal" || typeof arg.value !== "string") { - - // "var x = require(42);" - return REQ_COMPUTED; - } - - if (BUILTIN_MODULES.includes(arg.value)) { - - // "var fs = require('fs');" - return REQ_CORE; - } - if (/^\.{0,2}\//u.test(arg.value)) { - - // "var utils = require('./utils');" - return REQ_FILE; - } - - // "var async = require('async');" - return REQ_MODULE; - - } - - /** - * Check if the list of variable declarations is mixed, i.e. whether it - * contains both require and other declarations. - * @param {ASTNode} declarations The list of VariableDeclarators. - * @returns {boolean} True if the declarations are mixed, false if not. - */ - function isMixed(declarations) { - const contains = {}; - - declarations.forEach(declaration => { - const type = getDeclarationType(declaration.init); - - contains[type] = true; - }); - - return !!( - contains[DECL_REQUIRE] && - (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) - ); - } - - /** - * Check if all require declarations in the given list are of the same - * type. - * @param {ASTNode} declarations The list of VariableDeclarators. - * @returns {boolean} True if the declarations are grouped, false if not. - */ - function isGrouped(declarations) { - const found = {}; - - declarations.forEach(declaration => { - if (getDeclarationType(declaration.init) === DECL_REQUIRE) { - found[inferModuleType(declaration.init)] = true; - } - }); - - return Object.keys(found).length <= 1; - } - - - return { - - VariableDeclaration(node) { - - if (isMixed(node.declarations)) { - context.report({ - node, - messageId: "noMixRequire" - }); - } else if (grouping && !isGrouped(node.declarations)) { - context.report({ - node, - messageId: "noMixCoreModuleFileComputed" - }); - } - } - }; - - } + meta: { + 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", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-mixed-requires", + }, + + schema: [ + { + oneOf: [ + { + type: "boolean", + }, + { + type: "object", + properties: { + grouping: { + type: "boolean", + }, + allowCall: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + noMixRequire: "Do not mix 'require' and other declarations.", + noMixCoreModuleFileComputed: + "Do not mix core, module, file and computed requires.", + }, + }, + + create(context) { + const options = context.options[0]; + let grouping = false, + allowCall = false; + + if (typeof options === "object") { + grouping = options.grouping; + allowCall = options.allowCall; + } else { + grouping = !!options; + } + + /** + * Returns the list of built-in modules. + * @returns {string[]} An array of built-in Node.js modules. + */ + function getBuiltinModules() { + /* + * This list is generated using: + * `require("repl")._builtinLibs.concat('repl').sort()` + * This particular list is as per nodejs v0.12.2 and iojs v0.7.1 + */ + return [ + "assert", + "buffer", + "child_process", + "cluster", + "crypto", + "dgram", + "dns", + "domain", + "events", + "fs", + "http", + "https", + "net", + "os", + "path", + "punycode", + "querystring", + "readline", + "repl", + "smalloc", + "stream", + "string_decoder", + "tls", + "tty", + "url", + "util", + "v8", + "vm", + "zlib", + ]; + } + + const BUILTIN_MODULES = getBuiltinModules(); + + const DECL_REQUIRE = "require", + DECL_UNINITIALIZED = "uninitialized", + DECL_OTHER = "other"; + + const REQ_CORE = "core", + REQ_FILE = "file", + REQ_MODULE = "module", + REQ_COMPUTED = "computed"; + + /** + * Determines the type of a declaration statement. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The type of declaration represented by the expression. + */ + function getDeclarationType(initExpression) { + if (!initExpression) { + // "var x;" + return DECL_UNINITIALIZED; + } + + if ( + initExpression.type === "CallExpression" && + initExpression.callee.type === "Identifier" && + initExpression.callee.name === "require" + ) { + // "var x = require('util');" + return DECL_REQUIRE; + } + if ( + allowCall && + initExpression.type === "CallExpression" && + initExpression.callee.type === "CallExpression" + ) { + // "var x = require('diagnose')('sub-module');" + return getDeclarationType(initExpression.callee); + } + if (initExpression.type === "MemberExpression") { + // "var x = require('glob').Glob;" + return getDeclarationType(initExpression.object); + } + + // "var x = 42;" + return DECL_OTHER; + } + + /** + * Determines the type of module that is loaded via require. + * @param {ASTNode} initExpression The init node of the VariableDeclarator. + * @returns {string} The module type. + */ + function inferModuleType(initExpression) { + if (initExpression.type === "MemberExpression") { + // "var x = require('glob').Glob;" + return inferModuleType(initExpression.object); + } + if (initExpression.arguments.length === 0) { + // "var x = require();" + return REQ_COMPUTED; + } + + const arg = initExpression.arguments[0]; + + if (arg.type !== "Literal" || typeof arg.value !== "string") { + // "var x = require(42);" + return REQ_COMPUTED; + } + + if (BUILTIN_MODULES.includes(arg.value)) { + // "var fs = require('fs');" + return REQ_CORE; + } + if (/^\.{0,2}\//u.test(arg.value)) { + // "var utils = require('./utils');" + return REQ_FILE; + } + + // "var async = require('async');" + return REQ_MODULE; + } + + /** + * Check if the list of variable declarations is mixed, i.e. whether it + * contains both require and other declarations. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are mixed, false if not. + */ + function isMixed(declarations) { + const contains = {}; + + declarations.forEach(declaration => { + const type = getDeclarationType(declaration.init); + + contains[type] = true; + }); + + return !!( + contains[DECL_REQUIRE] && + (contains[DECL_UNINITIALIZED] || contains[DECL_OTHER]) + ); + } + + /** + * Check if all require declarations in the given list are of the same + * type. + * @param {ASTNode} declarations The list of VariableDeclarators. + * @returns {boolean} True if the declarations are grouped, false if not. + */ + function isGrouped(declarations) { + const found = {}; + + declarations.forEach(declaration => { + if (getDeclarationType(declaration.init) === DECL_REQUIRE) { + found[inferModuleType(declaration.init)] = true; + } + }); + + return Object.keys(found).length <= 1; + } + + return { + VariableDeclaration(node) { + if (isMixed(node.declarations)) { + context.report({ + node, + messageId: "noMixRequire", + }); + } else if (grouping && !isGrouped(node.declarations)) { + context.report({ + node, + messageId: "noMixCoreModuleFileComputed", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-mixed-spaces-and-tabs.js b/lib/rules/no-mixed-spaces-and-tabs.js index d379b045542f..3bc233f614fa 100644 --- a/lib/rules/no-mixed-spaces-and-tabs.js +++ b/lib/rules/no-mixed-spaces-and-tabs.js @@ -11,124 +11,137 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-mixed-spaces-and-tabs", - url: "https://eslint.style/rules/js/no-mixed-spaces-and-tabs" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow mixed spaces and tabs for indentation", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" - }, - - schema: [ - { - enum: ["smart-tabs", true, false] - } - ], - - messages: { - mixedSpacesAndTabs: "Mixed spaces and tabs." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - let smartTabs; - - switch (context.options[0]) { - case true: // Support old syntax, maybe add deprecation warning here - case "smart-tabs": - smartTabs = true; - break; - default: - smartTabs = false; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - "Program:exit"(node) { - const lines = sourceCode.lines, - comments = sourceCode.getAllComments(), - ignoredCommentLines = new Set(); - - // Add all lines except the first ones. - comments.forEach(comment => { - for (let i = comment.loc.start.line + 1; i <= comment.loc.end.line; i++) { - ignoredCommentLines.add(i); - } - }); - - /* - * At least one space followed by a tab - * or the reverse before non-tab/-space - * characters begin. - */ - let regex = /^(?=( +|\t+))\1(?:\t| )/u; - - if (smartTabs) { - - /* - * At least one space followed by a tab - * before non-tab/-space characters begin. - */ - regex = /^(?=(\t*))\1(?=( +))\2\t/u; - } - - lines.forEach((line, i) => { - const match = regex.exec(line); - - if (match) { - const lineNumber = i + 1; - const loc = { - start: { - line: lineNumber, - column: match[0].length - 2 - }, - end: { - line: lineNumber, - column: match[0].length - } - }; - - if (!ignoredCommentLines.has(lineNumber)) { - const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start)); - - if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) { - context.report({ - node, - loc, - messageId: "mixedSpacesAndTabs" - }); - } - } - } - }); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-mixed-spaces-and-tabs", + url: "https://eslint.style/rules/js/no-mixed-spaces-and-tabs", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow mixed spaces and tabs for indentation", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs", + }, + + schema: [ + { + enum: ["smart-tabs", true, false], + }, + ], + + messages: { + mixedSpacesAndTabs: "Mixed spaces and tabs.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + let smartTabs; + + switch (context.options[0]) { + case true: // Support old syntax, maybe add deprecation warning here + case "smart-tabs": + smartTabs = true; + break; + default: + smartTabs = false; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program:exit"(node) { + const lines = sourceCode.lines, + comments = sourceCode.getAllComments(), + ignoredCommentLines = new Set(); + + // Add all lines except the first ones. + comments.forEach(comment => { + for ( + let i = comment.loc.start.line + 1; + i <= comment.loc.end.line; + i++ + ) { + ignoredCommentLines.add(i); + } + }); + + /* + * At least one space followed by a tab + * or the reverse before non-tab/-space + * characters begin. + */ + let regex = /^(?=( +|\t+))\1(?:\t| )/u; + + if (smartTabs) { + /* + * At least one space followed by a tab + * before non-tab/-space characters begin. + */ + regex = /^(?=(\t*))\1(?=( +))\2\t/u; + } + + lines.forEach((line, i) => { + const match = regex.exec(line); + + if (match) { + const lineNumber = i + 1; + const loc = { + start: { + line: lineNumber, + column: match[0].length - 2, + }, + end: { + line: lineNumber, + column: match[0].length, + }, + }; + + if (!ignoredCommentLines.has(lineNumber)) { + const containingNode = + sourceCode.getNodeByRangeIndex( + sourceCode.getIndexFromLoc(loc.start), + ); + + if ( + !( + containingNode && + ["Literal", "TemplateElement"].includes( + containingNode.type, + ) + ) + ) { + context.report({ + node, + loc, + messageId: "mixedSpacesAndTabs", + }); + } + } + } + }); + }, + }; + }, }; diff --git a/lib/rules/no-multi-assign.js b/lib/rules/no-multi-assign.js index 6e45592689fb..b0151e1fab6f 100644 --- a/lib/rules/no-multi-assign.js +++ b/lib/rules/no-multi-assign.js @@ -5,60 +5,62 @@ "use strict"; - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - ignoreNonDeclaration: false - }], + meta: { + type: "suggestion", - docs: { - description: "Disallow use of chained assignment expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-multi-assign" - }, + defaultOptions: [ + { + ignoreNonDeclaration: false, + }, + ], - schema: [{ - type: "object", - properties: { - ignoreNonDeclaration: { - type: "boolean" - } - }, - additionalProperties: false - }], + docs: { + description: "Disallow use of chained assignment expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-multi-assign", + }, - messages: { - unexpectedChain: "Unexpected chained assignment." - } - }, + schema: [ + { + type: "object", + properties: { + ignoreNonDeclaration: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], - create(context) { - const [{ ignoreNonDeclaration }] = context.options; - const selectors = [ - "VariableDeclarator > AssignmentExpression.init", - "PropertyDefinition > AssignmentExpression.value" - ]; + messages: { + unexpectedChain: "Unexpected chained assignment.", + }, + }, - if (!ignoreNonDeclaration) { - selectors.push("AssignmentExpression > AssignmentExpression.right"); - } + create(context) { + const [{ ignoreNonDeclaration }] = context.options; + const selectors = [ + "VariableDeclarator > AssignmentExpression.init", + "PropertyDefinition > AssignmentExpression.value", + ]; - return { - [selectors](node) { - context.report({ - node, - messageId: "unexpectedChain" - }); - } - }; + if (!ignoreNonDeclaration) { + selectors.push("AssignmentExpression > AssignmentExpression.right"); + } - } + return { + [selectors](node) { + context.report({ + node, + messageId: "unexpectedChain", + }); + }, + }; + }, }; diff --git a/lib/rules/no-multi-spaces.js b/lib/rules/no-multi-spaces.js index 997b171c849e..c132f9b6cc0c 100644 --- a/lib/rules/no-multi-spaces.js +++ b/lib/rules/no-multi-spaces.js @@ -14,146 +14,166 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-multi-spaces", - url: "https://eslint.style/rules/js/no-multi-spaces" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow multiple spaces", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-multi-spaces" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - exceptions: { - type: "object", - patternProperties: { - "^([A-Z][a-z]*)+$": { - type: "boolean" - } - }, - additionalProperties: false - }, - ignoreEOLComments: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - messages: { - multipleSpaces: "Multiple spaces found before '{{displayValue}}'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const options = context.options[0] || {}; - const ignoreEOLComments = options.ignoreEOLComments; - const exceptions = Object.assign({ Property: true }, options.exceptions); - const hasExceptions = Object.keys(exceptions).some(key => exceptions[key]); - - /** - * Formats value of given comment token for error message by truncating its length. - * @param {Token} token comment token - * @returns {string} formatted value - * @private - */ - function formatReportedCommentValue(token) { - const valueLines = token.value.split("\n"); - const value = valueLines[0]; - const formattedValue = `${value.slice(0, 12)}...`; - - return valueLines.length === 1 && value.length <= 12 ? value : formattedValue; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program() { - sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => { - if (leftIndex === tokensAndComments.length - 1) { - return; - } - const rightToken = tokensAndComments[leftIndex + 1]; - - // Ignore tokens that don't have 2 spaces between them or are on different lines - if ( - !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") || - leftToken.loc.end.line < rightToken.loc.start.line - ) { - return; - } - - // Ignore comments that are the last token on their line if `ignoreEOLComments` is active. - if ( - ignoreEOLComments && - astUtils.isCommentToken(rightToken) && - ( - leftIndex === tokensAndComments.length - 2 || - rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line - ) - ) { - return; - } - - // Ignore tokens that are in a node in the "exceptions" object - if (hasExceptions) { - const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1); - - if (parentNode && exceptions[parentNode.type]) { - return; - } - } - - let displayValue; - - if (rightToken.type === "Block") { - displayValue = `/*${formatReportedCommentValue(rightToken)}*/`; - } else if (rightToken.type === "Line") { - displayValue = `//${formatReportedCommentValue(rightToken)}`; - } else { - displayValue = rightToken.value; - } - - context.report({ - node: rightToken, - loc: { start: leftToken.loc.end, end: rightToken.loc.start }, - messageId: "multipleSpaces", - data: { displayValue }, - fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ") - }); - }); - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-multi-spaces", + url: "https://eslint.style/rules/js/no-multi-spaces", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow multiple spaces", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-multi-spaces", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "object", + patternProperties: { + "^([A-Z][a-z]*)+$": { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ignoreEOLComments: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + multipleSpaces: "Multiple spaces found before '{{displayValue}}'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const options = context.options[0] || {}; + const ignoreEOLComments = options.ignoreEOLComments; + const exceptions = Object.assign( + { Property: true }, + options.exceptions, + ); + const hasExceptions = Object.keys(exceptions).some( + key => exceptions[key], + ); + + /** + * Formats value of given comment token for error message by truncating its length. + * @param {Token} token comment token + * @returns {string} formatted value + * @private + */ + function formatReportedCommentValue(token) { + const valueLines = token.value.split("\n"); + const value = valueLines[0]; + const formattedValue = `${value.slice(0, 12)}...`; + + return valueLines.length === 1 && value.length <= 12 + ? value + : formattedValue; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program() { + sourceCode.tokensAndComments.forEach( + (leftToken, leftIndex, tokensAndComments) => { + if (leftIndex === tokensAndComments.length - 1) { + return; + } + const rightToken = tokensAndComments[leftIndex + 1]; + + // Ignore tokens that don't have 2 spaces between them or are on different lines + if ( + !sourceCode.text + .slice(leftToken.range[1], rightToken.range[0]) + .includes(" ") || + leftToken.loc.end.line < rightToken.loc.start.line + ) { + return; + } + + // Ignore comments that are the last token on their line if `ignoreEOLComments` is active. + if ( + ignoreEOLComments && + astUtils.isCommentToken(rightToken) && + (leftIndex === tokensAndComments.length - 2 || + rightToken.loc.end.line < + tokensAndComments[leftIndex + 2].loc.start + .line) + ) { + return; + } + + // Ignore tokens that are in a node in the "exceptions" object + if (hasExceptions) { + const parentNode = sourceCode.getNodeByRangeIndex( + rightToken.range[0] - 1, + ); + + if (parentNode && exceptions[parentNode.type]) { + return; + } + } + + let displayValue; + + if (rightToken.type === "Block") { + displayValue = `/*${formatReportedCommentValue(rightToken)}*/`; + } else if (rightToken.type === "Line") { + displayValue = `//${formatReportedCommentValue(rightToken)}`; + } else { + displayValue = rightToken.value; + } + + context.report({ + node: rightToken, + loc: { + start: leftToken.loc.end, + end: rightToken.loc.start, + }, + messageId: "multipleSpaces", + data: { displayValue }, + fix: fixer => + fixer.replaceTextRange( + [leftToken.range[1], rightToken.range[0]], + " ", + ), + }); + }, + ); + }, + }; + }, }; diff --git a/lib/rules/no-multi-str.js b/lib/rules/no-multi-str.js index f58e2d4a44e5..697d0f61f8a3 100644 --- a/lib/rules/no-multi-str.js +++ b/lib/rules/no-multi-str.js @@ -17,50 +17,51 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow multiline strings", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-multi-str" - }, + docs: { + description: "Disallow multiline strings", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-multi-str", + }, - schema: [], + schema: [], - messages: { - multilineString: "Multiline support is limited to browsers supporting ES5 only." - } - }, + messages: { + multilineString: + "Multiline support is limited to browsers supporting ES5 only.", + }, + }, - create(context) { + create(context) { + /** + * Determines if a given node is part of JSX syntax. + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node is a JSX node, false if not. + * @private + */ + function isJSXElement(node) { + return node.type.indexOf("JSX") === 0; + } - /** - * Determines if a given node is part of JSX syntax. - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node is a JSX node, false if not. - * @private - */ - function isJSXElement(node) { - return node.type.indexOf("JSX") === 0; - } + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - Literal(node) { - if (astUtils.LINEBREAK_MATCHER.test(node.raw) && !isJSXElement(node.parent)) { - context.report({ - node, - messageId: "multilineString" - }); - } - } - }; - - } + return { + Literal(node) { + if ( + astUtils.LINEBREAK_MATCHER.test(node.raw) && + !isJSXElement(node.parent) + ) { + context.report({ + node, + messageId: "multilineString", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-multiple-empty-lines.js b/lib/rules/no-multiple-empty-lines.js index 4837851f5016..839d0e6867c4 100644 --- a/lib/rules/no-multiple-empty-lines.js +++ b/lib/rules/no-multiple-empty-lines.js @@ -12,161 +12,199 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-multiple-empty-lines", - url: "https://eslint.style/rules/js/no-multiple-empty-lines" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow multiple empty lines", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - max: { - type: "integer", - minimum: 0 - }, - maxEOF: { - type: "integer", - minimum: 0 - }, - maxBOF: { - type: "integer", - minimum: 0 - } - }, - required: ["max"], - additionalProperties: false - } - ], - - messages: { - blankBeginningOfFile: "Too many blank lines at the beginning of file. Max of {{max}} allowed.", - blankEndOfFile: "Too many blank lines at the end of file. Max of {{max}} allowed.", - consecutiveBlank: "More than {{max}} blank {{pluralizedLines}} not allowed." - } - }, - - create(context) { - - // Use options.max or 2 as default - let max = 2, - maxEOF = max, - maxBOF = max; - - if (context.options.length) { - max = context.options[0].max; - maxEOF = typeof context.options[0].maxEOF !== "undefined" ? context.options[0].maxEOF : max; - maxBOF = typeof context.options[0].maxBOF !== "undefined" ? context.options[0].maxBOF : max; - } - - const sourceCode = context.sourceCode; - - // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue - const allLines = sourceCode.lines.at(-1) === "" ? sourceCode.lines.slice(0, -1) : sourceCode.lines; - const templateLiteralLines = new Set(); - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - TemplateLiteral(node) { - node.quasis.forEach(literalPart => { - - // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines. - for (let ignoredLine = literalPart.loc.start.line; ignoredLine < literalPart.loc.end.line; ignoredLine++) { - templateLiteralLines.add(ignoredLine); - } - }); - }, - "Program:exit"(node) { - return allLines - - // Given a list of lines, first get a list of line numbers that are non-empty. - .reduce((nonEmptyLineNumbers, line, index) => { - if (line.trim() || templateLiteralLines.has(index + 1)) { - nonEmptyLineNumbers.push(index + 1); - } - return nonEmptyLineNumbers; - }, []) - - // Add a value at the end to allow trailing empty lines to be checked. - .concat(allLines.length + 1) - - // Given two line numbers of non-empty lines, report the lines between if the difference is too large. - .reduce((lastLineNumber, lineNumber) => { - let messageId, maxAllowed; - - if (lastLineNumber === 0) { - messageId = "blankBeginningOfFile"; - maxAllowed = maxBOF; - } else if (lineNumber === allLines.length + 1) { - messageId = "blankEndOfFile"; - maxAllowed = maxEOF; - } else { - messageId = "consecutiveBlank"; - maxAllowed = max; - } - - if (lineNumber - lastLineNumber - 1 > maxAllowed) { - context.report({ - node, - loc: { - start: { line: lastLineNumber + maxAllowed + 1, column: 0 }, - end: { line: lineNumber, column: 0 } - }, - messageId, - data: { - max: maxAllowed, - pluralizedLines: maxAllowed === 1 ? "line" : "lines" - }, - fix(fixer) { - const rangeStart = sourceCode.getIndexFromLoc({ line: lastLineNumber + 1, column: 0 }); - - /* - * The end of the removal range is usually the start index of the next line. - * However, at the end of the file there is no next line, so the end of the - * range is just the length of the text. - */ - const lineNumberAfterRemovedLines = lineNumber - maxAllowed; - const rangeEnd = lineNumberAfterRemovedLines <= allLines.length - ? sourceCode.getIndexFromLoc({ line: lineNumberAfterRemovedLines, column: 0 }) - : sourceCode.text.length; - - return fixer.removeRange([rangeStart, rangeEnd]); - } - }); - } - - return lineNumber; - }, 0); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-multiple-empty-lines", + url: "https://eslint.style/rules/js/no-multiple-empty-lines", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow multiple empty lines", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-multiple-empty-lines", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + max: { + type: "integer", + minimum: 0, + }, + maxEOF: { + type: "integer", + minimum: 0, + }, + maxBOF: { + type: "integer", + minimum: 0, + }, + }, + required: ["max"], + additionalProperties: false, + }, + ], + + messages: { + blankBeginningOfFile: + "Too many blank lines at the beginning of file. Max of {{max}} allowed.", + blankEndOfFile: + "Too many blank lines at the end of file. Max of {{max}} allowed.", + consecutiveBlank: + "More than {{max}} blank {{pluralizedLines}} not allowed.", + }, + }, + + create(context) { + // Use options.max or 2 as default + let max = 2, + maxEOF = max, + maxBOF = max; + + if (context.options.length) { + max = context.options[0].max; + maxEOF = + typeof context.options[0].maxEOF !== "undefined" + ? context.options[0].maxEOF + : max; + maxBOF = + typeof context.options[0].maxBOF !== "undefined" + ? context.options[0].maxBOF + : max; + } + + const sourceCode = context.sourceCode; + + // Swallow the final newline, as some editors add it automatically and we don't want it to cause an issue + const allLines = + sourceCode.lines.at(-1) === "" + ? sourceCode.lines.slice(0, -1) + : sourceCode.lines; + const templateLiteralLines = new Set(); + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + TemplateLiteral(node) { + node.quasis.forEach(literalPart => { + // Empty lines have a semantic meaning if they're inside template literals. Don't count these as empty lines. + for ( + let ignoredLine = literalPart.loc.start.line; + ignoredLine < literalPart.loc.end.line; + ignoredLine++ + ) { + templateLiteralLines.add(ignoredLine); + } + }); + }, + "Program:exit"(node) { + return ( + allLines + + // Given a list of lines, first get a list of line numbers that are non-empty. + .reduce((nonEmptyLineNumbers, line, index) => { + if ( + line.trim() || + templateLiteralLines.has(index + 1) + ) { + nonEmptyLineNumbers.push(index + 1); + } + return nonEmptyLineNumbers; + }, []) + + // Add a value at the end to allow trailing empty lines to be checked. + .concat(allLines.length + 1) + + // Given two line numbers of non-empty lines, report the lines between if the difference is too large. + .reduce((lastLineNumber, lineNumber) => { + let messageId, maxAllowed; + + if (lastLineNumber === 0) { + messageId = "blankBeginningOfFile"; + maxAllowed = maxBOF; + } else if (lineNumber === allLines.length + 1) { + messageId = "blankEndOfFile"; + maxAllowed = maxEOF; + } else { + messageId = "consecutiveBlank"; + maxAllowed = max; + } + + if (lineNumber - lastLineNumber - 1 > maxAllowed) { + context.report({ + node, + loc: { + start: { + line: + lastLineNumber + maxAllowed + 1, + column: 0, + }, + end: { line: lineNumber, column: 0 }, + }, + messageId, + data: { + max: maxAllowed, + pluralizedLines: + maxAllowed === 1 ? "line" : "lines", + }, + fix(fixer) { + const rangeStart = + sourceCode.getIndexFromLoc({ + line: lastLineNumber + 1, + column: 0, + }); + + /* + * The end of the removal range is usually the start index of the next line. + * However, at the end of the file there is no next line, so the end of the + * range is just the length of the text. + */ + const lineNumberAfterRemovedLines = + lineNumber - maxAllowed; + const rangeEnd = + lineNumberAfterRemovedLines <= + allLines.length + ? sourceCode.getIndexFromLoc({ + line: lineNumberAfterRemovedLines, + column: 0, + }) + : sourceCode.text.length; + + return fixer.removeRange([ + rangeStart, + rangeEnd, + ]); + }, + }); + } + + return lineNumber; + }, 0) + ); + }, + }; + }, }; diff --git a/lib/rules/no-native-reassign.js b/lib/rules/no-native-reassign.js index baafdeb57173..2f617d9275ca 100644 --- a/lib/rules/no-native-reassign.js +++ b/lib/rules/no-native-reassign.js @@ -12,98 +12,103 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow assignments to native objects or read-only global variables", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-native-reassign" - }, + docs: { + description: + "Disallow assignments to native objects or read-only global variables", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-native-reassign", + }, - 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" - } - } - ] - }, + 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", + }, + }, + ], + }, - schema: [ - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { type: "string" }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { type: "string" }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], - messages: { - nativeReassign: "Read-only global '{{name}}' should not be modified." - } - }, + messages: { + nativeReassign: + "Read-only global '{{name}}' should not be modified.", + }, + }, - create(context) { - const config = context.options[0]; - const exceptions = (config && config.exceptions) || []; - const sourceCode = context.sourceCode; + create(context) { + const config = context.options[0]; + const exceptions = (config && config.exceptions) || []; + const sourceCode = context.sourceCode; - /** - * Reports write references. - * @param {Reference} reference A reference to check. - * @param {int} index The index of the reference in the references. - * @param {Reference[]} references The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - const identifier = reference.identifier; + /** + * Reports write references. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; - if (reference.init === false && - reference.isWrite() && + if ( + reference.init === false && + reference.isWrite() && + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + context.report({ + node: identifier, + messageId: "nativeReassign", + data: identifier, + }); + } + } - /* - * Destructuring assignments can have multiple default value, - * so possibly there are multiple writeable references for the same identifier. - */ - (index === 0 || references[index - 1].identifier !== identifier) - ) { - context.report({ - node: identifier, - messageId: "nativeReassign", - data: identifier - }); - } - } + /** + * Reports write references if a given variable is read-only builtin. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if ( + variable.writeable === false && + !exceptions.includes(variable.name) + ) { + variable.references.forEach(checkReference); + } + } - /** - * Reports write references if a given variable is read-only builtin. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.writeable === false && !exceptions.includes(variable.name)) { - variable.references.forEach(checkReference); - } - } + return { + Program(node) { + const globalScope = sourceCode.getScope(node); - return { - Program(node) { - const globalScope = sourceCode.getScope(node); - - globalScope.variables.forEach(checkVariable); - } - }; - } + globalScope.variables.forEach(checkVariable); + }, + }; + }, }; diff --git a/lib/rules/no-negated-condition.js b/lib/rules/no-negated-condition.js index 641123dba4a0..9c6ae9ae2fee 100644 --- a/lib/rules/no-negated-condition.js +++ b/lib/rules/no-negated-condition.js @@ -10,87 +10,91 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow negated conditions", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-negated-condition" - }, + docs: { + description: "Disallow negated conditions", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-negated-condition", + }, - schema: [], + schema: [], - messages: { - unexpectedNegated: "Unexpected negated condition." - } - }, + messages: { + unexpectedNegated: "Unexpected negated condition.", + }, + }, - create(context) { + create(context) { + /** + * Determines if a given node is an if-else without a condition on the else + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has an else without an if. + * @private + */ + function hasElseWithoutCondition(node) { + return node.alternate && node.alternate.type !== "IfStatement"; + } - /** - * Determines if a given node is an if-else without a condition on the else - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has an else without an if. - * @private - */ - function hasElseWithoutCondition(node) { - return node.alternate && node.alternate.type !== "IfStatement"; - } + /** + * Determines if a given node is a negated unary expression + * @param {Object} test The test object to check. + * @returns {boolean} True if the node is a negated unary expression. + * @private + */ + function isNegatedUnaryExpression(test) { + return test.type === "UnaryExpression" && test.operator === "!"; + } - /** - * Determines if a given node is a negated unary expression - * @param {Object} test The test object to check. - * @returns {boolean} True if the node is a negated unary expression. - * @private - */ - function isNegatedUnaryExpression(test) { - return test.type === "UnaryExpression" && test.operator === "!"; - } + /** + * Determines if a given node is a negated binary expression + * @param {Test} test The test to check. + * @returns {boolean} True if the node is a negated binary expression. + * @private + */ + function isNegatedBinaryExpression(test) { + return ( + test.type === "BinaryExpression" && + (test.operator === "!=" || test.operator === "!==") + ); + } - /** - * Determines if a given node is a negated binary expression - * @param {Test} test The test to check. - * @returns {boolean} True if the node is a negated binary expression. - * @private - */ - function isNegatedBinaryExpression(test) { - return test.type === "BinaryExpression" && - (test.operator === "!=" || test.operator === "!=="); - } + /** + * Determines if a given node has a negated if expression + * @param {ASTNode} node The node to check. + * @returns {boolean} True if the node has a negated if expression. + * @private + */ + function isNegatedIf(node) { + return ( + isNegatedUnaryExpression(node.test) || + isNegatedBinaryExpression(node.test) + ); + } - /** - * Determines if a given node has a negated if expression - * @param {ASTNode} node The node to check. - * @returns {boolean} True if the node has a negated if expression. - * @private - */ - function isNegatedIf(node) { - return isNegatedUnaryExpression(node.test) || isNegatedBinaryExpression(node.test); - } + return { + IfStatement(node) { + if (!hasElseWithoutCondition(node)) { + return; + } - return { - IfStatement(node) { - if (!hasElseWithoutCondition(node)) { - return; - } - - if (isNegatedIf(node)) { - context.report({ - node, - messageId: "unexpectedNegated" - }); - } - }, - ConditionalExpression(node) { - if (isNegatedIf(node)) { - context.report({ - node, - messageId: "unexpectedNegated" - }); - } - } - }; - } + if (isNegatedIf(node)) { + context.report({ + node, + messageId: "unexpectedNegated", + }); + } + }, + ConditionalExpression(node) { + if (isNegatedIf(node)) { + context.report({ + node, + messageId: "unexpectedNegated", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-negated-in-lhs.js b/lib/rules/no-negated-in-lhs.js index 3ef071aa100c..14a0b09d6d9b 100644 --- a/lib/rules/no-negated-in-lhs.js +++ b/lib/rules/no-negated-in-lhs.js @@ -12,46 +12,48 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow negating the left operand in `in` expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-negated-in-lhs" - }, - - 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" - } - } - ] - }, - schema: [], - - messages: { - negatedLHS: "The 'in' expression's left operand is negated." - } - }, - - create(context) { - - return { - - BinaryExpression(node) { - if (node.operator === "in" && node.left.type === "UnaryExpression" && node.left.operator === "!") { - context.report({ node, messageId: "negatedLHS" }); - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: + "Disallow negating the left operand in `in` expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-negated-in-lhs", + }, + + 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", + }, + }, + ], + }, + schema: [], + + messages: { + negatedLHS: "The 'in' expression's left operand is negated.", + }, + }, + + create(context) { + return { + BinaryExpression(node) { + if ( + node.operator === "in" && + node.left.type === "UnaryExpression" && + node.left.operator === "!" + ) { + context.report({ node, messageId: "negatedLHS" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-nested-ternary.js b/lib/rules/no-nested-ternary.js index cf26f287c3a5..9dd9c9ca79e7 100644 --- a/lib/rules/no-nested-ternary.js +++ b/lib/rules/no-nested-ternary.js @@ -11,35 +11,36 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow nested ternary expressions", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-nested-ternary" - }, - - schema: [], - - messages: { - noNestedTernary: "Do not nest ternary expressions." - } - }, - - create(context) { - - return { - ConditionalExpression(node) { - if (node.alternate.type === "ConditionalExpression" || - node.consequent.type === "ConditionalExpression") { - context.report({ - node, - messageId: "noNestedTernary" - }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow nested ternary expressions", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-nested-ternary", + }, + + schema: [], + + messages: { + noNestedTernary: "Do not nest ternary expressions.", + }, + }, + + create(context) { + return { + ConditionalExpression(node) { + if ( + node.alternate.type === "ConditionalExpression" || + node.consequent.type === "ConditionalExpression" + ) { + context.report({ + node, + messageId: "noNestedTernary", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-new-func.js b/lib/rules/no-new-func.js index d58b2d715472..7f2e39437a0e 100644 --- a/lib/rules/no-new-func.js +++ b/lib/rules/no-new-func.js @@ -23,65 +23,74 @@ const callMethods = new Set(["apply", "bind", "call"]); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `new` operators with the `Function` object", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new-func" - }, - - schema: [], - - messages: { - noFunctionConstructor: "The Function constructor is eval." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - const variable = globalScope.set.get("Function"); - - if (variable && variable.defs.length === 0) { - variable.references.forEach(ref => { - const idNode = ref.identifier; - const { parent } = idNode; - let evalNode; - - if (parent) { - if (idNode === parent.callee && ( - parent.type === "NewExpression" || - parent.type === "CallExpression" - )) { - evalNode = parent; - } else if ( - parent.type === "MemberExpression" && - idNode === parent.object && - callMethods.has(astUtils.getStaticPropertyName(parent)) - ) { - const maybeCallee = parent.parent.type === "ChainExpression" ? parent.parent : parent; - - if (maybeCallee.parent.type === "CallExpression" && maybeCallee.parent.callee === maybeCallee) { - evalNode = maybeCallee.parent; - } - } - } - - if (evalNode) { - context.report({ - node: evalNode, - messageId: "noFunctionConstructor" - }); - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow `new` operators with the `Function` object", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new-func", + }, + + schema: [], + + messages: { + noFunctionConstructor: "The Function constructor is eval.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + const variable = globalScope.set.get("Function"); + + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const idNode = ref.identifier; + const { parent } = idNode; + let evalNode; + + if (parent) { + if ( + idNode === parent.callee && + (parent.type === "NewExpression" || + parent.type === "CallExpression") + ) { + evalNode = parent; + } else if ( + parent.type === "MemberExpression" && + idNode === parent.object && + callMethods.has( + astUtils.getStaticPropertyName(parent), + ) + ) { + const maybeCallee = + parent.parent.type === "ChainExpression" + ? parent.parent + : parent; + + if ( + maybeCallee.parent.type === + "CallExpression" && + maybeCallee.parent.callee === maybeCallee + ) { + evalNode = maybeCallee.parent; + } + } + } + + if (evalNode) { + context.report({ + node: evalNode, + messageId: "noFunctionConstructor", + }); + } + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-new-native-nonconstructor.js b/lib/rules/no-new-native-nonconstructor.js index 699390dd5a19..e8e12bbbdd29 100644 --- a/lib/rules/no-new-native-nonconstructor.js +++ b/lib/rules/no-new-native-nonconstructor.js @@ -17,50 +17,54 @@ const nonConstructorGlobalFunctionNames = ["Symbol", "BigInt"]; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - docs: { - description: "Disallow `new` operators with global non-constructor functions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" - }, + docs: { + description: + "Disallow `new` operators with global non-constructor functions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor", + }, - schema: [], + schema: [], - messages: { - noNewNonconstructor: "`{{name}}` cannot be called as a constructor." - } - }, + messages: { + noNewNonconstructor: + "`{{name}}` cannot be called as a constructor.", + }, + }, - create(context) { + create(context) { + const sourceCode = context.sourceCode; - const sourceCode = context.sourceCode; + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); + for (const nonConstructorName of nonConstructorGlobalFunctionNames) { + const variable = globalScope.set.get(nonConstructorName); - for (const nonConstructorName of nonConstructorGlobalFunctionNames) { - const variable = globalScope.set.get(nonConstructorName); + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const idNode = ref.identifier; + const parent = idNode.parent; - if (variable && variable.defs.length === 0) { - variable.references.forEach(ref => { - const idNode = ref.identifier; - const parent = idNode.parent; - - if (parent && parent.type === "NewExpression" && parent.callee === idNode) { - context.report({ - node: idNode, - messageId: "noNewNonconstructor", - data: { name: nonConstructorName } - }); - } - }); - } - } - } - }; - - } + if ( + parent && + parent.type === "NewExpression" && + parent.callee === idNode + ) { + context.report({ + node: idNode, + messageId: "noNewNonconstructor", + data: { name: nonConstructorName }, + }); + } + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-new-object.js b/lib/rules/no-new-object.js index 62b4aedfb100..c44f5f396356 100644 --- a/lib/rules/no-new-object.js +++ b/lib/rules/no-new-object.js @@ -18,59 +18,59 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow `Object` constructors", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new-object" - }, + docs: { + description: "Disallow `Object` constructors", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new-object", + }, - 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" - } - } - ] - }, + 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", + }, + }, + ], + }, - schema: [], + schema: [], - messages: { - preferLiteral: "The object literal notation {} is preferable." - } - }, + messages: { + preferLiteral: "The object literal notation {} is preferable.", + }, + }, - create(context) { + create(context) { + const sourceCode = context.sourceCode; - const sourceCode = context.sourceCode; + return { + NewExpression(node) { + const variable = astUtils.getVariableByName( + sourceCode.getScope(node), + node.callee.name, + ); - return { - NewExpression(node) { - const variable = astUtils.getVariableByName( - sourceCode.getScope(node), - node.callee.name - ); + if (variable && variable.identifiers.length > 0) { + return; + } - if (variable && variable.identifiers.length > 0) { - return; - } - - if (node.callee.name === "Object") { - context.report({ - node, - messageId: "preferLiteral" - }); - } - } - }; - } + if (node.callee.name === "Object") { + context.report({ + node, + messageId: "preferLiteral", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-new-require.js b/lib/rules/no-new-require.js index 0168ccd1a645..a84c5077f4f0 100644 --- a/lib/rules/no-new-require.js +++ b/lib/rules/no-new-require.js @@ -12,55 +12,56 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - } - } - ] - }, + meta: { + 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", + type: "suggestion", - docs: { - description: "Disallow `new` operators with calls to `require`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new-require" - }, + docs: { + description: "Disallow `new` operators with calls to `require`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new-require", + }, - schema: [], + schema: [], - messages: { - noNewRequire: "Unexpected use of new with require." - } - }, + messages: { + noNewRequire: "Unexpected use of new with require.", + }, + }, - create(context) { - - return { - - NewExpression(node) { - if (node.callee.type === "Identifier" && node.callee.name === "require") { - context.report({ - node, - messageId: "noNewRequire" - }); - } - } - }; - - } + create(context) { + return { + NewExpression(node) { + if ( + node.callee.type === "Identifier" && + node.callee.name === "require" + ) { + context.report({ + node, + messageId: "noNewRequire", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-new-symbol.js b/lib/rules/no-new-symbol.js index 18146faf5280..e7370781a5f1 100644 --- a/lib/rules/no-new-symbol.js +++ b/lib/rules/no-new-symbol.js @@ -12,61 +12,63 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - docs: { - description: "Disallow `new` operators with the `Symbol` object", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new-symbol" - }, + docs: { + description: "Disallow `new` operators with the `Symbol` object", + 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" - } - } - ] - }, + 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", + }, + }, + ], + }, - schema: [], + schema: [], - messages: { - noNewSymbol: "`Symbol` cannot be called as a constructor." - } - }, + messages: { + noNewSymbol: "`Symbol` cannot be called as a constructor.", + }, + }, - create(context) { + create(context) { + const sourceCode = context.sourceCode; - const sourceCode = context.sourceCode; + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + const variable = globalScope.set.get("Symbol"); - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - const variable = globalScope.set.get("Symbol"); + if (variable && variable.defs.length === 0) { + variable.references.forEach(ref => { + const idNode = ref.identifier; + const parent = idNode.parent; - if (variable && variable.defs.length === 0) { - variable.references.forEach(ref => { - const idNode = ref.identifier; - const parent = idNode.parent; - - if (parent && parent.type === "NewExpression" && parent.callee === idNode) { - context.report({ - node: idNode, - messageId: "noNewSymbol" - }); - } - }); - } - } - }; - - } + if ( + parent && + parent.type === "NewExpression" && + parent.callee === idNode + ) { + context.report({ + node: idNode, + messageId: "noNewSymbol", + }); + } + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-new-wrappers.js b/lib/rules/no-new-wrappers.js index 5050a98a044a..353b659a1e81 100644 --- a/lib/rules/no-new-wrappers.js +++ b/lib/rules/no-new-wrappers.js @@ -17,44 +17,46 @@ const { getVariableByName } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new-wrappers" - }, - - schema: [], - - messages: { - noConstructor: "Do not use {{fn}} as a constructor." - } - }, - - create(context) { - const { sourceCode } = context; - - return { - - NewExpression(node) { - const wrapperObjects = ["String", "Number", "Boolean"]; - const { name } = node.callee; - - if (wrapperObjects.includes(name)) { - const variable = getVariableByName(sourceCode.getScope(node), name); - - if (variable && variable.identifiers.length === 0) { - context.report({ - node, - messageId: "noConstructor", - data: { fn: name } - }); - } - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new-wrappers", + }, + + schema: [], + + messages: { + noConstructor: "Do not use {{fn}} as a constructor.", + }, + }, + + create(context) { + const { sourceCode } = context; + + return { + NewExpression(node) { + const wrapperObjects = ["String", "Number", "Boolean"]; + const { name } = node.callee; + + if (wrapperObjects.includes(name)) { + const variable = getVariableByName( + sourceCode.getScope(node), + name, + ); + + if (variable && variable.identifiers.length === 0) { + context.report({ + node, + messageId: "noConstructor", + data: { fn: name }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-new.js b/lib/rules/no-new.js index 9e20bad7b6e2..d74601008689 100644 --- a/lib/rules/no-new.js +++ b/lib/rules/no-new.js @@ -12,32 +12,31 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `new` operators outside of assignments or comparisons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-new" - }, - - schema: [], - - messages: { - noNewStatement: "Do not use 'new' for side effects." - } - }, - - create(context) { - - return { - "ExpressionStatement > NewExpression"(node) { - context.report({ - node: node.parent, - messageId: "noNewStatement" - }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow `new` operators outside of assignments or comparisons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-new", + }, + + schema: [], + + messages: { + noNewStatement: "Do not use 'new' for side effects.", + }, + }, + + create(context) { + return { + "ExpressionStatement > NewExpression"(node) { + context.report({ + node: node.parent, + messageId: "noNewStatement", + }); + }, + }; + }, }; diff --git a/lib/rules/no-nonoctal-decimal-escape.js b/lib/rules/no-nonoctal-decimal-escape.js index 5939390fd9a8..964f02f0c0fa 100644 --- a/lib/rules/no-nonoctal-decimal-escape.js +++ b/lib/rules/no-nonoctal-decimal-escape.js @@ -17,7 +17,7 @@ const QUICK_TEST_REGEX = /\\[89]/u; * @returns {string} "\uXXXX" sequence. */ function getUnicodeEscape(character) { - return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`; + return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`; } //------------------------------------------------------------------------------ @@ -26,123 +26,143 @@ function getUnicodeEscape(character) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `\\8` and `\\9` escape sequences in string literals", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.", - - // suggestions - refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.", - escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Creates a new Suggestion object. - * @param {string} messageId "refactor" or "escapeBackslash". - * @param {int[]} range The range to replace. - * @param {string} replacement New text for the range. - * @returns {Object} Suggestion - */ - function createSuggestion(messageId, range, replacement) { - return { - messageId, - data: { - original: sourceCode.getText().slice(...range), - replacement - }, - fix(fixer) { - return fixer.replaceTextRange(range, replacement); - } - }; - } - - return { - Literal(node) { - if (typeof node.value !== "string") { - return; - } - - if (!QUICK_TEST_REGEX.test(node.raw)) { - return; - } - - const regex = /(?:[^\\]|(?\\.))*?(?\\[89])/suy; - let match; - - while ((match = regex.exec(node.raw))) { - const { previousEscape, decimalEscape } = match.groups; - const decimalEscapeRangeEnd = node.range[0] + match.index + match[0].length; - const decimalEscapeRangeStart = decimalEscapeRangeEnd - decimalEscape.length; - const decimalEscapeRange = [decimalEscapeRangeStart, decimalEscapeRangeEnd]; - const suggest = []; - - // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape` - if (previousEscape === "\\0") { - - /* - * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8". - * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing - * an octal escape while fixing a decimal escape, we provide different suggestions. - */ - suggest.push( - createSuggestion( // "\0\8" -> "\u00008" - "refactor", - [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd], - `${getUnicodeEscape("\0")}${decimalEscape[1]}` - ), - createSuggestion( // "\8" -> "\u0038" - "refactor", - decimalEscapeRange, - getUnicodeEscape(decimalEscape[1]) - ) - ); - } else { - suggest.push( - createSuggestion( // "\8" -> "8" - "refactor", - decimalEscapeRange, - decimalEscape[1] - ) - ); - } - - suggest.push( - createSuggestion( // "\8" -> "\\8" - "escapeBackslash", - decimalEscapeRange, - `\\${decimalEscape}` - ) - ); - - context.report({ - node, - loc: { - start: sourceCode.getLocFromIndex(decimalEscapeRangeStart), - end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd) - }, - messageId: "decimalEscape", - data: { - decimalEscape - }, - suggest - }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow `\\8` and `\\9` escape sequences in string literals", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape", + }, + + hasSuggestions: true, + + schema: [], + + messages: { + decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.", + + // suggestions + refactor: + "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.", + escapeBackslash: + "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Creates a new Suggestion object. + * @param {string} messageId "refactor" or "escapeBackslash". + * @param {int[]} range The range to replace. + * @param {string} replacement New text for the range. + * @returns {Object} Suggestion + */ + function createSuggestion(messageId, range, replacement) { + return { + messageId, + data: { + original: sourceCode.getText().slice(...range), + replacement, + }, + fix(fixer) { + return fixer.replaceTextRange(range, replacement); + }, + }; + } + + return { + Literal(node) { + if (typeof node.value !== "string") { + return; + } + + if (!QUICK_TEST_REGEX.test(node.raw)) { + return; + } + + const regex = + /(?:[^\\]|(?\\.))*?(?\\[89])/suy; + let match; + + while ((match = regex.exec(node.raw))) { + const { previousEscape, decimalEscape } = match.groups; + const decimalEscapeRangeEnd = + node.range[0] + match.index + match[0].length; + const decimalEscapeRangeStart = + decimalEscapeRangeEnd - decimalEscape.length; + const decimalEscapeRange = [ + decimalEscapeRangeStart, + decimalEscapeRangeEnd, + ]; + const suggest = []; + + // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape` + if (previousEscape === "\\0") { + /* + * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8". + * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing + * an octal escape while fixing a decimal escape, we provide different suggestions. + */ + suggest.push( + createSuggestion( + // "\0\8" -> "\u00008" + "refactor", + [ + decimalEscapeRangeStart - + previousEscape.length, + decimalEscapeRangeEnd, + ], + `${getUnicodeEscape("\0")}${decimalEscape[1]}`, + ), + createSuggestion( + // "\8" -> "\u0038" + "refactor", + decimalEscapeRange, + getUnicodeEscape(decimalEscape[1]), + ), + ); + } else { + suggest.push( + createSuggestion( + // "\8" -> "8" + "refactor", + decimalEscapeRange, + decimalEscape[1], + ), + ); + } + + suggest.push( + createSuggestion( + // "\8" -> "\\8" + "escapeBackslash", + decimalEscapeRange, + `\\${decimalEscape}`, + ), + ); + + context.report({ + node, + loc: { + start: sourceCode.getLocFromIndex( + decimalEscapeRangeStart, + ), + end: sourceCode.getLocFromIndex( + decimalEscapeRangeEnd, + ), + }, + messageId: "decimalEscape", + data: { + decimalEscape, + }, + suggest, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-obj-calls.js b/lib/rules/no-obj-calls.js index ee767ea2fa30..c9302618ec0c 100644 --- a/lib/rules/no-obj-calls.js +++ b/lib/rules/no-obj-calls.js @@ -9,7 +9,11 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker } = require("@eslint-community/eslint-utils"); +const { + CALL, + CONSTRUCT, + ReferenceTracker, +} = require("@eslint-community/eslint-utils"); const getPropertyName = require("./utils/ast-utils").getStaticPropertyName; //------------------------------------------------------------------------------ @@ -24,13 +28,13 @@ const nonCallableGlobals = ["Atomics", "JSON", "Math", "Reflect", "Intl"]; * @returns {string} name to report */ function getReportNodeName(node) { - if (node.type === "ChainExpression") { - return getReportNodeName(node.expression); - } - if (node.type === "MemberExpression") { - return getPropertyName(node); - } - return node.name; + if (node.type === "ChainExpression") { + return getReportNodeName(node.expression); + } + if (node.type === "MemberExpression") { + return getPropertyName(node); + } + return node.name; } //------------------------------------------------------------------------------ @@ -39,48 +43,57 @@ function getReportNodeName(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow calling global object properties as functions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-obj-calls" - }, - - schema: [], - - messages: { - unexpectedCall: "'{{name}}' is not a function.", - unexpectedRefCall: "'{{name}}' is reference to '{{ref}}', which is not a function." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - return { - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const traceMap = {}; - - for (const g of nonCallableGlobals) { - traceMap[g] = { - [CALL]: true, - [CONSTRUCT]: true - }; - } - - for (const { node: refNode, path } of tracker.iterateGlobalReferences(traceMap)) { - const name = getReportNodeName(refNode.callee); - const ref = path[0]; - const messageId = name === ref ? "unexpectedCall" : "unexpectedRefCall"; - - context.report({ node: refNode, messageId, data: { name, ref } }); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow calling global object properties as functions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-obj-calls", + }, + + schema: [], + + messages: { + unexpectedCall: "'{{name}}' is not a function.", + unexpectedRefCall: + "'{{name}}' is reference to '{{ref}}', which is not a function.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + const traceMap = {}; + + for (const g of nonCallableGlobals) { + traceMap[g] = { + [CALL]: true, + [CONSTRUCT]: true, + }; + } + + for (const { + node: refNode, + path, + } of tracker.iterateGlobalReferences(traceMap)) { + const name = getReportNodeName(refNode.callee); + const ref = path[0]; + const messageId = + name === ref ? "unexpectedCall" : "unexpectedRefCall"; + + context.report({ + node: refNode, + messageId, + data: { name, ref }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-object-constructor.js b/lib/rules/no-object-constructor.js index 8875ec2124b1..94eed7a809cc 100644 --- a/lib/rules/no-object-constructor.js +++ b/lib/rules/no-object-constructor.js @@ -10,10 +10,10 @@ //------------------------------------------------------------------------------ const { - getVariableByName, - isArrowToken, - isStartOfExpressionStatement, - needsPrecedingSemicolon + getVariableByName, + isArrowToken, + isStartOfExpressionStatement, + needsPrecedingSemicolon, } = require("./utils/ast-utils"); //------------------------------------------------------------------------------ @@ -22,96 +22,103 @@ const { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow calls to the `Object` constructor without an argument", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-object-constructor" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - preferLiteral: "The object literal notation {} is preferable.", - useLiteral: "Replace with '{{replacement}}'.", - useLiteralAfterSemicolon: "Replace with '{{replacement}}', add preceding semicolon." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses. - * @param {ASTNode} node The node to be replaced. - * @returns {boolean} Whether or not parentheses around the object literal are required. - */ - function needsParentheses(node) { - if (isStartOfExpressionStatement(node)) { - return true; - } - - const prevToken = sourceCode.getTokenBefore(node); - - if (prevToken && isArrowToken(prevToken)) { - return true; - } - - return false; - } - - /** - * Reports on nodes where the `Object` constructor is called without arguments. - * @param {ASTNode} node The node to evaluate. - * @returns {void} - */ - function check(node) { - if (node.callee.type !== "Identifier" || node.callee.name !== "Object" || node.arguments.length) { - return; - } - - const variable = getVariableByName(sourceCode.getScope(node), "Object"); - - if (variable && variable.identifiers.length === 0) { - let replacement; - let fixText; - let messageId = "useLiteral"; - - if (needsParentheses(node)) { - replacement = "({})"; - if (needsPrecedingSemicolon(sourceCode, node)) { - fixText = ";({})"; - messageId = "useLiteralAfterSemicolon"; - } else { - fixText = "({})"; - } - } else { - replacement = fixText = "{}"; - } - - context.report({ - node, - messageId: "preferLiteral", - suggest: [ - { - messageId, - data: { replacement }, - fix: fixer => fixer.replaceText(node, fixText) - } - ] - }); - } - } - - return { - CallExpression: check, - NewExpression: check - }; - - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow calls to the `Object` constructor without an argument", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-object-constructor", + }, + + hasSuggestions: true, + + schema: [], + + messages: { + preferLiteral: "The object literal notation {} is preferable.", + useLiteral: "Replace with '{{replacement}}'.", + useLiteralAfterSemicolon: + "Replace with '{{replacement}}', add preceding semicolon.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Determines whether or not an object literal that replaces a specified node needs to be enclosed in parentheses. + * @param {ASTNode} node The node to be replaced. + * @returns {boolean} Whether or not parentheses around the object literal are required. + */ + function needsParentheses(node) { + if (isStartOfExpressionStatement(node)) { + return true; + } + + const prevToken = sourceCode.getTokenBefore(node); + + if (prevToken && isArrowToken(prevToken)) { + return true; + } + + return false; + } + + /** + * Reports on nodes where the `Object` constructor is called without arguments. + * @param {ASTNode} node The node to evaluate. + * @returns {void} + */ + function check(node) { + if ( + node.callee.type !== "Identifier" || + node.callee.name !== "Object" || + node.arguments.length + ) { + return; + } + + const variable = getVariableByName( + sourceCode.getScope(node), + "Object", + ); + + if (variable && variable.identifiers.length === 0) { + let replacement; + let fixText; + let messageId = "useLiteral"; + + if (needsParentheses(node)) { + replacement = "({})"; + if (needsPrecedingSemicolon(sourceCode, node)) { + fixText = ";({})"; + messageId = "useLiteralAfterSemicolon"; + } else { + fixText = "({})"; + } + } else { + replacement = fixText = "{}"; + } + + context.report({ + node, + messageId: "preferLiteral", + suggest: [ + { + messageId, + data: { replacement }, + fix: fixer => fixer.replaceText(node, fixText), + }, + ], + }); + } + } + + return { + CallExpression: check, + NewExpression: check, + }; + }, }; diff --git a/lib/rules/no-octal-escape.js b/lib/rules/no-octal-escape.js index 6924d5419d56..86a3530736c6 100644 --- a/lib/rules/no-octal-escape.js +++ b/lib/rules/no-octal-escape.js @@ -11,46 +11,43 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow octal escape sequences in string literals", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-octal-escape" - }, - - schema: [], - - messages: { - octalEscapeSequence: "Don't use octal: '\\{{sequence}}'. Use '\\u....' instead." - } - }, - - create(context) { - - return { - - Literal(node) { - if (typeof node.value !== "string") { - return; - } - - // \0 represents a valid NULL character if it isn't followed by a digit. - const match = node.raw.match( - /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su - ); - - if (match) { - context.report({ - node, - messageId: "octalEscapeSequence", - data: { sequence: match[1] } - }); - } - } - - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow octal escape sequences in string literals", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-octal-escape", + }, + + schema: [], + + messages: { + octalEscapeSequence: + "Don't use octal: '\\{{sequence}}'. Use '\\u....' instead.", + }, + }, + + create(context) { + return { + Literal(node) { + if (typeof node.value !== "string") { + return; + } + + // \0 represents a valid NULL character if it isn't followed by a digit. + const match = node.raw.match( + /^(?:[^\\]|\\.)*?\\([0-3][0-7]{1,2}|[4-7][0-7]|0(?=[89])|[1-7])/su, + ); + + if (match) { + context.report({ + node, + messageId: "octalEscapeSequence", + data: { sequence: match[1] }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-octal.js b/lib/rules/no-octal.js index dc027696a8e6..672a3509e71d 100644 --- a/lib/rules/no-octal.js +++ b/lib/rules/no-octal.js @@ -11,35 +11,35 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow octal literals", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-octal" - }, - - schema: [], - - messages: { - noOctal: "Octal literals should not be used." - } - }, - - create(context) { - - return { - - Literal(node) { - if (typeof node.value === "number" && /^0[0-9]/u.test(node.raw)) { - context.report({ - node, - messageId: "noOctal" - }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow octal literals", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-octal", + }, + + schema: [], + + messages: { + noOctal: "Octal literals should not be used.", + }, + }, + + create(context) { + return { + Literal(node) { + if ( + typeof node.value === "number" && + /^0[0-9]/u.test(node.raw) + ) { + context.report({ + node, + messageId: "noOctal", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-param-reassign.js b/lib/rules/no-param-reassign.js index 4f6c6f51d37b..baca5531407b 100644 --- a/lib/rules/no-param-reassign.js +++ b/lib/rules/no-param-reassign.js @@ -8,223 +8,241 @@ // Rule Definition //------------------------------------------------------------------------------ -const stopNodePattern = /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u; +const stopNodePattern = + /(?:Statement|Declaration|Function(?:Expression)?|Program)$/u; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow reassigning function parameters", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-param-reassign" - }, - - schema: [ - { - oneOf: [ - { - type: "object", - properties: { - props: { - enum: [false] - } - }, - additionalProperties: false - }, - { - type: "object", - properties: { - props: { - enum: [true] - }, - ignorePropertyModificationsFor: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - }, - ignorePropertyModificationsForRegex: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - assignmentToFunctionParam: "Assignment to function parameter '{{name}}'.", - assignmentToFunctionParamProp: "Assignment to property of function parameter '{{name}}'." - } - }, - - create(context) { - const props = context.options[0] && context.options[0].props; - const ignoredPropertyAssignmentsFor = context.options[0] && context.options[0].ignorePropertyModificationsFor || []; - const ignoredPropertyAssignmentsForRegex = context.options[0] && context.options[0].ignorePropertyModificationsForRegex || []; - const sourceCode = context.sourceCode; - - /** - * Checks whether or not the reference modifies properties of its variable. - * @param {Reference} reference A reference to check. - * @returns {boolean} Whether or not the reference modifies properties of its variable. - */ - function isModifyingProp(reference) { - let node = reference.identifier; - let parent = node.parent; - - while (parent && (!stopNodePattern.test(parent.type) || - parent.type === "ForInStatement" || parent.type === "ForOfStatement")) { - switch (parent.type) { - - // e.g. foo.a = 0; - case "AssignmentExpression": - return parent.left === node; - - // e.g. ++foo.a; - case "UpdateExpression": - return true; - - // e.g. delete foo.a; - case "UnaryExpression": - if (parent.operator === "delete") { - return true; - } - break; - - // e.g. for (foo.a in b) {} - case "ForInStatement": - case "ForOfStatement": - if (parent.left === node) { - return true; - } - - // this is a stop node for parent.right and parent.body - return false; - - // EXCLUDES: e.g. cache.get(foo.a).b = 0; - case "CallExpression": - if (parent.callee !== node) { - return false; - } - break; - - // EXCLUDES: e.g. cache[foo.a] = 0; - case "MemberExpression": - if (parent.property === node) { - return false; - } - break; - - // EXCLUDES: e.g. ({ [foo]: a }) = bar; - case "Property": - if (parent.key === node) { - return false; - } - - break; - - // EXCLUDES: e.g. (foo ? a : b).c = bar; - case "ConditionalExpression": - if (parent.test === node) { - return false; - } - - break; - - // no default - } - - node = parent; - parent = node.parent; - } - - return false; - } - - /** - * Tests that an identifier name matches any of the ignored property assignments. - * First we test strings in ignoredPropertyAssignmentsFor. - * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings. - * @param {string} identifierName A string that describes the name of an identifier to - * ignore property assignments for. - * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not. - */ - function isIgnoredPropertyAssignment(identifierName) { - return ignoredPropertyAssignmentsFor.includes(identifierName) || - ignoredPropertyAssignmentsForRegex.some(ignored => new RegExp(ignored, "u").test(identifierName)); - } - - /** - * Reports a reference if is non initializer and writable. - * @param {Reference} reference A reference to check. - * @param {int} index The index of the reference in the references. - * @param {Reference[]} references The array that the reference belongs to. - * @returns {void} - */ - function checkReference(reference, index, references) { - const identifier = reference.identifier; - - if (identifier && - !reference.init && - - /* - * Destructuring assignments can have multiple default value, - * so possibly there are multiple writeable references for the same identifier. - */ - (index === 0 || references[index - 1].identifier !== identifier) - ) { - if (reference.isWrite()) { - context.report({ - node: identifier, - messageId: "assignmentToFunctionParam", - data: { name: identifier.name } - }); - } else if (props && isModifyingProp(reference) && !isIgnoredPropertyAssignment(identifier.name)) { - context.report({ - node: identifier, - messageId: "assignmentToFunctionParamProp", - data: { name: identifier.name } - }); - } - } - } - - /** - * Finds and reports references that are non initializer and writable. - * @param {Variable} variable A variable to check. - * @returns {void} - */ - function checkVariable(variable) { - if (variable.defs[0].type === "Parameter") { - variable.references.forEach(checkReference); - } - } - - /** - * Checks parameters of a given function node. - * @param {ASTNode} node A function node to check. - * @returns {void} - */ - function checkForFunction(node) { - sourceCode.getDeclaredVariables(node).forEach(checkVariable); - } - - return { - - // `:exit` is needed for the `node.parent` property of identifier nodes. - "FunctionDeclaration:exit": checkForFunction, - "FunctionExpression:exit": checkForFunction, - "ArrowFunctionExpression:exit": checkForFunction - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow reassigning function parameters", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-param-reassign", + }, + + schema: [ + { + oneOf: [ + { + type: "object", + properties: { + props: { + enum: [false], + }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + props: { + enum: [true], + }, + ignorePropertyModificationsFor: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + ignorePropertyModificationsForRegex: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + assignmentToFunctionParam: + "Assignment to function parameter '{{name}}'.", + assignmentToFunctionParamProp: + "Assignment to property of function parameter '{{name}}'.", + }, + }, + + create(context) { + const props = context.options[0] && context.options[0].props; + const ignoredPropertyAssignmentsFor = + (context.options[0] && + context.options[0].ignorePropertyModificationsFor) || + []; + const ignoredPropertyAssignmentsForRegex = + (context.options[0] && + context.options[0].ignorePropertyModificationsForRegex) || + []; + const sourceCode = context.sourceCode; + + /** + * Checks whether or not the reference modifies properties of its variable. + * @param {Reference} reference A reference to check. + * @returns {boolean} Whether or not the reference modifies properties of its variable. + */ + function isModifyingProp(reference) { + let node = reference.identifier; + let parent = node.parent; + + while ( + parent && + (!stopNodePattern.test(parent.type) || + parent.type === "ForInStatement" || + parent.type === "ForOfStatement") + ) { + switch (parent.type) { + // e.g. foo.a = 0; + case "AssignmentExpression": + return parent.left === node; + + // e.g. ++foo.a; + case "UpdateExpression": + return true; + + // e.g. delete foo.a; + case "UnaryExpression": + if (parent.operator === "delete") { + return true; + } + break; + + // e.g. for (foo.a in b) {} + case "ForInStatement": + case "ForOfStatement": + if (parent.left === node) { + return true; + } + + // this is a stop node for parent.right and parent.body + return false; + + // EXCLUDES: e.g. cache.get(foo.a).b = 0; + case "CallExpression": + if (parent.callee !== node) { + return false; + } + break; + + // EXCLUDES: e.g. cache[foo.a] = 0; + case "MemberExpression": + if (parent.property === node) { + return false; + } + break; + + // EXCLUDES: e.g. ({ [foo]: a }) = bar; + case "Property": + if (parent.key === node) { + return false; + } + + break; + + // EXCLUDES: e.g. (foo ? a : b).c = bar; + case "ConditionalExpression": + if (parent.test === node) { + return false; + } + + break; + + // no default + } + + node = parent; + parent = node.parent; + } + + return false; + } + + /** + * Tests that an identifier name matches any of the ignored property assignments. + * First we test strings in ignoredPropertyAssignmentsFor. + * Then we instantiate and test RegExp objects from ignoredPropertyAssignmentsForRegex strings. + * @param {string} identifierName A string that describes the name of an identifier to + * ignore property assignments for. + * @returns {boolean} Whether the string matches an ignored property assignment regular expression or not. + */ + function isIgnoredPropertyAssignment(identifierName) { + return ( + ignoredPropertyAssignmentsFor.includes(identifierName) || + ignoredPropertyAssignmentsForRegex.some(ignored => + new RegExp(ignored, "u").test(identifierName), + ) + ); + } + + /** + * Reports a reference if is non initializer and writable. + * @param {Reference} reference A reference to check. + * @param {int} index The index of the reference in the references. + * @param {Reference[]} references The array that the reference belongs to. + * @returns {void} + */ + function checkReference(reference, index, references) { + const identifier = reference.identifier; + + if ( + identifier && + !reference.init && + /* + * Destructuring assignments can have multiple default value, + * so possibly there are multiple writeable references for the same identifier. + */ + (index === 0 || references[index - 1].identifier !== identifier) + ) { + if (reference.isWrite()) { + context.report({ + node: identifier, + messageId: "assignmentToFunctionParam", + data: { name: identifier.name }, + }); + } else if ( + props && + isModifyingProp(reference) && + !isIgnoredPropertyAssignment(identifier.name) + ) { + context.report({ + node: identifier, + messageId: "assignmentToFunctionParamProp", + data: { name: identifier.name }, + }); + } + } + } + + /** + * Finds and reports references that are non initializer and writable. + * @param {Variable} variable A variable to check. + * @returns {void} + */ + function checkVariable(variable) { + if (variable.defs[0].type === "Parameter") { + variable.references.forEach(checkReference); + } + } + + /** + * Checks parameters of a given function node. + * @param {ASTNode} node A function node to check. + * @returns {void} + */ + function checkForFunction(node) { + sourceCode.getDeclaredVariables(node).forEach(checkVariable); + } + + return { + // `:exit` is needed for the `node.parent` property of identifier nodes. + "FunctionDeclaration:exit": checkForFunction, + "FunctionExpression:exit": checkForFunction, + "ArrowFunctionExpression:exit": checkForFunction, + }; + }, }; diff --git a/lib/rules/no-path-concat.js b/lib/rules/no-path-concat.js index 3051648fb59a..8d0a27ba2f84 100644 --- a/lib/rules/no-path-concat.js +++ b/lib/rules/no-path-concat.js @@ -11,70 +11,69 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-path-concat" - }, - - schema: [], - - messages: { - usePathFunctions: "Use path.join() or path.resolve() instead of + to create paths." - } - }, - - create(context) { - - const MATCHER = /^__(?:dir|file)name$/u; - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - BinaryExpression(node) { - - const left = node.left, - right = node.right; - - if (node.operator === "+" && - ((left.type === "Identifier" && MATCHER.test(left.name)) || - (right.type === "Identifier" && MATCHER.test(right.name))) - ) { - - context.report({ - node, - messageId: "usePathFunctions" - }); - } - } - - }; - - } + meta: { + 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`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-path-concat", + }, + + schema: [], + + messages: { + usePathFunctions: + "Use path.join() or path.resolve() instead of + to create paths.", + }, + }, + + create(context) { + const MATCHER = /^__(?:dir|file)name$/u; + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression(node) { + const left = node.left, + right = node.right; + + if ( + node.operator === "+" && + ((left.type === "Identifier" && MATCHER.test(left.name)) || + (right.type === "Identifier" && + MATCHER.test(right.name))) + ) { + context.report({ + node, + messageId: "usePathFunctions", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-plusplus.js b/lib/rules/no-plusplus.js index 8a1d6ad58759..3f87637e7ba3 100644 --- a/lib/rules/no-plusplus.js +++ b/lib/rules/no-plusplus.js @@ -16,9 +16,9 @@ * @returns {boolean} `true` if the node is `ForStatement` update. */ function isForStatementUpdate(node) { - const parent = node.parent; + const parent = node.parent; - return parent.type === "ForStatement" && parent.update === node; + return parent.type === "ForStatement" && parent.update === node; } /** @@ -32,13 +32,13 @@ function isForStatementUpdate(node) { * @returns {boolean} `true` if the node is a for loop afterthought. */ function isForLoopAfterthought(node) { - const parent = node.parent; + const parent = node.parent; - if (parent.type === "SequenceExpression") { - return isForLoopAfterthought(parent); - } + if (parent.type === "SequenceExpression") { + return isForLoopAfterthought(parent); + } - return isForStatementUpdate(node); + return isForStatementUpdate(node); } //------------------------------------------------------------------------------ @@ -47,57 +47,56 @@ function isForLoopAfterthought(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - allowForLoopAfterthoughts: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedUnaryOp: "Unary operator '{{operator}}' used." - } - }, - - create(context) { - const [{ allowForLoopAfterthoughts }] = context.options; - - return { - - UpdateExpression(node) { - if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) { - return; - } - - context.report({ - node, - messageId: "unexpectedUnaryOp", - data: { - operator: node.operator - } - }); - } - - }; - - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + allowForLoopAfterthoughts: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedUnaryOp: "Unary operator '{{operator}}' used.", + }, + }, + + create(context) { + const [{ allowForLoopAfterthoughts }] = context.options; + + return { + UpdateExpression(node) { + if (allowForLoopAfterthoughts && isForLoopAfterthought(node)) { + return; + } + + context.report({ + node, + messageId: "unexpectedUnaryOp", + data: { + operator: node.operator, + }, + }); + }, + }; + }, }; diff --git a/lib/rules/no-process-env.js b/lib/rules/no-process-env.js index a5ab059cf86f..ab7f38f6efbe 100644 --- a/lib/rules/no-process-env.js +++ b/lib/rules/no-process-env.js @@ -11,57 +11,58 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - } - } - ] - }, + meta: { + 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", + type: "suggestion", - docs: { - description: "Disallow the use of `process.env`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-process-env" - }, + docs: { + description: "Disallow the use of `process.env`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-process-env", + }, - schema: [], + schema: [], - messages: { - unexpectedProcessEnv: "Unexpected use of process.env." - } - }, + messages: { + unexpectedProcessEnv: "Unexpected use of process.env.", + }, + }, - create(context) { + create(context) { + return { + MemberExpression(node) { + const objectName = node.object.name, + propertyName = node.property.name; - return { - - MemberExpression(node) { - const objectName = node.object.name, - propertyName = node.property.name; - - if (objectName === "process" && !node.computed && propertyName && propertyName === "env") { - context.report({ node, messageId: "unexpectedProcessEnv" }); - } - - } - - }; - - } + if ( + objectName === "process" && + !node.computed && + propertyName && + propertyName === "env" + ) { + context.report({ node, messageId: "unexpectedProcessEnv" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-process-exit.js b/lib/rules/no-process-exit.js index 72acca7bc9e6..7dd7e124e10f 100644 --- a/lib/rules/no-process-exit.js +++ b/lib/rules/no-process-exit.js @@ -11,53 +11,57 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-process-exit" - }, - - schema: [], - - messages: { - noProcessExit: "Don't use process.exit(); throw an error instead." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) { - context.report({ node: node.parent, messageId: "noProcessExit" }); - } - }; - - } + meta: { + 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()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-process-exit", + }, + + schema: [], + + messages: { + noProcessExit: "Don't use process.exit(); throw an error instead.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"( + node, + ) { + context.report({ + node: node.parent, + messageId: "noProcessExit", + }); + }, + }; + }, }; diff --git a/lib/rules/no-promise-executor-return.js b/lib/rules/no-promise-executor-return.js index a056f6d67f4e..9defa253d8ae 100644 --- a/lib/rules/no-promise-executor-return.js +++ b/lib/rules/no-promise-executor-return.js @@ -16,7 +16,10 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpression"]); +const functionTypesToCheck = new Set([ + "ArrowFunctionExpression", + "FunctionExpression", +]); /** * Determines whether the given identifier node is a reference to a global variable. @@ -25,9 +28,13 @@ const functionTypesToCheck = new Set(["ArrowFunctionExpression", "FunctionExpres * @returns {boolean} True if the identifier is a reference to a global variable. */ function isGlobalReference(node, scope) { - const variable = findVariable(scope, node); + const variable = findVariable(scope, node); - return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; + return ( + variable !== null && + variable.scope.type === "global" && + variable.defs.length === 0 + ); } /** @@ -36,12 +43,12 @@ function isGlobalReference(node, scope) { * @returns {Scope} Function's outer scope. */ function getOuterScope(scope) { - const upper = scope.upper; + const upper = scope.upper; - if (upper.type === "function-expression-name") { - return upper.upper; - } - return upper; + if (upper.type === "function-expression-name") { + return upper.upper; + } + return upper; } /** @@ -51,13 +58,15 @@ function getOuterScope(scope) { * @returns {boolean} `true` if the node is a Promise executor. */ function isPromiseExecutor(node, scope) { - const parent = node.parent; - - return parent.type === "NewExpression" && - parent.arguments[0] === node && - parent.callee.type === "Identifier" && - parent.callee.name === "Promise" && - isGlobalReference(parent.callee, getOuterScope(scope)); + const parent = node.parent; + + return ( + parent.type === "NewExpression" && + parent.arguments[0] === node && + parent.callee.type === "Identifier" && + parent.callee.name === "Promise" && + isGlobalReference(parent.callee, getOuterScope(scope)) + ); } /** @@ -66,7 +75,7 @@ function isPromiseExecutor(node, scope) { * @returns {boolean} - `true` if the node is a void expression */ function expressionIsVoid(node) { - return node.type === "UnaryExpression" && node.operator === "void"; + return node.type === "UnaryExpression" && node.operator === "void"; } /** @@ -77,39 +86,40 @@ function expressionIsVoid(node) { * @returns {Array} - An array of fix objects to apply to the node. */ function voidPrependFixer(sourceCode, node, fixer) { - - const requiresParens = - - // prepending `void ` will fail if the node has a lower precedence than void - astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression", operator: "void" }) && - - // check if there are parentheses around the node to avoid redundant parentheses - !astUtils.isParenthesised(sourceCode, node); - - // avoid parentheses issues - const returnOrArrowToken = sourceCode.getTokenBefore( - node, - node.parent.type === "ArrowFunctionExpression" - ? astUtils.isArrowToken - - // isReturnToken - : token => token.type === "Keyword" && token.value === "return" - ); - - const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); - - const prependSpace = - - // is return token, as => allows void to be adjacent - returnOrArrowToken.value === "return" && - - // If two tokens (return and "(") are adjacent - returnOrArrowToken.range[1] === firstToken.range[0]; - - return [ - fixer.insertTextBefore(firstToken, `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`), - fixer.insertTextAfter(node, requiresParens ? ")" : "") - ]; + const requiresParens = + // prepending `void ` will fail if the node has a lower precedence than void + astUtils.getPrecedence(node) < + astUtils.getPrecedence({ + type: "UnaryExpression", + operator: "void", + }) && + // check if there are parentheses around the node to avoid redundant parentheses + !astUtils.isParenthesised(sourceCode, node); + + // avoid parentheses issues + const returnOrArrowToken = sourceCode.getTokenBefore( + node, + node.parent.type === "ArrowFunctionExpression" + ? astUtils.isArrowToken + : // isReturnToken + token => token.type === "Keyword" && token.value === "return", + ); + + const firstToken = sourceCode.getTokenAfter(returnOrArrowToken); + + const prependSpace = + // is return token, as => allows void to be adjacent + returnOrArrowToken.value === "return" && + // If two tokens (return and "(") are adjacent + returnOrArrowToken.range[1] === firstToken.range[0]; + + return [ + fixer.insertTextBefore( + firstToken, + `${prependSpace ? " " : ""}void ${requiresParens ? "(" : ""}`, + ), + fixer.insertTextAfter(node, requiresParens ? ")" : ""), + ]; } /** @@ -120,16 +130,18 @@ function voidPrependFixer(sourceCode, node, fixer) { * @returns {Array} - An array of fix objects to apply to the node. */ function curlyWrapFixer(sourceCode, node, fixer) { - - // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923 - const arrowToken = sourceCode.getTokenBefore(node.body, astUtils.isArrowToken); - const firstToken = sourceCode.getTokenAfter(arrowToken); - const lastToken = sourceCode.getLastToken(node); - - return [ - fixer.insertTextBefore(firstToken, "{"), - fixer.insertTextAfter(lastToken, "}") - ]; + // https://github.com/eslint/eslint/pull/17282#issuecomment-1592795923 + const arrowToken = sourceCode.getTokenBefore( + node.body, + astUtils.isArrowToken, + ); + const firstToken = sourceCode.getTokenAfter(arrowToken); + const lastToken = sourceCode.getLastToken(node); + + return [ + fixer.insertTextBefore(firstToken, "{"), + fixer.insertTextAfter(lastToken, "}"), + ]; } //------------------------------------------------------------------------------ @@ -138,126 +150,146 @@ function curlyWrapFixer(sourceCode, node, fixer) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - allowVoid: false - }], - - docs: { - description: "Disallow returning values from Promise executor functions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-promise-executor-return" - }, - - hasSuggestions: true, - - schema: [{ - type: "object", - properties: { - allowVoid: { - type: "boolean" - } - }, - additionalProperties: false - }], - - messages: { - returnsValue: "Return values from promise executor functions cannot be read.", - - // arrow and function suggestions - prependVoid: "Prepend `void` to the expression.", - - // only arrow suggestions - wrapBraces: "Wrap the expression in `{}`." - } - }, - - create(context) { - let funcInfo = null; - const sourceCode = context.sourceCode; - const [{ allowVoid }] = context.options; - - return { - - onCodePathStart(_, node) { - funcInfo = { - upper: funcInfo, - shouldCheck: - functionTypesToCheck.has(node.type) && - isPromiseExecutor(node, sourceCode.getScope(node)) - }; - - if (// Is a Promise executor - funcInfo.shouldCheck && - node.type === "ArrowFunctionExpression" && - node.expression && - - // Except void - !(allowVoid && expressionIsVoid(node.body)) - ) { - const suggest = []; - - // prevent useless refactors - if (allowVoid) { - suggest.push({ - messageId: "prependVoid", - fix(fixer) { - return voidPrependFixer(sourceCode, node.body, fixer); - } - }); - } - - // Do not suggest wrapping an unnamed FunctionExpression in braces as that would be invalid syntax. - if (!(node.body.type === "FunctionExpression" && !node.body.id)) { - suggest.push({ - messageId: "wrapBraces", - fix(fixer) { - return curlyWrapFixer(sourceCode, node, fixer); - } - }); - } - - context.report({ - node: node.body, - messageId: "returnsValue", - suggest - }); - } - }, - - onCodePathEnd() { - funcInfo = funcInfo.upper; - }, - - ReturnStatement(node) { - if (!(funcInfo.shouldCheck && node.argument)) { - return; - } - - // node is `return ` - if (!allowVoid) { - context.report({ node, messageId: "returnsValue" }); - return; - } - - if (expressionIsVoid(node.argument)) { - return; - } - - // allowVoid && !expressionIsVoid - context.report({ - node, - messageId: "returnsValue", - suggest: [{ - messageId: "prependVoid", - fix(fixer) { - return voidPrependFixer(sourceCode, node.argument, fixer); - } - }] - }); - } - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + allowVoid: false, + }, + ], + + docs: { + description: + "Disallow returning values from Promise executor functions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-promise-executor-return", + }, + + hasSuggestions: true, + + schema: [ + { + type: "object", + properties: { + allowVoid: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + returnsValue: + "Return values from promise executor functions cannot be read.", + + // arrow and function suggestions + prependVoid: "Prepend `void` to the expression.", + + // only arrow suggestions + wrapBraces: "Wrap the expression in `{}`.", + }, + }, + + create(context) { + let funcInfo = null; + const sourceCode = context.sourceCode; + const [{ allowVoid }] = context.options; + + return { + onCodePathStart(_, node) { + funcInfo = { + upper: funcInfo, + shouldCheck: + functionTypesToCheck.has(node.type) && + isPromiseExecutor(node, sourceCode.getScope(node)), + }; + + if ( + // Is a Promise executor + funcInfo.shouldCheck && + node.type === "ArrowFunctionExpression" && + node.expression && + // Except void + !(allowVoid && expressionIsVoid(node.body)) + ) { + const suggest = []; + + // prevent useless refactors + if (allowVoid) { + suggest.push({ + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer( + sourceCode, + node.body, + fixer, + ); + }, + }); + } + + // Do not suggest wrapping an unnamed FunctionExpression in braces as that would be invalid syntax. + if ( + !( + node.body.type === "FunctionExpression" && + !node.body.id + ) + ) { + suggest.push({ + messageId: "wrapBraces", + fix(fixer) { + return curlyWrapFixer(sourceCode, node, fixer); + }, + }); + } + + context.report({ + node: node.body, + messageId: "returnsValue", + suggest, + }); + } + }, + + onCodePathEnd() { + funcInfo = funcInfo.upper; + }, + + ReturnStatement(node) { + if (!(funcInfo.shouldCheck && node.argument)) { + return; + } + + // node is `return ` + if (!allowVoid) { + context.report({ node, messageId: "returnsValue" }); + return; + } + + if (expressionIsVoid(node.argument)) { + return; + } + + // allowVoid && !expressionIsVoid + context.report({ + node, + messageId: "returnsValue", + suggest: [ + { + messageId: "prependVoid", + fix(fixer) { + return voidPrependFixer( + sourceCode, + node.argument, + fixer, + ); + }, + }, + ], + }); + }, + }; + }, }; diff --git a/lib/rules/no-proto.js b/lib/rules/no-proto.js index 28320d5d5e5f..8eca7aeb9a35 100644 --- a/lib/rules/no-proto.js +++ b/lib/rules/no-proto.js @@ -17,32 +17,29 @@ const { getStaticPropertyName } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of the `__proto__` property", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-proto" - }, - - schema: [], - - messages: { - unexpectedProto: "The '__proto__' property is deprecated." - } - }, - - create(context) { - - return { - - MemberExpression(node) { - if (getStaticPropertyName(node) === "__proto__") { - context.report({ node, messageId: "unexpectedProto" }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow the use of the `__proto__` property", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-proto", + }, + + schema: [], + + messages: { + unexpectedProto: "The '__proto__' property is deprecated.", + }, + }, + + create(context) { + return { + MemberExpression(node) { + if (getStaticPropertyName(node) === "__proto__") { + context.report({ node, messageId: "unexpectedProto" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-prototype-builtins.js b/lib/rules/no-prototype-builtins.js index b61e585291a9..96aa0633d156 100644 --- a/lib/rules/no-prototype-builtins.js +++ b/lib/rules/no-prototype-builtins.js @@ -25,135 +25,157 @@ const astUtils = require("./utils/ast-utils"); * or the node itself is an optional call or member `?.`. */ function isAfterOptional(node) { - let leftNode; - - if (node.type === "MemberExpression") { - leftNode = node.object; - } else if (node.type === "CallExpression") { - leftNode = node.callee; - } else { - return false; - } - if (node.optional) { - return true; - } - return isAfterOptional(leftNode); + let leftNode; + + if (node.type === "MemberExpression") { + leftNode = node.object; + } else if (node.type === "CallExpression") { + leftNode = node.callee; + } else { + return false; + } + if (node.optional) { + return true; + } + return isAfterOptional(leftNode); } - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow calling some `Object.prototype` methods directly on objects", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-prototype-builtins" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - prototypeBuildIn: "Do not access Object.prototype method '{{prop}}' from target object.", - callObjectPrototype: "Call Object.prototype.{{prop}} explicitly." - } - }, - - create(context) { - const DISALLOWED_PROPS = new Set([ - "hasOwnProperty", - "isPrototypeOf", - "propertyIsEnumerable" - ]); - - /** - * Reports if a disallowed property is used in a CallExpression - * @param {ASTNode} node The CallExpression node. - * @returns {void} - */ - function disallowBuiltIns(node) { - - const callee = astUtils.skipChainExpression(node.callee); - - if (callee.type !== "MemberExpression") { - return; - } - - const propName = astUtils.getStaticPropertyName(callee); - - if (propName !== null && DISALLOWED_PROPS.has(propName)) { - context.report({ - messageId: "prototypeBuildIn", - loc: callee.property.loc, - data: { prop: propName }, - node, - suggest: [ - { - messageId: "callObjectPrototype", - data: { prop: propName }, - fix(fixer) { - const sourceCode = context.sourceCode; - - /* - * A call after an optional chain (e.g. a?.b.hasOwnProperty(c)) - * must be fixed manually because the call can be short-circuited - */ - if (isAfterOptional(node)) { - return null; - } - - /* - * A call on a ChainExpression (e.g. (a?.hasOwnProperty)(c)) will trigger - * no-unsafe-optional-chaining which should be fixed before this suggestion - */ - if (node.callee.type === "ChainExpression") { - return null; - } - - const objectVariable = astUtils.getVariableByName(sourceCode.getScope(node), "Object"); - - /* - * We can't use Object if the global Object was shadowed, - * or Object does not exist in the global scope for some reason - */ - if (!objectVariable || objectVariable.scope.type !== "global" || objectVariable.defs.length > 0) { - return null; - } - - let objectText = sourceCode.getText(callee.object); - - if (astUtils.getPrecedence(callee.object) <= astUtils.getPrecedence({ type: "SequenceExpression" })) { - objectText = `(${objectText})`; - } - - const openParenToken = sourceCode.getTokenAfter( - node.callee, - astUtils.isOpeningParenToken - ); - const isEmptyParameters = node.arguments.length === 0; - const delim = isEmptyParameters ? "" : ", "; - const fixes = [ - fixer.replaceText(callee, `Object.prototype.${propName}.call`), - fixer.insertTextAfter(openParenToken, objectText + delim) - ]; - - return fixes; - } - } - ] - }); - } - } - - return { - CallExpression: disallowBuiltIns - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow calling some `Object.prototype` methods directly on objects", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-prototype-builtins", + }, + + hasSuggestions: true, + + schema: [], + + messages: { + prototypeBuildIn: + "Do not access Object.prototype method '{{prop}}' from target object.", + callObjectPrototype: "Call Object.prototype.{{prop}} explicitly.", + }, + }, + + create(context) { + const DISALLOWED_PROPS = new Set([ + "hasOwnProperty", + "isPrototypeOf", + "propertyIsEnumerable", + ]); + + /** + * Reports if a disallowed property is used in a CallExpression + * @param {ASTNode} node The CallExpression node. + * @returns {void} + */ + function disallowBuiltIns(node) { + const callee = astUtils.skipChainExpression(node.callee); + + if (callee.type !== "MemberExpression") { + return; + } + + const propName = astUtils.getStaticPropertyName(callee); + + if (propName !== null && DISALLOWED_PROPS.has(propName)) { + context.report({ + messageId: "prototypeBuildIn", + loc: callee.property.loc, + data: { prop: propName }, + node, + suggest: [ + { + messageId: "callObjectPrototype", + data: { prop: propName }, + fix(fixer) { + const sourceCode = context.sourceCode; + + /* + * A call after an optional chain (e.g. a?.b.hasOwnProperty(c)) + * must be fixed manually because the call can be short-circuited + */ + if (isAfterOptional(node)) { + return null; + } + + /* + * A call on a ChainExpression (e.g. (a?.hasOwnProperty)(c)) will trigger + * no-unsafe-optional-chaining which should be fixed before this suggestion + */ + if (node.callee.type === "ChainExpression") { + return null; + } + + const objectVariable = + astUtils.getVariableByName( + sourceCode.getScope(node), + "Object", + ); + + /* + * We can't use Object if the global Object was shadowed, + * or Object does not exist in the global scope for some reason + */ + if ( + !objectVariable || + objectVariable.scope.type !== "global" || + objectVariable.defs.length > 0 + ) { + return null; + } + + let objectText = sourceCode.getText( + callee.object, + ); + + if ( + astUtils.getPrecedence(callee.object) <= + astUtils.getPrecedence({ + type: "SequenceExpression", + }) + ) { + objectText = `(${objectText})`; + } + + const openParenToken = sourceCode.getTokenAfter( + node.callee, + astUtils.isOpeningParenToken, + ); + const isEmptyParameters = + node.arguments.length === 0; + const delim = isEmptyParameters ? "" : ", "; + const fixes = [ + fixer.replaceText( + callee, + `Object.prototype.${propName}.call`, + ), + fixer.insertTextAfter( + openParenToken, + objectText + delim, + ), + ]; + + return fixes; + }, + }, + ], + }); + } + } + + return { + CallExpression: disallowBuiltIns, + }; + }, }; diff --git a/lib/rules/no-redeclare.js b/lib/rules/no-redeclare.js index 94a3c212472d..41df242f2079 100644 --- a/lib/rules/no-redeclare.js +++ b/lib/rules/no-redeclare.js @@ -17,155 +17,157 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ builtinGlobals: true }], - - docs: { - description: "Disallow variable redeclaration", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-redeclare" - }, - - messages: { - redeclared: "'{{id}}' is already defined.", - redeclaredAsBuiltin: "'{{id}}' is already defined as a built-in global variable.", - redeclaredBySyntax: "'{{id}}' is already defined by a variable declaration." - }, - - schema: [ - { - type: "object", - properties: { - builtinGlobals: { type: "boolean" } - }, - additionalProperties: false - } - ] - }, - - create(context) { - const [{ builtinGlobals }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Iterate declarations of a given variable. - * @param {escope.variable} variable The variable object to iterate declarations. - * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations. - */ - function *iterateDeclarations(variable) { - if (builtinGlobals && ( - variable.eslintImplicitGlobalSetting === "readonly" || - variable.eslintImplicitGlobalSetting === "writable" - )) { - yield { type: "builtin" }; - } - - for (const id of variable.identifiers) { - yield { type: "syntax", node: id, loc: id.loc }; - } - - if (variable.eslintExplicitGlobalComments) { - for (const comment of variable.eslintExplicitGlobalComments) { - yield { - type: "comment", - node: comment, - loc: astUtils.getNameLocationInGlobalDirectiveComment( - sourceCode, - comment, - variable.name - ) - }; - } - } - } - - /** - * Find variables in a given scope and flag redeclared ones. - * @param {Scope} scope An eslint-scope scope object. - * @returns {void} - * @private - */ - function findVariablesInScope(scope) { - for (const variable of scope.variables) { - const [ - declaration, - ...extraDeclarations - ] = iterateDeclarations(variable); - - if (extraDeclarations.length === 0) { - continue; - } - - /* - * If the type of a declaration is different from the type of - * the first declaration, it shows the location of the first - * declaration. - */ - const detailMessageId = declaration.type === "builtin" - ? "redeclaredAsBuiltin" - : "redeclaredBySyntax"; - const data = { id: variable.name }; - - // Report extra declarations. - for (const { type, node, loc } of extraDeclarations) { - const messageId = type === declaration.type - ? "redeclared" - : detailMessageId; - - context.report({ node, loc, messageId, data }); - } - } - } - - /** - * Find variables in the current scope. - * @param {ASTNode} node The node of the current scope. - * @returns {void} - * @private - */ - function checkForBlock(node) { - const scope = sourceCode.getScope(node); - - /* - * In ES5, some node type such as `BlockStatement` doesn't have that scope. - * `scope.block` is a different node in such a case. - */ - if (scope.block === node) { - findVariablesInScope(scope); - } - } - - return { - Program(node) { - const scope = sourceCode.getScope(node); - - findVariablesInScope(scope); - - // Node.js or ES modules has a special scope. - if ( - scope.type === "global" && - scope.childScopes[0] && - - // The special scope's block is the Program node. - scope.block === scope.childScopes[0].block - ) { - findVariablesInScope(scope.childScopes[0]); - } - }, - - FunctionDeclaration: checkForBlock, - FunctionExpression: checkForBlock, - ArrowFunctionExpression: checkForBlock, - - StaticBlock: checkForBlock, - - BlockStatement: checkForBlock, - ForStatement: checkForBlock, - ForInStatement: checkForBlock, - ForOfStatement: checkForBlock, - SwitchStatement: checkForBlock - }; - } + meta: { + type: "suggestion", + + defaultOptions: [{ builtinGlobals: true }], + + docs: { + description: "Disallow variable redeclaration", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-redeclare", + }, + + messages: { + redeclared: "'{{id}}' is already defined.", + redeclaredAsBuiltin: + "'{{id}}' is already defined as a built-in global variable.", + redeclaredBySyntax: + "'{{id}}' is already defined by a variable declaration.", + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const [{ builtinGlobals }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Iterate declarations of a given variable. + * @param {escope.variable} variable The variable object to iterate declarations. + * @returns {IterableIterator<{type:string,node:ASTNode,loc:SourceLocation}>} The declarations. + */ + function* iterateDeclarations(variable) { + if ( + builtinGlobals && + (variable.eslintImplicitGlobalSetting === "readonly" || + variable.eslintImplicitGlobalSetting === "writable") + ) { + yield { type: "builtin" }; + } + + for (const id of variable.identifiers) { + yield { type: "syntax", node: id, loc: id.loc }; + } + + if (variable.eslintExplicitGlobalComments) { + for (const comment of variable.eslintExplicitGlobalComments) { + yield { + type: "comment", + node: comment, + loc: astUtils.getNameLocationInGlobalDirectiveComment( + sourceCode, + comment, + variable.name, + ), + }; + } + } + } + + /** + * Find variables in a given scope and flag redeclared ones. + * @param {Scope} scope An eslint-scope scope object. + * @returns {void} + * @private + */ + function findVariablesInScope(scope) { + for (const variable of scope.variables) { + const [declaration, ...extraDeclarations] = + iterateDeclarations(variable); + + if (extraDeclarations.length === 0) { + continue; + } + + /* + * If the type of a declaration is different from the type of + * the first declaration, it shows the location of the first + * declaration. + */ + const detailMessageId = + declaration.type === "builtin" + ? "redeclaredAsBuiltin" + : "redeclaredBySyntax"; + const data = { id: variable.name }; + + // Report extra declarations. + for (const { type, node, loc } of extraDeclarations) { + const messageId = + type === declaration.type + ? "redeclared" + : detailMessageId; + + context.report({ node, loc, messageId, data }); + } + } + } + + /** + * Find variables in the current scope. + * @param {ASTNode} node The node of the current scope. + * @returns {void} + * @private + */ + function checkForBlock(node) { + const scope = sourceCode.getScope(node); + + /* + * In ES5, some node type such as `BlockStatement` doesn't have that scope. + * `scope.block` is a different node in such a case. + */ + if (scope.block === node) { + findVariablesInScope(scope); + } + } + + return { + Program(node) { + const scope = sourceCode.getScope(node); + + findVariablesInScope(scope); + + // Node.js or ES modules has a special scope. + if ( + scope.type === "global" && + scope.childScopes[0] && + // The special scope's block is the Program node. + scope.block === scope.childScopes[0].block + ) { + findVariablesInScope(scope.childScopes[0]); + } + }, + + FunctionDeclaration: checkForBlock, + FunctionExpression: checkForBlock, + ArrowFunctionExpression: checkForBlock, + + StaticBlock: checkForBlock, + + BlockStatement: checkForBlock, + ForStatement: checkForBlock, + ForInStatement: checkForBlock, + ForOfStatement: checkForBlock, + SwitchStatement: checkForBlock, + }; + }, }; diff --git a/lib/rules/no-regex-spaces.js b/lib/rules/no-regex-spaces.js index cb250107289a..f99539d825b1 100644 --- a/lib/rules/no-regex-spaces.js +++ b/lib/rules/no-regex-spaces.js @@ -26,7 +26,7 @@ const DOUBLE_SPACE = / {2}/u; * @private */ function isString(node) { - return node && node.type === "Literal" && typeof node.value === "string"; + return node && node.type === "Literal" && typeof node.value === "string"; } //------------------------------------------------------------------------------ @@ -35,163 +35,185 @@ function isString(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow multiple spaces in regular expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-regex-spaces" - }, - - schema: [], - fixable: "code", - - messages: { - multipleSpaces: "Spaces are hard to count. Use {{{length}}}." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Validate regular expression - * @param {ASTNode} nodeToReport Node to report. - * @param {string} pattern Regular expression pattern to validate. - * @param {string} rawPattern Raw representation of the pattern in the source code. - * @param {number} rawPatternStartRange Start range of the pattern in the source code. - * @param {string} flags Regular expression flags. - * @returns {void} - * @private - */ - function checkRegex(nodeToReport, pattern, rawPattern, rawPatternStartRange, flags) { - - // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ '). - if (!DOUBLE_SPACE.test(rawPattern)) { - return; - } - - const characterClassNodes = []; - let regExpAST; - - try { - regExpAST = regExpParser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); - } catch { - - // Ignore regular expressions with syntax errors - return; - } - - regexpp.visitRegExpAST(regExpAST, { - onCharacterClassEnter(ccNode) { - characterClassNodes.push(ccNode); - } - }); - - const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu; - let match; - - while ((match = spacesPattern.exec(pattern))) { - const { 1: { length }, index } = match; - - // Report only consecutive spaces that are not in character classes. - if ( - characterClassNodes.every(({ start, end }) => index < start || end <= index) - ) { - context.report({ - node: nodeToReport, - messageId: "multipleSpaces", - data: { length }, - fix(fixer) { - if (pattern !== rawPattern) { - return null; - } - return fixer.replaceTextRange( - [rawPatternStartRange + index, rawPatternStartRange + index + length], - ` {${length}}` - ); - } - }); - - // Report only the first occurrence of consecutive spaces - return; - } - } - } - - /** - * Validate regular expression literals - * @param {ASTNode} node node to validate - * @returns {void} - * @private - */ - function checkLiteral(node) { - if (node.regex) { - const pattern = node.regex.pattern; - const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/")); - const rawPatternStartRange = node.range[0] + 1; - const flags = node.regex.flags; - - checkRegex( - node, - pattern, - rawPattern, - rawPatternStartRange, - flags - ); - } - } - - /** - * Validate strings passed to the RegExp constructor - * @param {ASTNode} node node to validate - * @returns {void} - * @private - */ - function checkFunction(node) { - const scope = sourceCode.getScope(node); - const regExpVar = astUtils.getVariableByName(scope, "RegExp"); - const shadowed = regExpVar && regExpVar.defs.length > 0; - const patternNode = node.arguments[0]; - - if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(patternNode) && !shadowed) { - const pattern = patternNode.value; - const rawPattern = patternNode.raw.slice(1, -1); - const rawPatternStartRange = patternNode.range[0] + 1; - let flags; - - if (node.arguments.length < 2) { - - // It has no flags. - flags = ""; - } else { - const flagsNode = node.arguments[1]; - - if (isString(flagsNode)) { - flags = flagsNode.value; - } else { - - // The flags cannot be determined. - return; - } - } - - checkRegex( - node, - pattern, - rawPattern, - rawPatternStartRange, - flags - ); - } - } - - return { - Literal: checkLiteral, - CallExpression: checkFunction, - NewExpression: checkFunction - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow multiple spaces in regular expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-regex-spaces", + }, + + schema: [], + fixable: "code", + + messages: { + multipleSpaces: "Spaces are hard to count. Use {{{length}}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Validate regular expression + * @param {ASTNode} nodeToReport Node to report. + * @param {string} pattern Regular expression pattern to validate. + * @param {string} rawPattern Raw representation of the pattern in the source code. + * @param {number} rawPatternStartRange Start range of the pattern in the source code. + * @param {string} flags Regular expression flags. + * @returns {void} + * @private + */ + function checkRegex( + nodeToReport, + pattern, + rawPattern, + rawPatternStartRange, + flags, + ) { + // Skip if there are no consecutive spaces in the source code, to avoid reporting e.g., RegExp(' \ '). + if (!DOUBLE_SPACE.test(rawPattern)) { + return; + } + + const characterClassNodes = []; + let regExpAST; + + try { + regExpAST = regExpParser.parsePattern( + pattern, + 0, + pattern.length, + { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v"), + }, + ); + } catch { + // Ignore regular expressions with syntax errors + return; + } + + regexpp.visitRegExpAST(regExpAST, { + onCharacterClassEnter(ccNode) { + characterClassNodes.push(ccNode); + }, + }); + + const spacesPattern = /( {2,})(?: [+*{?]|[^+*{?]|$)/gu; + let match; + + while ((match = spacesPattern.exec(pattern))) { + const { + 1: { length }, + index, + } = match; + + // Report only consecutive spaces that are not in character classes. + if ( + characterClassNodes.every( + ({ start, end }) => index < start || end <= index, + ) + ) { + context.report({ + node: nodeToReport, + messageId: "multipleSpaces", + data: { length }, + fix(fixer) { + if (pattern !== rawPattern) { + return null; + } + return fixer.replaceTextRange( + [ + rawPatternStartRange + index, + rawPatternStartRange + index + length, + ], + ` {${length}}`, + ); + }, + }); + + // Report only the first occurrence of consecutive spaces + return; + } + } + } + + /** + * Validate regular expression literals + * @param {ASTNode} node node to validate + * @returns {void} + * @private + */ + function checkLiteral(node) { + if (node.regex) { + const pattern = node.regex.pattern; + const rawPattern = node.raw.slice(1, node.raw.lastIndexOf("/")); + const rawPatternStartRange = node.range[0] + 1; + const flags = node.regex.flags; + + checkRegex( + node, + pattern, + rawPattern, + rawPatternStartRange, + flags, + ); + } + } + + /** + * Validate strings passed to the RegExp constructor + * @param {ASTNode} node node to validate + * @returns {void} + * @private + */ + function checkFunction(node) { + const scope = sourceCode.getScope(node); + const regExpVar = astUtils.getVariableByName(scope, "RegExp"); + const shadowed = regExpVar && regExpVar.defs.length > 0; + const patternNode = node.arguments[0]; + + if ( + node.callee.type === "Identifier" && + node.callee.name === "RegExp" && + isString(patternNode) && + !shadowed + ) { + const pattern = patternNode.value; + const rawPattern = patternNode.raw.slice(1, -1); + const rawPatternStartRange = patternNode.range[0] + 1; + let flags; + + if (node.arguments.length < 2) { + // It has no flags. + flags = ""; + } else { + const flagsNode = node.arguments[1]; + + if (isString(flagsNode)) { + flags = flagsNode.value; + } else { + // The flags cannot be determined. + return; + } + } + + checkRegex( + node, + pattern, + rawPattern, + rawPatternStartRange, + flags, + ); + } + } + + return { + Literal: checkLiteral, + CallExpression: checkFunction, + NewExpression: checkFunction, + }; + }, }; diff --git a/lib/rules/no-restricted-exports.js b/lib/rules/no-restricted-exports.js index 8da2f2dfe01c..1f90f2e401ae 100644 --- a/lib/rules/no-restricted-exports.js +++ b/lib/rules/no-restricted-exports.js @@ -17,188 +17,211 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow specified names in exports", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-exports" - }, - - schema: [{ - anyOf: [ - { - type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - }, - restrictedNamedExportsPattern: { type: "string" } - }, - additionalProperties: false - }, - { - type: "object", - properties: { - restrictedNamedExports: { - type: "array", - items: { - type: "string", - pattern: "^(?!default$)" - }, - uniqueItems: true - }, - restrictedNamedExportsPattern: { type: "string" }, - restrictDefaultExports: { - type: "object", - properties: { - - // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format - direct: { - type: "boolean" - }, - - // Allow/Disallow `export { foo as default };` declarations - named: { - type: "boolean" - }, - - // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations - defaultFrom: { - type: "boolean" - }, - - // Allow/Disallow `export { foo as default } from "mod";` declarations - namedFrom: { - type: "boolean" - }, - - // Allow/Disallow `export * as default from "mod"`; declarations - namespaceFrom: { - type: "boolean" - } - }, - additionalProperties: false - } - }, - additionalProperties: false - } - ] - }], - - messages: { - restrictedNamed: "'{{name}}' is restricted from being used as an exported name.", - restrictedDefault: "Exporting 'default' is restricted." - } - }, - - create(context) { - - const restrictedNames = new Set(context.options[0] && context.options[0].restrictedNamedExports); - const restrictedNamePattern = context.options[0] && context.options[0].restrictedNamedExportsPattern; - const restrictDefaultExports = context.options[0] && context.options[0].restrictDefaultExports; - const sourceCode = context.sourceCode; - - /** - * Checks and reports given exported name. - * @param {ASTNode} node exported `Identifier` or string `Literal` node to check. - * @returns {void} - */ - function checkExportedName(node) { - const name = astUtils.getModuleExportName(node); - - let matchesRestrictedNamePattern = false; - - if (restrictedNamePattern && name !== "default") { - const patternRegex = new RegExp(restrictedNamePattern, "u"); - - matchesRestrictedNamePattern = patternRegex.test(name); - } - - if (matchesRestrictedNamePattern || restrictedNames.has(name)) { - context.report({ - node, - messageId: "restrictedNamed", - data: { name } - }); - return; - } - - if (name === "default") { - if (node.parent.type === "ExportAllDeclaration") { - if (restrictDefaultExports && restrictDefaultExports.namespaceFrom) { - context.report({ - node, - messageId: "restrictedDefault" - }); - } - - } else { // ExportSpecifier - const isSourceSpecified = !!node.parent.parent.source; - const specifierLocalName = astUtils.getModuleExportName(node.parent.local); - - if (!isSourceSpecified && restrictDefaultExports && restrictDefaultExports.named) { - context.report({ - node, - messageId: "restrictedDefault" - }); - return; - } - - if (isSourceSpecified && restrictDefaultExports) { - if ( - (specifierLocalName === "default" && restrictDefaultExports.defaultFrom) || - (specifierLocalName !== "default" && restrictDefaultExports.namedFrom) - ) { - context.report({ - node, - messageId: "restrictedDefault" - }); - } - } - } - } - } - - return { - ExportAllDeclaration(node) { - if (node.exported) { - checkExportedName(node.exported); - } - }, - - ExportDefaultDeclaration(node) { - if (restrictDefaultExports && restrictDefaultExports.direct) { - context.report({ - node, - messageId: "restrictedDefault" - }); - } - }, - - ExportNamedDeclaration(node) { - const declaration = node.declaration; - - if (declaration) { - if (declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration") { - checkExportedName(declaration.id); - } else if (declaration.type === "VariableDeclaration") { - sourceCode.getDeclaredVariables(declaration) - .map(v => v.defs.find(d => d.parent === declaration)) - .map(d => d.name) // Identifier nodes - .forEach(checkExportedName); - } - } else { - node.specifiers - .map(s => s.exported) - .forEach(checkExportedName); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow specified names in exports", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-exports", + }, + + schema: [ + { + anyOf: [ + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + restrictedNamedExportsPattern: { type: "string" }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + restrictedNamedExports: { + type: "array", + items: { + type: "string", + pattern: "^(?!default$)", + }, + uniqueItems: true, + }, + restrictedNamedExportsPattern: { type: "string" }, + restrictDefaultExports: { + type: "object", + properties: { + // Allow/Disallow `export default foo; export default 42; export default function foo() {}` format + direct: { + type: "boolean", + }, + + // Allow/Disallow `export { foo as default };` declarations + named: { + type: "boolean", + }, + + // Allow/Disallow `export { default } from "mod"; export { default as default } from "mod";` declarations + defaultFrom: { + type: "boolean", + }, + + // Allow/Disallow `export { foo as default } from "mod";` declarations + namedFrom: { + type: "boolean", + }, + + // Allow/Disallow `export * as default from "mod"`; declarations + namespaceFrom: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + restrictedNamed: + "'{{name}}' is restricted from being used as an exported name.", + restrictedDefault: "Exporting 'default' is restricted.", + }, + }, + + create(context) { + const restrictedNames = new Set( + context.options[0] && context.options[0].restrictedNamedExports, + ); + const restrictedNamePattern = + context.options[0] && + context.options[0].restrictedNamedExportsPattern; + const restrictDefaultExports = + context.options[0] && context.options[0].restrictDefaultExports; + const sourceCode = context.sourceCode; + + /** + * Checks and reports given exported name. + * @param {ASTNode} node exported `Identifier` or string `Literal` node to check. + * @returns {void} + */ + function checkExportedName(node) { + const name = astUtils.getModuleExportName(node); + + let matchesRestrictedNamePattern = false; + + if (restrictedNamePattern && name !== "default") { + const patternRegex = new RegExp(restrictedNamePattern, "u"); + + matchesRestrictedNamePattern = patternRegex.test(name); + } + + if (matchesRestrictedNamePattern || restrictedNames.has(name)) { + context.report({ + node, + messageId: "restrictedNamed", + data: { name }, + }); + return; + } + + if (name === "default") { + if (node.parent.type === "ExportAllDeclaration") { + if ( + restrictDefaultExports && + restrictDefaultExports.namespaceFrom + ) { + context.report({ + node, + messageId: "restrictedDefault", + }); + } + } else { + // ExportSpecifier + const isSourceSpecified = !!node.parent.parent.source; + const specifierLocalName = astUtils.getModuleExportName( + node.parent.local, + ); + + if ( + !isSourceSpecified && + restrictDefaultExports && + restrictDefaultExports.named + ) { + context.report({ + node, + messageId: "restrictedDefault", + }); + return; + } + + if (isSourceSpecified && restrictDefaultExports) { + if ( + (specifierLocalName === "default" && + restrictDefaultExports.defaultFrom) || + (specifierLocalName !== "default" && + restrictDefaultExports.namedFrom) + ) { + context.report({ + node, + messageId: "restrictedDefault", + }); + } + } + } + } + } + + return { + ExportAllDeclaration(node) { + if (node.exported) { + checkExportedName(node.exported); + } + }, + + ExportDefaultDeclaration(node) { + if (restrictDefaultExports && restrictDefaultExports.direct) { + context.report({ + node, + messageId: "restrictedDefault", + }); + } + }, + + ExportNamedDeclaration(node) { + const declaration = node.declaration; + + if (declaration) { + if ( + declaration.type === "FunctionDeclaration" || + declaration.type === "ClassDeclaration" + ) { + checkExportedName(declaration.id); + } else if (declaration.type === "VariableDeclaration") { + sourceCode + .getDeclaredVariables(declaration) + .map(v => + v.defs.find(d => d.parent === declaration), + ) + .map(d => d.name) // Identifier nodes + .forEach(checkExportedName); + } + } else { + node.specifiers + .map(s => s.exported) + .forEach(checkExportedName); + } + }, + }; + }, }; diff --git a/lib/rules/no-restricted-globals.js b/lib/rules/no-restricted-globals.js index 060d50a04371..05d8fb5bfe5e 100644 --- a/lib/rules/no-restricted-globals.js +++ b/lib/rules/no-restricted-globals.js @@ -10,115 +10,114 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow specified global variables", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-globals" - }, - - schema: { - type: "array", - items: { - oneOf: [ - { - type: "string" - }, - { - type: "object", - properties: { - name: { type: "string" }, - message: { type: "string" } - }, - required: ["name"], - additionalProperties: false - } - ] - }, - uniqueItems: true, - minItems: 0 - }, - - messages: { - defaultMessage: "Unexpected use of '{{name}}'.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - customMessage: "Unexpected use of '{{name}}'. {{customMessage}}" - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - // If no globals are restricted, we don't need to do anything - if (context.options.length === 0) { - return {}; - } - - const restrictedGlobalMessages = context.options.reduce((memo, option) => { - if (typeof option === "string") { - memo[option] = null; - } else { - memo[option.name] = option.message; - } - - return memo; - }, {}); - - /** - * Report a variable to be used as a restricted global. - * @param {Reference} reference the variable reference - * @returns {void} - * @private - */ - function reportReference(reference) { - const name = reference.identifier.name, - customMessage = restrictedGlobalMessages[name], - messageId = customMessage - ? "customMessage" - : "defaultMessage"; - - context.report({ - node: reference.identifier, - messageId, - data: { - name, - customMessage - } - }); - } - - /** - * Check if the given name is a restricted global name. - * @param {string} name name of a variable - * @returns {boolean} whether the variable is a restricted global or not - * @private - */ - function isRestricted(name) { - return Object.hasOwn(restrictedGlobalMessages, name); - } - - return { - Program(node) { - const scope = sourceCode.getScope(node); - - // Report variables declared elsewhere (ex: variables defined as "global" by eslint) - scope.variables.forEach(variable => { - if (!variable.defs.length && isRestricted(variable.name)) { - variable.references.forEach(reportReference); - } - }); - - // Report variables not declared at all - scope.through.forEach(reference => { - if (isRestricted(reference.identifier.name)) { - reportReference(reference); - } - }); - - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow specified global variables", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-globals", + }, + + schema: { + type: "array", + items: { + oneOf: [ + { + type: "string", + }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { type: "string" }, + }, + required: ["name"], + additionalProperties: false, + }, + ], + }, + uniqueItems: true, + minItems: 0, + }, + + messages: { + defaultMessage: "Unexpected use of '{{name}}'.", + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + customMessage: "Unexpected use of '{{name}}'. {{customMessage}}", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + // If no globals are restricted, we don't need to do anything + if (context.options.length === 0) { + return {}; + } + + const restrictedGlobalMessages = context.options.reduce( + (memo, option) => { + if (typeof option === "string") { + memo[option] = null; + } else { + memo[option.name] = option.message; + } + + return memo; + }, + {}, + ); + + /** + * Report a variable to be used as a restricted global. + * @param {Reference} reference the variable reference + * @returns {void} + * @private + */ + function reportReference(reference) { + const name = reference.identifier.name, + customMessage = restrictedGlobalMessages[name], + messageId = customMessage ? "customMessage" : "defaultMessage"; + + context.report({ + node: reference.identifier, + messageId, + data: { + name, + customMessage, + }, + }); + } + + /** + * Check if the given name is a restricted global name. + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted global or not + * @private + */ + function isRestricted(name) { + return Object.hasOwn(restrictedGlobalMessages, name); + } + + return { + Program(node) { + const scope = sourceCode.getScope(node); + + // Report variables declared elsewhere (ex: variables defined as "global" by eslint) + scope.variables.forEach(variable => { + if (!variable.defs.length && isRestricted(variable.name)) { + variable.references.forEach(reportReference); + } + }); + + // Report variables not declared at all + scope.through.forEach(reference => { + if (isRestricted(reference.identifier.name)) { + reportReference(reference); + } + }); + }, + }; + }, }; diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js index 5fd4744c0b0b..e493985ce205 100644 --- a/lib/rules/no-restricted-imports.js +++ b/lib/rules/no-restricted-imports.js @@ -17,547 +17,667 @@ const astUtils = require("./utils/ast-utils"); const ignore = require("ignore"); const arrayOfStringsOrObjects = { - type: "array", - items: { - anyOf: [ - { type: "string" }, - { - type: "object", - properties: { - name: { type: "string" }, - message: { - type: "string", - minLength: 1 - }, - importNames: { - type: "array", - items: { - type: "string" - } - }, - allowImportNames: { - type: "array", - items: { - type: "string" - } - } - }, - additionalProperties: false, - required: ["name"], - not: { required: ["importNames", "allowImportNames"] } - } - ] - }, - uniqueItems: true + type: "array", + items: { + anyOf: [ + { type: "string" }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { + type: "string", + minLength: 1, + }, + importNames: { + type: "array", + items: { + type: "string", + }, + }, + allowImportNames: { + type: "array", + items: { + type: "string", + }, + }, + }, + additionalProperties: false, + required: ["name"], + not: { required: ["importNames", "allowImportNames"] }, + }, + ], + }, + uniqueItems: true, }; const arrayOfStringsOrObjectPatterns = { - anyOf: [ - { - type: "array", - items: { - type: "string" - }, - uniqueItems: true - }, - { - type: "array", - items: { - type: "object", - properties: { - importNames: { - type: "array", - items: { - type: "string" - }, - minItems: 1, - uniqueItems: true - }, - allowImportNames: { - type: "array", - items: { - type: "string" - }, - minItems: 1, - uniqueItems: true - }, - group: { - type: "array", - items: { - type: "string" - }, - minItems: 1, - uniqueItems: true - }, - regex: { - type: "string" - }, - importNamePattern: { - type: "string" - }, - allowImportNamePattern: { - type: "string" - }, - message: { - type: "string", - minLength: 1 - }, - caseSensitive: { - type: "boolean" - } - }, - additionalProperties: false, - not: { - anyOf: [ - { required: ["importNames", "allowImportNames"] }, - { required: ["importNamePattern", "allowImportNamePattern"] }, - { required: ["importNames", "allowImportNamePattern"] }, - { required: ["importNamePattern", "allowImportNames"] }, - { required: ["allowImportNames", "allowImportNamePattern"] } - ] - }, - oneOf: [ - { required: ["group"] }, - { required: ["regex"] } - ] - }, - uniqueItems: true - } - ] + anyOf: [ + { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + }, + { + type: "array", + items: { + type: "object", + properties: { + importNames: { + type: "array", + items: { + type: "string", + }, + minItems: 1, + uniqueItems: true, + }, + allowImportNames: { + type: "array", + items: { + type: "string", + }, + minItems: 1, + uniqueItems: true, + }, + group: { + type: "array", + items: { + type: "string", + }, + minItems: 1, + uniqueItems: true, + }, + regex: { + type: "string", + }, + importNamePattern: { + type: "string", + }, + allowImportNamePattern: { + type: "string", + }, + message: { + type: "string", + minLength: 1, + }, + caseSensitive: { + type: "boolean", + }, + }, + additionalProperties: false, + not: { + anyOf: [ + { required: ["importNames", "allowImportNames"] }, + { + required: [ + "importNamePattern", + "allowImportNamePattern", + ], + }, + { required: ["importNames", "allowImportNamePattern"] }, + { required: ["importNamePattern", "allowImportNames"] }, + { + required: [ + "allowImportNames", + "allowImportNamePattern", + ], + }, + ], + }, + oneOf: [{ required: ["group"] }, { required: ["regex"] }], + }, + uniqueItems: true, + }, + ], }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow specified modules when loaded by `import`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-imports" - }, - - messages: { - path: "'{{importSource}}' import is restricted from being used.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - pathWithCustomMessage: "'{{importSource}}' import is restricted from being used. {{customMessage}}", - - patterns: "'{{importSource}}' import is restricted from being used by a pattern.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - patternWithCustomMessage: "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}", - - patternAndImportName: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - patternAndImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", - - patternAndEverything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.", - - patternAndEverythingWithRegexImportName: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - patternAndEverythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - patternAndEverythingWithRegexImportNameAndCustomMessage: "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}", - - everything: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - everythingWithCustomMessage: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", - - importName: "'{{importName}}' import from '{{importSource}}' is restricted.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - importNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}", - - allowedImportName: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - allowedImportNameWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed. {{customMessage}}", - - everythingWithAllowImportNames: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - everythingWithAllowImportNamesAndCustomMessage: "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed. {{customMessage}}", - - allowedImportNamePattern: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - allowedImportNamePatternWithCustomMessage: "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'. {{customMessage}}", - - everythingWithAllowedImportNamePattern: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - everythingWithAllowedImportNamePatternWithCustomMessage: "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed. {{customMessage}}" - }, - - schema: { - anyOf: [ - arrayOfStringsOrObjects, - { - type: "array", - items: [{ - type: "object", - properties: { - paths: arrayOfStringsOrObjects, - patterns: arrayOfStringsOrObjectPatterns - }, - additionalProperties: false - }], - additionalItems: false - } - ] - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const options = Array.isArray(context.options) ? context.options : []; - const isPathAndPatternsObject = - typeof options[0] === "object" && - (Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns")); - - const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; - const groupedRestrictedPaths = restrictedPaths.reduce((memo, importSource) => { - const path = typeof importSource === "string" - ? importSource - : importSource.name; - - if (!memo[path]) { - memo[path] = []; - } - - if (typeof importSource === "string") { - memo[path].push({}); - } else { - memo[path].push({ - message: importSource.message, - importNames: importSource.importNames, - allowImportNames: importSource.allowImportNames - }); - } - return memo; - }, Object.create(null)); - - // Handle patterns too, either as strings or groups - let restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; - - // standardize to array of objects if we have an array of strings - if (restrictedPatterns.length > 0 && typeof restrictedPatterns[0] === "string") { - restrictedPatterns = [{ group: restrictedPatterns }]; - } - - // relative paths are supported for this rule - const restrictedPatternGroups = restrictedPatterns.map( - ({ group, regex, message, caseSensitive, importNames, importNamePattern, allowImportNames, allowImportNamePattern }) => ( - { - ...(group ? { matcher: ignore({ allowRelativePaths: true, ignorecase: !caseSensitive }).add(group) } : {}), - ...(typeof regex === "string" ? { regexMatcher: new RegExp(regex, caseSensitive ? "u" : "iu") } : {}), - customMessage: message, - importNames, - importNamePattern, - allowImportNames, - allowImportNamePattern - } - ) - ); - - // if no imports are restricted we don't need to check - if (Object.keys(restrictedPaths).length === 0 && restrictedPatternGroups.length === 0) { - return {}; - } - - /** - * Report a restricted path. - * @param {string} importSource path of the import - * @param {Map} importNames Map of import names that are being imported - * @param {node} node representing the restricted path reference - * @returns {void} - * @private - */ - function checkRestrictedPathAndReport(importSource, importNames, node) { - if (!Object.hasOwn(groupedRestrictedPaths, importSource)) { - return; - } - - groupedRestrictedPaths[importSource].forEach(restrictedPathEntry => { - const customMessage = restrictedPathEntry.message; - const restrictedImportNames = restrictedPathEntry.importNames; - const allowedImportNames = restrictedPathEntry.allowImportNames; - - if (!restrictedImportNames && !allowedImportNames) { - context.report({ - node, - messageId: customMessage ? "pathWithCustomMessage" : "path", - data: { - importSource, - customMessage - } - }); - - return; - } - - importNames.forEach((specifiers, importName) => { - if (importName === "*") { - const [specifier] = specifiers; - - if (restrictedImportNames) { - context.report({ - node, - messageId: customMessage ? "everythingWithCustomMessage" : "everything", - loc: specifier.loc, - data: { - importSource, - importNames: restrictedImportNames, - customMessage - } - }); - } else if (allowedImportNames) { - context.report({ - node, - messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames", - loc: specifier.loc, - data: { - importSource, - allowedImportNames, - customMessage - } - }); - } - - return; - } - - if (restrictedImportNames && restrictedImportNames.includes(importName)) { - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "importNameWithCustomMessage" : "importName", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName - } - }); - }); - } - - if (allowedImportNames && !allowedImportNames.includes(importName)) { - specifiers.forEach(specifier => { - context.report({ - node, - loc: specifier.loc, - messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName", - data: { - importSource, - customMessage, - importName, - allowedImportNames - } - }); - }); - } - }); - }); - } - - /** - * Report a restricted path specifically for patterns. - * @param {node} node representing the restricted path reference - * @param {Object} group contains an Ignore instance for paths, the customMessage to show on failure, - * and any restricted import names that have been specified in the config - * @param {Map} importNames Map of import names that are being imported - * @returns {void} - * @private - */ - function reportPathForPatterns(node, group, importNames) { - const importSource = node.source.value.trim(); - - const customMessage = group.customMessage; - const restrictedImportNames = group.importNames; - const restrictedImportNamePattern = group.importNamePattern ? new RegExp(group.importNamePattern, "u") : null; - const allowedImportNames = group.allowImportNames; - const allowedImportNamePattern = group.allowImportNamePattern ? new RegExp(group.allowImportNamePattern, "u") : null; - - /** - * If we are not restricting to any specific import names and just the pattern itself, - * report the error and move on - */ - if (!restrictedImportNames && !allowedImportNames && !restrictedImportNamePattern && !allowedImportNamePattern) { - context.report({ - node, - messageId: customMessage ? "patternWithCustomMessage" : "patterns", - data: { - importSource, - customMessage - } - }); - return; - } - - importNames.forEach((specifiers, importName) => { - if (importName === "*") { - const [specifier] = specifiers; - - if (restrictedImportNames) { - context.report({ - node, - messageId: customMessage ? "patternAndEverythingWithCustomMessage" : "patternAndEverything", - loc: specifier.loc, - data: { - importSource, - importNames: restrictedImportNames, - customMessage - } - }); - } else if (allowedImportNames) { - context.report({ - node, - messageId: customMessage ? "everythingWithAllowImportNamesAndCustomMessage" : "everythingWithAllowImportNames", - loc: specifier.loc, - data: { - importSource, - allowedImportNames, - customMessage - } - }); - } else if (allowedImportNamePattern) { - context.report({ - node, - messageId: customMessage ? "everythingWithAllowedImportNamePatternWithCustomMessage" : "everythingWithAllowedImportNamePattern", - loc: specifier.loc, - data: { - importSource, - allowedImportNamePattern, - customMessage - } - }); - } else { - context.report({ - node, - messageId: customMessage ? "patternAndEverythingWithRegexImportNameAndCustomMessage" : "patternAndEverythingWithRegexImportName", - loc: specifier.loc, - data: { - importSource, - importNames: restrictedImportNamePattern, - customMessage - } - }); - } - - return; - } - - if ( - (restrictedImportNames && restrictedImportNames.includes(importName)) || - (restrictedImportNamePattern && restrictedImportNamePattern.test(importName)) - ) { - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "patternAndImportNameWithCustomMessage" : "patternAndImportName", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName - } - }); - }); - } - - if (allowedImportNames && !allowedImportNames.includes(importName)) { - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "allowedImportNameWithCustomMessage" : "allowedImportName", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName, - allowedImportNames - } - }); - }); - } else if (allowedImportNamePattern && !allowedImportNamePattern.test(importName)) { - specifiers.forEach(specifier => { - context.report({ - node, - messageId: customMessage ? "allowedImportNamePatternWithCustomMessage" : "allowedImportNamePattern", - loc: specifier.loc, - data: { - importSource, - customMessage, - importName, - allowedImportNamePattern - } - }); - }); - } - }); - } - - /** - * Check if the given importSource is restricted by a pattern. - * @param {string} importSource path of the import - * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails - * @returns {boolean} whether the variable is a restricted pattern or not - * @private - */ - function isRestrictedPattern(importSource, group) { - return group.regexMatcher ? group.regexMatcher.test(importSource) : group.matcher.ignores(importSource); - } - - /** - * Checks a node to see if any problems should be reported. - * @param {ASTNode} node The node to check. - * @returns {void} - * @private - */ - function checkNode(node) { - const importSource = node.source.value.trim(); - const importNames = new Map(); - - if (node.type === "ExportAllDeclaration") { - const starToken = sourceCode.getFirstToken(node, 1); - - importNames.set("*", [{ loc: starToken.loc }]); - } else if (node.specifiers) { - for (const specifier of node.specifiers) { - let name; - const specifierData = { loc: specifier.loc }; - - if (specifier.type === "ImportDefaultSpecifier") { - name = "default"; - } else if (specifier.type === "ImportNamespaceSpecifier") { - name = "*"; - } else if (specifier.imported) { - name = astUtils.getModuleExportName(specifier.imported); - } else if (specifier.local) { - name = astUtils.getModuleExportName(specifier.local); - } - - if (typeof name === "string") { - if (importNames.has(name)) { - importNames.get(name).push(specifierData); - } else { - importNames.set(name, [specifierData]); - } - } - } - } - - checkRestrictedPathAndReport(importSource, importNames, node); - restrictedPatternGroups.forEach(group => { - if (isRestrictedPattern(importSource, group)) { - reportPathForPatterns(node, group, importNames); - } - }); - } - - return { - ImportDeclaration: checkNode, - ExportNamedDeclaration(node) { - if (node.source) { - checkNode(node); - } - }, - ExportAllDeclaration: checkNode - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow specified modules when loaded by `import`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-imports", + }, + + messages: { + path: "'{{importSource}}' import is restricted from being used.", + pathWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importSource}}' import is restricted from being used. {{customMessage}}", + + patterns: + "'{{importSource}}' import is restricted from being used by a pattern.", + patternWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importSource}}' import is restricted from being used by a pattern. {{customMessage}}", + + patternAndImportName: + "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern.", + patternAndImportNameWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importName}}' import from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", + + patternAndEverything: + "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern.", + + patternAndEverythingWithRegexImportName: + "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used.", + patternAndEverythingWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted from being used by a pattern. {{customMessage}}", + patternAndEverythingWithRegexImportNameAndCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "* import is invalid because import name matching '{{importNames}}' pattern from '{{importSource}}' is restricted from being used. {{customMessage}}", + + everything: + "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.", + everythingWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted. {{customMessage}}", + + importName: + "'{{importName}}' import from '{{importSource}}' is restricted.", + importNameWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importName}}' import from '{{importSource}}' is restricted. {{customMessage}}", + + allowedImportName: + "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed.", + allowedImportNameWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importName}}' import from '{{importSource}}' is restricted because only '{{allowedImportNames}}' import(s) is/are allowed. {{customMessage}}", + + everythingWithAllowImportNames: + "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed.", + everythingWithAllowImportNamesAndCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "* import is invalid because only '{{allowedImportNames}}' from '{{importSource}}' is/are allowed. {{customMessage}}", + + allowedImportNamePattern: + "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'.", + allowedImportNamePatternWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{importName}}' import from '{{importSource}}' is restricted because only imports that match the pattern '{{allowedImportNamePattern}}' are allowed from '{{importSource}}'. {{customMessage}}", + + everythingWithAllowedImportNamePattern: + "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed.", + everythingWithAllowedImportNamePatternWithCustomMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "* import is invalid because only imports that match the pattern '{{allowedImportNamePattern}}' from '{{importSource}}' are allowed. {{customMessage}}", + }, + + schema: { + anyOf: [ + arrayOfStringsOrObjects, + { + type: "array", + items: [ + { + type: "object", + properties: { + paths: arrayOfStringsOrObjects, + patterns: arrayOfStringsOrObjectPatterns, + }, + additionalProperties: false, + }, + ], + additionalItems: false, + }, + ], + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const options = Array.isArray(context.options) ? context.options : []; + const isPathAndPatternsObject = + typeof options[0] === "object" && + (Object.hasOwn(options[0], "paths") || + Object.hasOwn(options[0], "patterns")); + + const restrictedPaths = + (isPathAndPatternsObject ? options[0].paths : context.options) || + []; + const groupedRestrictedPaths = restrictedPaths.reduce( + (memo, importSource) => { + const path = + typeof importSource === "string" + ? importSource + : importSource.name; + + if (!memo[path]) { + memo[path] = []; + } + + if (typeof importSource === "string") { + memo[path].push({}); + } else { + memo[path].push({ + message: importSource.message, + importNames: importSource.importNames, + allowImportNames: importSource.allowImportNames, + }); + } + return memo; + }, + Object.create(null), + ); + + // Handle patterns too, either as strings or groups + let restrictedPatterns = + (isPathAndPatternsObject ? options[0].patterns : []) || []; + + // standardize to array of objects if we have an array of strings + if ( + restrictedPatterns.length > 0 && + typeof restrictedPatterns[0] === "string" + ) { + restrictedPatterns = [{ group: restrictedPatterns }]; + } + + // relative paths are supported for this rule + const restrictedPatternGroups = restrictedPatterns.map( + ({ + group, + regex, + message, + caseSensitive, + importNames, + importNamePattern, + allowImportNames, + allowImportNamePattern, + }) => ({ + ...(group + ? { + matcher: ignore({ + allowRelativePaths: true, + ignorecase: !caseSensitive, + }).add(group), + } + : {}), + ...(typeof regex === "string" + ? { + regexMatcher: new RegExp( + regex, + caseSensitive ? "u" : "iu", + ), + } + : {}), + customMessage: message, + importNames, + importNamePattern, + allowImportNames, + allowImportNamePattern, + }), + ); + + // if no imports are restricted we don't need to check + if ( + Object.keys(restrictedPaths).length === 0 && + restrictedPatternGroups.length === 0 + ) { + return {}; + } + + /** + * Report a restricted path. + * @param {string} importSource path of the import + * @param {Map} importNames Map of import names that are being imported + * @param {node} node representing the restricted path reference + * @returns {void} + * @private + */ + function checkRestrictedPathAndReport(importSource, importNames, node) { + if (!Object.hasOwn(groupedRestrictedPaths, importSource)) { + return; + } + + groupedRestrictedPaths[importSource].forEach( + restrictedPathEntry => { + const customMessage = restrictedPathEntry.message; + const restrictedImportNames = + restrictedPathEntry.importNames; + const allowedImportNames = + restrictedPathEntry.allowImportNames; + + if (!restrictedImportNames && !allowedImportNames) { + context.report({ + node, + messageId: customMessage + ? "pathWithCustomMessage" + : "path", + data: { + importSource, + customMessage, + }, + }); + + return; + } + + importNames.forEach((specifiers, importName) => { + if (importName === "*") { + const [specifier] = specifiers; + + if (restrictedImportNames) { + context.report({ + node, + messageId: customMessage + ? "everythingWithCustomMessage" + : "everything", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage, + }, + }); + } else if (allowedImportNames) { + context.report({ + node, + messageId: customMessage + ? "everythingWithAllowImportNamesAndCustomMessage" + : "everythingWithAllowImportNames", + loc: specifier.loc, + data: { + importSource, + allowedImportNames, + customMessage, + }, + }); + } + + return; + } + + if ( + restrictedImportNames && + restrictedImportNames.includes(importName) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage + ? "importNameWithCustomMessage" + : "importName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + }, + }); + }); + } + + if ( + allowedImportNames && + !allowedImportNames.includes(importName) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + loc: specifier.loc, + messageId: customMessage + ? "allowedImportNameWithCustomMessage" + : "allowedImportName", + data: { + importSource, + customMessage, + importName, + allowedImportNames, + }, + }); + }); + } + }); + }, + ); + } + + /** + * Report a restricted path specifically for patterns. + * @param {node} node representing the restricted path reference + * @param {Object} group contains an Ignore instance for paths, the customMessage to show on failure, + * and any restricted import names that have been specified in the config + * @param {Map} importNames Map of import names that are being imported + * @returns {void} + * @private + */ + function reportPathForPatterns(node, group, importNames) { + const importSource = node.source.value.trim(); + + const customMessage = group.customMessage; + const restrictedImportNames = group.importNames; + const restrictedImportNamePattern = group.importNamePattern + ? new RegExp(group.importNamePattern, "u") + : null; + const allowedImportNames = group.allowImportNames; + const allowedImportNamePattern = group.allowImportNamePattern + ? new RegExp(group.allowImportNamePattern, "u") + : null; + + /** + * If we are not restricting to any specific import names and just the pattern itself, + * report the error and move on + */ + if ( + !restrictedImportNames && + !allowedImportNames && + !restrictedImportNamePattern && + !allowedImportNamePattern + ) { + context.report({ + node, + messageId: customMessage + ? "patternWithCustomMessage" + : "patterns", + data: { + importSource, + customMessage, + }, + }); + return; + } + + importNames.forEach((specifiers, importName) => { + if (importName === "*") { + const [specifier] = specifiers; + + if (restrictedImportNames) { + context.report({ + node, + messageId: customMessage + ? "patternAndEverythingWithCustomMessage" + : "patternAndEverything", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNames, + customMessage, + }, + }); + } else if (allowedImportNames) { + context.report({ + node, + messageId: customMessage + ? "everythingWithAllowImportNamesAndCustomMessage" + : "everythingWithAllowImportNames", + loc: specifier.loc, + data: { + importSource, + allowedImportNames, + customMessage, + }, + }); + } else if (allowedImportNamePattern) { + context.report({ + node, + messageId: customMessage + ? "everythingWithAllowedImportNamePatternWithCustomMessage" + : "everythingWithAllowedImportNamePattern", + loc: specifier.loc, + data: { + importSource, + allowedImportNamePattern, + customMessage, + }, + }); + } else { + context.report({ + node, + messageId: customMessage + ? "patternAndEverythingWithRegexImportNameAndCustomMessage" + : "patternAndEverythingWithRegexImportName", + loc: specifier.loc, + data: { + importSource, + importNames: restrictedImportNamePattern, + customMessage, + }, + }); + } + + return; + } + + if ( + (restrictedImportNames && + restrictedImportNames.includes(importName)) || + (restrictedImportNamePattern && + restrictedImportNamePattern.test(importName)) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage + ? "patternAndImportNameWithCustomMessage" + : "patternAndImportName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + }, + }); + }); + } + + if ( + allowedImportNames && + !allowedImportNames.includes(importName) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage + ? "allowedImportNameWithCustomMessage" + : "allowedImportName", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + allowedImportNames, + }, + }); + }); + } else if ( + allowedImportNamePattern && + !allowedImportNamePattern.test(importName) + ) { + specifiers.forEach(specifier => { + context.report({ + node, + messageId: customMessage + ? "allowedImportNamePatternWithCustomMessage" + : "allowedImportNamePattern", + loc: specifier.loc, + data: { + importSource, + customMessage, + importName, + allowedImportNamePattern, + }, + }); + }); + } + }); + } + + /** + * Check if the given importSource is restricted by a pattern. + * @param {string} importSource path of the import + * @param {Object} group contains a Ignore instance for paths, and the customMessage to show if it fails + * @returns {boolean} whether the variable is a restricted pattern or not + * @private + */ + function isRestrictedPattern(importSource, group) { + return group.regexMatcher + ? group.regexMatcher.test(importSource) + : group.matcher.ignores(importSource); + } + + /** + * Checks a node to see if any problems should be reported. + * @param {ASTNode} node The node to check. + * @returns {void} + * @private + */ + function checkNode(node) { + const importSource = node.source.value.trim(); + const importNames = new Map(); + + if (node.type === "ExportAllDeclaration") { + const starToken = sourceCode.getFirstToken(node, 1); + + importNames.set("*", [{ loc: starToken.loc }]); + } else if (node.specifiers) { + for (const specifier of node.specifiers) { + let name; + const specifierData = { loc: specifier.loc }; + + if (specifier.type === "ImportDefaultSpecifier") { + name = "default"; + } else if (specifier.type === "ImportNamespaceSpecifier") { + name = "*"; + } else if (specifier.imported) { + name = astUtils.getModuleExportName(specifier.imported); + } else if (specifier.local) { + name = astUtils.getModuleExportName(specifier.local); + } + + if (typeof name === "string") { + if (importNames.has(name)) { + importNames.get(name).push(specifierData); + } else { + importNames.set(name, [specifierData]); + } + } + } + } + + checkRestrictedPathAndReport(importSource, importNames, node); + restrictedPatternGroups.forEach(group => { + if (isRestrictedPattern(importSource, group)) { + reportPathForPatterns(node, group, importNames); + } + }); + } + + return { + ImportDeclaration: checkNode, + ExportNamedDeclaration(node) { + if (node.source) { + checkNode(node); + } + }, + ExportAllDeclaration: checkNode, + }; + }, }; diff --git a/lib/rules/no-restricted-modules.js b/lib/rules/no-restricted-modules.js index 8fe1f9bed4a2..de5577cd7c38 100644 --- a/lib/rules/no-restricted-modules.js +++ b/lib/rules/no-restricted-modules.js @@ -18,212 +18,232 @@ const astUtils = require("./utils/ast-utils"); const ignore = require("ignore"); const arrayOfStrings = { - type: "array", - items: { type: "string" }, - uniqueItems: true + type: "array", + items: { type: "string" }, + uniqueItems: true, }; const arrayOfStringsOrObjects = { - type: "array", - items: { - anyOf: [ - { type: "string" }, - { - type: "object", - properties: { - name: { type: "string" }, - message: { - type: "string", - minLength: 1 - } - }, - additionalProperties: false, - required: ["name"] - } - ] - }, - uniqueItems: true + type: "array", + items: { + anyOf: [ + { type: "string" }, + { + type: "object", + properties: { + name: { type: "string" }, + message: { + type: "string", + minLength: 1, + }, + }, + additionalProperties: false, + required: ["name"], + }, + ], + }, + uniqueItems: true, }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-modules" - }, - - schema: { - anyOf: [ - arrayOfStringsOrObjects, - { - type: "array", - items: { - type: "object", - properties: { - paths: arrayOfStringsOrObjects, - patterns: arrayOfStrings - }, - additionalProperties: false - }, - additionalItems: false - } - ] - }, - - messages: { - defaultMessage: "'{{name}}' module is restricted from being used.", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - customMessage: "'{{name}}' module is restricted from being used. {{customMessage}}", - patternMessage: "'{{name}}' module is restricted from being used by a pattern." - } - }, - - create(context) { - const options = Array.isArray(context.options) ? context.options : []; - const isPathAndPatternsObject = - typeof options[0] === "object" && - (Object.hasOwn(options[0], "paths") || Object.hasOwn(options[0], "patterns")); - - const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || []; - const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || []; - - const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => { - if (typeof importName === "string") { - memo[importName] = null; - } else { - memo[importName.name] = importName.message; - } - return memo; - }, {}); - - // if no imports are restricted we don't need to check - if (Object.keys(restrictedPaths).length === 0 && restrictedPatterns.length === 0) { - return {}; - } - - // relative paths are supported for this rule - const ig = ignore({ allowRelativePaths: true }).add(restrictedPatterns); - - - /** - * Function to check if a node is a string literal. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a string literal. - */ - function isStringLiteral(node) { - return node && node.type === "Literal" && typeof node.value === "string"; - } - - /** - * Function to check if a node is a require call. - * @param {ASTNode} node The node to check. - * @returns {boolean} If the node is a require call. - */ - function isRequireCall(node) { - return node.callee.type === "Identifier" && node.callee.name === "require"; - } - - /** - * Extract string from Literal or TemplateLiteral node - * @param {ASTNode} node The node to extract from - * @returns {string|null} Extracted string or null if node doesn't represent a string - */ - function getFirstArgumentString(node) { - if (isStringLiteral(node)) { - return node.value.trim(); - } - - if (astUtils.isStaticTemplateLiteral(node)) { - return node.quasis[0].value.cooked.trim(); - } - - return null; - } - - /** - * Report a restricted path. - * @param {node} node representing the restricted path reference - * @param {string} name restricted path - * @returns {void} - * @private - */ - function reportPath(node, name) { - const customMessage = restrictedPathMessages[name]; - const messageId = customMessage - ? "customMessage" - : "defaultMessage"; - - context.report({ - node, - messageId, - data: { - name, - customMessage - } - }); - } - - /** - * Check if the given name is a restricted path name - * @param {string} name name of a variable - * @returns {boolean} whether the variable is a restricted path or not - * @private - */ - function isRestrictedPath(name) { - return Object.hasOwn(restrictedPathMessages, name); - } - - return { - CallExpression(node) { - if (isRequireCall(node)) { - - // node has arguments - if (node.arguments.length) { - const name = getFirstArgumentString(node.arguments[0]); - - // if first argument is a string literal or a static string template literal - if (name) { - - // check if argument value is in restricted modules array - if (isRestrictedPath(name)) { - reportPath(node, name); - } - - if (restrictedPatterns.length > 0 && ig.ignores(name)) { - context.report({ - node, - messageId: "patternMessage", - data: { name } - }); - } - } - } - } - } - }; - } + meta: { + 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`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-modules", + }, + + schema: { + anyOf: [ + arrayOfStringsOrObjects, + { + type: "array", + items: { + type: "object", + properties: { + paths: arrayOfStringsOrObjects, + patterns: arrayOfStrings, + }, + additionalProperties: false, + }, + additionalItems: false, + }, + ], + }, + + messages: { + defaultMessage: "'{{name}}' module is restricted from being used.", + customMessage: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{name}}' module is restricted from being used. {{customMessage}}", + patternMessage: + "'{{name}}' module is restricted from being used by a pattern.", + }, + }, + + create(context) { + const options = Array.isArray(context.options) ? context.options : []; + const isPathAndPatternsObject = + typeof options[0] === "object" && + (Object.hasOwn(options[0], "paths") || + Object.hasOwn(options[0], "patterns")); + + const restrictedPaths = + (isPathAndPatternsObject ? options[0].paths : context.options) || + []; + const restrictedPatterns = + (isPathAndPatternsObject ? options[0].patterns : []) || []; + + const restrictedPathMessages = restrictedPaths.reduce( + (memo, importName) => { + if (typeof importName === "string") { + memo[importName] = null; + } else { + memo[importName.name] = importName.message; + } + return memo; + }, + {}, + ); + + // if no imports are restricted we don't need to check + if ( + Object.keys(restrictedPaths).length === 0 && + restrictedPatterns.length === 0 + ) { + return {}; + } + + // relative paths are supported for this rule + const ig = ignore({ allowRelativePaths: true }).add(restrictedPatterns); + + /** + * Function to check if a node is a string literal. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a string literal. + */ + function isStringLiteral(node) { + return ( + node && + node.type === "Literal" && + typeof node.value === "string" + ); + } + + /** + * Function to check if a node is a require call. + * @param {ASTNode} node The node to check. + * @returns {boolean} If the node is a require call. + */ + function isRequireCall(node) { + return ( + node.callee.type === "Identifier" && + node.callee.name === "require" + ); + } + + /** + * Extract string from Literal or TemplateLiteral node + * @param {ASTNode} node The node to extract from + * @returns {string|null} Extracted string or null if node doesn't represent a string + */ + function getFirstArgumentString(node) { + if (isStringLiteral(node)) { + return node.value.trim(); + } + + if (astUtils.isStaticTemplateLiteral(node)) { + return node.quasis[0].value.cooked.trim(); + } + + return null; + } + + /** + * Report a restricted path. + * @param {node} node representing the restricted path reference + * @param {string} name restricted path + * @returns {void} + * @private + */ + function reportPath(node, name) { + const customMessage = restrictedPathMessages[name]; + const messageId = customMessage + ? "customMessage" + : "defaultMessage"; + + context.report({ + node, + messageId, + data: { + name, + customMessage, + }, + }); + } + + /** + * Check if the given name is a restricted path name + * @param {string} name name of a variable + * @returns {boolean} whether the variable is a restricted path or not + * @private + */ + function isRestrictedPath(name) { + return Object.hasOwn(restrictedPathMessages, name); + } + + return { + CallExpression(node) { + if (isRequireCall(node)) { + // node has arguments + if (node.arguments.length) { + const name = getFirstArgumentString(node.arguments[0]); + + // if first argument is a string literal or a static string template literal + if (name) { + // check if argument value is in restricted modules array + if (isRestrictedPath(name)) { + reportPath(node, name); + } + + if ( + restrictedPatterns.length > 0 && + ig.ignores(name) + ) { + context.report({ + node, + messageId: "patternMessage", + data: { name }, + }); + } + } + } + } + }, + }; + }, }; diff --git a/lib/rules/no-restricted-properties.js b/lib/rules/no-restricted-properties.js index acd178ae53e8..9036d49ad76c 100644 --- a/lib/rules/no-restricted-properties.js +++ b/lib/rules/no-restricted-properties.js @@ -13,156 +13,184 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow certain properties on certain objects", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-properties" - }, - - schema: { - type: "array", - items: { - anyOf: [ // `object` and `property` are both optional, but at least one of them must be provided. - { - type: "object", - properties: { - object: { - type: "string" - }, - property: { - type: "string" - }, - message: { - type: "string" - } - }, - additionalProperties: false, - required: ["object"] - }, - { - type: "object", - properties: { - object: { - type: "string" - }, - property: { - type: "string" - }, - message: { - type: "string" - } - }, - additionalProperties: false, - required: ["property"] - } - ] - }, - uniqueItems: true - }, - - messages: { - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - restrictedObjectProperty: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - restrictedProperty: "'{{propertyName}}' is restricted from being used.{{message}}" - } - }, - - create(context) { - const restrictedCalls = context.options; - - if (restrictedCalls.length === 0) { - return {}; - } - - const restrictedProperties = new Map(); - const globallyRestrictedObjects = new Map(); - const globallyRestrictedProperties = new Map(); - - restrictedCalls.forEach(option => { - const objectName = option.object; - const propertyName = option.property; - - if (typeof objectName === "undefined") { - globallyRestrictedProperties.set(propertyName, { message: option.message }); - } else if (typeof propertyName === "undefined") { - globallyRestrictedObjects.set(objectName, { message: option.message }); - } else { - if (!restrictedProperties.has(objectName)) { - restrictedProperties.set(objectName, new Map()); - } - - restrictedProperties.get(objectName).set(propertyName, { - message: option.message - }); - } - }); - - /** - * Checks to see whether a property access is restricted, and reports it if so. - * @param {ASTNode} node The node to report - * @param {string} objectName The name of the object - * @param {string} propertyName The name of the property - * @returns {undefined} - */ - function checkPropertyAccess(node, objectName, propertyName) { - if (propertyName === null) { - return; - } - const matchedObject = restrictedProperties.get(objectName); - const matchedObjectProperty = matchedObject ? matchedObject.get(propertyName) : globallyRestrictedObjects.get(objectName); - const globalMatchedProperty = globallyRestrictedProperties.get(propertyName); - - if (matchedObjectProperty) { - const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : ""; - - context.report({ - node, - messageId: "restrictedObjectProperty", - data: { - objectName, - propertyName, - message - } - }); - } else if (globalMatchedProperty) { - const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : ""; - - context.report({ - node, - messageId: "restrictedProperty", - data: { - propertyName, - message - } - }); - } - } - - return { - MemberExpression(node) { - checkPropertyAccess(node, node.object && node.object.name, astUtils.getStaticPropertyName(node)); - }, - ObjectPattern(node) { - let objectName = null; - - if (node.parent.type === "VariableDeclarator") { - if (node.parent.init && node.parent.init.type === "Identifier") { - objectName = node.parent.init.name; - } - } else if (node.parent.type === "AssignmentExpression" || node.parent.type === "AssignmentPattern") { - if (node.parent.right.type === "Identifier") { - objectName = node.parent.right.name; - } - } - - node.properties.forEach(property => { - checkPropertyAccess(node, objectName, astUtils.getStaticPropertyName(property)); - }); - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow certain properties on certain objects", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-properties", + }, + + schema: { + type: "array", + items: { + anyOf: [ + // `object` and `property` are both optional, but at least one of them must be provided. + { + type: "object", + properties: { + object: { + type: "string", + }, + property: { + type: "string", + }, + message: { + type: "string", + }, + }, + additionalProperties: false, + required: ["object"], + }, + { + type: "object", + properties: { + object: { + type: "string", + }, + property: { + type: "string", + }, + message: { + type: "string", + }, + }, + additionalProperties: false, + required: ["property"], + }, + ], + }, + uniqueItems: true, + }, + + messages: { + restrictedObjectProperty: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", + restrictedProperty: + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + "'{{propertyName}}' is restricted from being used.{{message}}", + }, + }, + + create(context) { + const restrictedCalls = context.options; + + if (restrictedCalls.length === 0) { + return {}; + } + + const restrictedProperties = new Map(); + const globallyRestrictedObjects = new Map(); + const globallyRestrictedProperties = new Map(); + + restrictedCalls.forEach(option => { + const objectName = option.object; + const propertyName = option.property; + + if (typeof objectName === "undefined") { + globallyRestrictedProperties.set(propertyName, { + message: option.message, + }); + } else if (typeof propertyName === "undefined") { + globallyRestrictedObjects.set(objectName, { + message: option.message, + }); + } else { + if (!restrictedProperties.has(objectName)) { + restrictedProperties.set(objectName, new Map()); + } + + restrictedProperties.get(objectName).set(propertyName, { + message: option.message, + }); + } + }); + + /** + * Checks to see whether a property access is restricted, and reports it if so. + * @param {ASTNode} node The node to report + * @param {string} objectName The name of the object + * @param {string} propertyName The name of the property + * @returns {undefined} + */ + function checkPropertyAccess(node, objectName, propertyName) { + if (propertyName === null) { + return; + } + const matchedObject = restrictedProperties.get(objectName); + const matchedObjectProperty = matchedObject + ? matchedObject.get(propertyName) + : globallyRestrictedObjects.get(objectName); + const globalMatchedProperty = + globallyRestrictedProperties.get(propertyName); + + if (matchedObjectProperty) { + const message = matchedObjectProperty.message + ? ` ${matchedObjectProperty.message}` + : ""; + + context.report({ + node, + messageId: "restrictedObjectProperty", + data: { + objectName, + propertyName, + message, + }, + }); + } else if (globalMatchedProperty) { + const message = globalMatchedProperty.message + ? ` ${globalMatchedProperty.message}` + : ""; + + context.report({ + node, + messageId: "restrictedProperty", + data: { + propertyName, + message, + }, + }); + } + } + + return { + MemberExpression(node) { + checkPropertyAccess( + node, + node.object && node.object.name, + astUtils.getStaticPropertyName(node), + ); + }, + ObjectPattern(node) { + let objectName = null; + + if (node.parent.type === "VariableDeclarator") { + if ( + node.parent.init && + node.parent.init.type === "Identifier" + ) { + objectName = node.parent.init.name; + } + } else if ( + node.parent.type === "AssignmentExpression" || + node.parent.type === "AssignmentPattern" + ) { + if (node.parent.right.type === "Identifier") { + objectName = node.parent.right.name; + } + } + + node.properties.forEach(property => { + checkPropertyAccess( + node, + objectName, + astUtils.getStaticPropertyName(property), + ); + }); + }, + }; + }, }; diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js index 930882c330ad..a45da550298d 100644 --- a/lib/rules/no-restricted-syntax.js +++ b/lib/rules/no-restricted-syntax.js @@ -10,61 +10,65 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow specified syntax", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-restricted-syntax" - }, + docs: { + description: "Disallow specified syntax", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-restricted-syntax", + }, - schema: { - type: "array", - items: { - oneOf: [ - { - type: "string" - }, - { - type: "object", - properties: { - selector: { type: "string" }, - message: { type: "string" } - }, - required: ["selector"], - additionalProperties: false - } - ] - }, - uniqueItems: true, - minItems: 0 - }, + schema: { + type: "array", + items: { + oneOf: [ + { + type: "string", + }, + { + type: "object", + properties: { + selector: { type: "string" }, + message: { type: "string" }, + }, + required: ["selector"], + additionalProperties: false, + }, + ], + }, + uniqueItems: true, + minItems: 0, + }, - messages: { - // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period - restrictedSyntax: "{{message}}" - } - }, + messages: { + // eslint-disable-next-line eslint-plugin/report-message-format -- Custom message might not end in a period + restrictedSyntax: "{{message}}", + }, + }, - create(context) { - return context.options.reduce((result, selectorOrObject) => { - const isStringFormat = (typeof selectorOrObject === "string"); - const hasCustomMessage = !isStringFormat && Boolean(selectorOrObject.message); + create(context) { + return context.options.reduce((result, selectorOrObject) => { + const isStringFormat = typeof selectorOrObject === "string"; + const hasCustomMessage = + !isStringFormat && Boolean(selectorOrObject.message); - const selector = isStringFormat ? selectorOrObject : selectorOrObject.selector; - const message = hasCustomMessage ? selectorOrObject.message : `Using '${selector}' is not allowed.`; + const selector = isStringFormat + ? selectorOrObject + : selectorOrObject.selector; + const message = hasCustomMessage + ? selectorOrObject.message + : `Using '${selector}' is not allowed.`; - return Object.assign(result, { - [selector](node) { - context.report({ - node, - messageId: "restrictedSyntax", - data: { message } - }); - } - }); - }, {}); - - } + return Object.assign(result, { + [selector](node) { + context.report({ + node, + messageId: "restrictedSyntax", + data: { message }, + }); + }, + }); + }, {}); + }, }; diff --git a/lib/rules/no-return-assign.js b/lib/rules/no-return-assign.js index 7c1f00ec9a21..12e87020e7dc 100644 --- a/lib/rules/no-return-assign.js +++ b/lib/rules/no-return-assign.js @@ -14,7 +14,8 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/u; +const SENTINEL_TYPE = + /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionExpression|ClassExpression)$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -22,61 +23,65 @@ const SENTINEL_TYPE = /^(?:[a-zA-Z]+?Statement|ArrowFunctionExpression|FunctionE /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - defaultOptions: ["except-parens"], + defaultOptions: ["except-parens"], - docs: { - description: "Disallow assignment operators in `return` statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-return-assign" - }, + docs: { + description: "Disallow assignment operators in `return` statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-return-assign", + }, - schema: [ - { - enum: ["except-parens", "always"] - } - ], + schema: [ + { + enum: ["except-parens", "always"], + }, + ], - messages: { - returnAssignment: "Return statement should not contain assignment.", - arrowAssignment: "Arrow function should not return assignment." - } - }, + messages: { + returnAssignment: "Return statement should not contain assignment.", + arrowAssignment: "Arrow function should not return assignment.", + }, + }, - create(context) { - const always = context.options[0] !== "except-parens"; - const sourceCode = context.sourceCode; + create(context) { + const always = context.options[0] !== "except-parens"; + const sourceCode = context.sourceCode; - return { - AssignmentExpression(node) { - if (!always && astUtils.isParenthesised(sourceCode, node)) { - return; - } + return { + AssignmentExpression(node) { + if (!always && astUtils.isParenthesised(sourceCode, node)) { + return; + } - let currentChild = node; - let parent = currentChild.parent; + let currentChild = node; + let parent = currentChild.parent; - // Find ReturnStatement or ArrowFunctionExpression in ancestors. - while (parent && !SENTINEL_TYPE.test(parent.type)) { - currentChild = parent; - parent = parent.parent; - } + // Find ReturnStatement or ArrowFunctionExpression in ancestors. + while (parent && !SENTINEL_TYPE.test(parent.type)) { + currentChild = parent; + parent = parent.parent; + } - // Reports. - if (parent && parent.type === "ReturnStatement") { - context.report({ - node: parent, - messageId: "returnAssignment" - }); - } else if (parent && parent.type === "ArrowFunctionExpression" && parent.body === currentChild) { - context.report({ - node: parent, - messageId: "arrowAssignment" - }); - } - } - }; - } + // Reports. + if (parent && parent.type === "ReturnStatement") { + context.report({ + node: parent, + messageId: "returnAssignment", + }); + } else if ( + parent && + parent.type === "ArrowFunctionExpression" && + parent.body === currentChild + ) { + context.report({ + node: parent, + messageId: "arrowAssignment", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-return-await.js b/lib/rules/no-return-await.js index e51ee9f26e96..23b78fe5e2b0 100644 --- a/lib/rules/no-return-await.js +++ b/lib/rules/no-return-await.js @@ -13,127 +13,151 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - hasSuggestions: true, - type: "suggestion", - - docs: { - description: "Disallow unnecessary `return await`", - - recommended: false, - - url: "https://eslint.org/docs/latest/rules/no-return-await" - }, - - fixable: null, - - deprecated: { - message: "The original assumption of the rule no longer holds true because of engine optimization.", - url: "https://eslint.org/docs/latest/rules/no-return-await", - deprecatedSince: "8.46.0", - availableUntil: null, - replacedBy: [] - }, - - schema: [ - ], - - messages: { - removeAwait: "Remove redundant `await`.", - redundantUseOfAwait: "Redundant use of `await` on a return value." - } - }, - - create(context) { - - /** - * Reports a found unnecessary `await` expression. - * @param {ASTNode} node The node representing the `await` expression to report - * @returns {void} - */ - function reportUnnecessaryAwait(node) { - context.report({ - node: context.sourceCode.getFirstToken(node), - loc: node.loc, - messageId: "redundantUseOfAwait", - suggest: [ - { - messageId: "removeAwait", - fix(fixer) { - const sourceCode = context.sourceCode; - const [awaitToken, tokenAfterAwait] = sourceCode.getFirstTokens(node, 2); - - const areAwaitAndAwaitedExpressionOnTheSameLine = awaitToken.loc.start.line === tokenAfterAwait.loc.start.line; - - if (!areAwaitAndAwaitedExpressionOnTheSameLine) { - return null; - } - - const [startOfAwait, endOfAwait] = awaitToken.range; - - const characterAfterAwait = sourceCode.text[endOfAwait]; - const trimLength = characterAfterAwait === " " ? 1 : 0; - - const range = [startOfAwait, endOfAwait + trimLength]; - - return fixer.removeRange(range); - } - } - ] - - }); - } - - /** - * Determines whether a thrown error from this node will be caught/handled within this function rather than immediately halting - * this function. For example, a statement in a `try` block will always have an error handler. A statement in - * a `catch` block will only have an error handler if there is also a `finally` block. - * @param {ASTNode} node A node representing a location where an could be thrown - * @returns {boolean} `true` if a thrown error will be caught/handled in this function - */ - function hasErrorHandler(node) { - let ancestor = node; - - while (!astUtils.isFunction(ancestor) && ancestor.type !== "Program") { - if (ancestor.parent.type === "TryStatement" && (ancestor === ancestor.parent.block || ancestor === ancestor.parent.handler && ancestor.parent.finalizer)) { - return true; - } - ancestor = ancestor.parent; - } - return false; - } - - /** - * Checks if a node is placed in tail call position. Once `return` arguments (or arrow function expressions) can be a complex expression, - * an `await` expression could or could not be unnecessary by the definition of this rule. So we're looking for `await` expressions that are in tail position. - * @param {ASTNode} node A node representing the `await` expression to check - * @returns {boolean} The checking result - */ - function isInTailCallPosition(node) { - if (node.parent.type === "ArrowFunctionExpression") { - return true; - } - if (node.parent.type === "ReturnStatement") { - return !hasErrorHandler(node.parent); - } - if (node.parent.type === "ConditionalExpression" && (node === node.parent.consequent || node === node.parent.alternate)) { - return isInTailCallPosition(node.parent); - } - if (node.parent.type === "LogicalExpression" && node === node.parent.right) { - return isInTailCallPosition(node.parent); - } - if (node.parent.type === "SequenceExpression" && node === node.parent.expressions.at(-1)) { - return isInTailCallPosition(node.parent); - } - return false; - } - - return { - AwaitExpression(node) { - if (isInTailCallPosition(node) && !hasErrorHandler(node)) { - reportUnnecessaryAwait(node); - } - } - }; - } + meta: { + hasSuggestions: true, + type: "suggestion", + + docs: { + description: "Disallow unnecessary `return await`", + + recommended: false, + + url: "https://eslint.org/docs/latest/rules/no-return-await", + }, + + fixable: null, + + deprecated: { + message: + "The original assumption of the rule no longer holds true because of engine optimization.", + url: "https://eslint.org/docs/latest/rules/no-return-await", + deprecatedSince: "8.46.0", + availableUntil: null, + replacedBy: [], + }, + + schema: [], + + messages: { + removeAwait: "Remove redundant `await`.", + redundantUseOfAwait: "Redundant use of `await` on a return value.", + }, + }, + + create(context) { + /** + * Reports a found unnecessary `await` expression. + * @param {ASTNode} node The node representing the `await` expression to report + * @returns {void} + */ + function reportUnnecessaryAwait(node) { + context.report({ + node: context.sourceCode.getFirstToken(node), + loc: node.loc, + messageId: "redundantUseOfAwait", + suggest: [ + { + messageId: "removeAwait", + fix(fixer) { + const sourceCode = context.sourceCode; + const [awaitToken, tokenAfterAwait] = + sourceCode.getFirstTokens(node, 2); + + const areAwaitAndAwaitedExpressionOnTheSameLine = + awaitToken.loc.start.line === + tokenAfterAwait.loc.start.line; + + if (!areAwaitAndAwaitedExpressionOnTheSameLine) { + return null; + } + + const [startOfAwait, endOfAwait] = awaitToken.range; + + const characterAfterAwait = + sourceCode.text[endOfAwait]; + const trimLength = + characterAfterAwait === " " ? 1 : 0; + + const range = [ + startOfAwait, + endOfAwait + trimLength, + ]; + + return fixer.removeRange(range); + }, + }, + ], + }); + } + + /** + * Determines whether a thrown error from this node will be caught/handled within this function rather than immediately halting + * this function. For example, a statement in a `try` block will always have an error handler. A statement in + * a `catch` block will only have an error handler if there is also a `finally` block. + * @param {ASTNode} node A node representing a location where an could be thrown + * @returns {boolean} `true` if a thrown error will be caught/handled in this function + */ + function hasErrorHandler(node) { + let ancestor = node; + + while ( + !astUtils.isFunction(ancestor) && + ancestor.type !== "Program" + ) { + if ( + ancestor.parent.type === "TryStatement" && + (ancestor === ancestor.parent.block || + (ancestor === ancestor.parent.handler && + ancestor.parent.finalizer)) + ) { + return true; + } + ancestor = ancestor.parent; + } + return false; + } + + /** + * Checks if a node is placed in tail call position. Once `return` arguments (or arrow function expressions) can be a complex expression, + * an `await` expression could or could not be unnecessary by the definition of this rule. So we're looking for `await` expressions that are in tail position. + * @param {ASTNode} node A node representing the `await` expression to check + * @returns {boolean} The checking result + */ + function isInTailCallPosition(node) { + if (node.parent.type === "ArrowFunctionExpression") { + return true; + } + if (node.parent.type === "ReturnStatement") { + return !hasErrorHandler(node.parent); + } + if ( + node.parent.type === "ConditionalExpression" && + (node === node.parent.consequent || + node === node.parent.alternate) + ) { + return isInTailCallPosition(node.parent); + } + if ( + node.parent.type === "LogicalExpression" && + node === node.parent.right + ) { + return isInTailCallPosition(node.parent); + } + if ( + node.parent.type === "SequenceExpression" && + node === node.parent.expressions.at(-1) + ) { + return isInTailCallPosition(node.parent); + } + return false; + } + + return { + AwaitExpression(node) { + if (isInTailCallPosition(node) && !hasErrorHandler(node)) { + reportUnnecessaryAwait(node); + } + }, + }; + }, }; diff --git a/lib/rules/no-script-url.js b/lib/rules/no-script-url.js index 607c768a2cbd..963541316e22 100644 --- a/lib/rules/no-script-url.js +++ b/lib/rules/no-script-url.js @@ -14,48 +14,55 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `javascript:` URLs", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-script-url" - }, - - schema: [], - - messages: { - unexpectedScriptURL: "Script URL is a form of eval." - } - }, - - create(context) { - - /** - * Check whether a node's static value starts with `javascript:` or not. - * And report an error for unexpected script URL. - * @param {ASTNode} node node to check - * @returns {void} - */ - function check(node) { - const value = astUtils.getStaticStringValue(node); - - if (typeof value === "string" && value.toLowerCase().indexOf("javascript:") === 0) { - context.report({ node, messageId: "unexpectedScriptURL" }); - } - } - return { - Literal(node) { - if (node.value && typeof node.value === "string") { - check(node); - } - }, - TemplateLiteral(node) { - if (!(node.parent && node.parent.type === "TaggedTemplateExpression")) { - check(node); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow `javascript:` URLs", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-script-url", + }, + + schema: [], + + messages: { + unexpectedScriptURL: "Script URL is a form of eval.", + }, + }, + + create(context) { + /** + * Check whether a node's static value starts with `javascript:` or not. + * And report an error for unexpected script URL. + * @param {ASTNode} node node to check + * @returns {void} + */ + function check(node) { + const value = astUtils.getStaticStringValue(node); + + if ( + typeof value === "string" && + value.toLowerCase().indexOf("javascript:") === 0 + ) { + context.report({ node, messageId: "unexpectedScriptURL" }); + } + } + return { + Literal(node) { + if (node.value && typeof node.value === "string") { + check(node); + } + }, + TemplateLiteral(node) { + if ( + !( + node.parent && + node.parent.type === "TaggedTemplateExpression" + ) + ) { + check(node); + } + }, + }; + }, }; diff --git a/lib/rules/no-self-assign.js b/lib/rules/no-self-assign.js index 91b928ea112c..b12172b06666 100644 --- a/lib/rules/no-self-assign.js +++ b/lib/rules/no-self-assign.js @@ -28,96 +28,97 @@ const SPACES = /\s+/gu; * @returns {void} */ function eachSelfAssignment(left, right, props, report) { - if (!left || !right) { - - // do nothing - } else if ( - left.type === "Identifier" && - right.type === "Identifier" && - left.name === right.name - ) { - report(right); - } else if ( - left.type === "ArrayPattern" && - right.type === "ArrayExpression" - ) { - const end = Math.min(left.elements.length, right.elements.length); - - for (let i = 0; i < end; ++i) { - const leftElement = left.elements[i]; - const rightElement = right.elements[i]; - - // Avoid cases such as [...a] = [...a, 1] - if ( - leftElement && - leftElement.type === "RestElement" && - i < right.elements.length - 1 - ) { - break; - } - - eachSelfAssignment(leftElement, rightElement, props, report); - - // After a spread element, those indices are unknown. - if (rightElement && rightElement.type === "SpreadElement") { - break; - } - } - } else if ( - left.type === "RestElement" && - right.type === "SpreadElement" - ) { - eachSelfAssignment(left.argument, right.argument, props, report); - } else if ( - left.type === "ObjectPattern" && - right.type === "ObjectExpression" && - right.properties.length >= 1 - ) { - - /* - * Gets the index of the last spread property. - * It's possible to overwrite properties followed by it. - */ - let startJ = 0; - - for (let i = right.properties.length - 1; i >= 0; --i) { - const propType = right.properties[i].type; - - if (propType === "SpreadElement" || propType === "ExperimentalSpreadProperty") { - startJ = i + 1; - break; - } - } - - for (let i = 0; i < left.properties.length; ++i) { - for (let j = startJ; j < right.properties.length; ++j) { - eachSelfAssignment( - left.properties[i], - right.properties[j], - props, - report - ); - } - } - } else if ( - left.type === "Property" && - right.type === "Property" && - right.kind === "init" && - !right.method - ) { - const leftName = astUtils.getStaticPropertyName(left); - - if (leftName !== null && leftName === astUtils.getStaticPropertyName(right)) { - eachSelfAssignment(left.value, right.value, props, report); - } - } else if ( - props && - astUtils.skipChainExpression(left).type === "MemberExpression" && - astUtils.skipChainExpression(right).type === "MemberExpression" && - astUtils.isSameReference(left, right) - ) { - report(right); - } + if (!left || !right) { + // do nothing + } else if ( + left.type === "Identifier" && + right.type === "Identifier" && + left.name === right.name + ) { + report(right); + } else if ( + left.type === "ArrayPattern" && + right.type === "ArrayExpression" + ) { + const end = Math.min(left.elements.length, right.elements.length); + + for (let i = 0; i < end; ++i) { + const leftElement = left.elements[i]; + const rightElement = right.elements[i]; + + // Avoid cases such as [...a] = [...a, 1] + if ( + leftElement && + leftElement.type === "RestElement" && + i < right.elements.length - 1 + ) { + break; + } + + eachSelfAssignment(leftElement, rightElement, props, report); + + // After a spread element, those indices are unknown. + if (rightElement && rightElement.type === "SpreadElement") { + break; + } + } + } else if (left.type === "RestElement" && right.type === "SpreadElement") { + eachSelfAssignment(left.argument, right.argument, props, report); + } else if ( + left.type === "ObjectPattern" && + right.type === "ObjectExpression" && + right.properties.length >= 1 + ) { + /* + * Gets the index of the last spread property. + * It's possible to overwrite properties followed by it. + */ + let startJ = 0; + + for (let i = right.properties.length - 1; i >= 0; --i) { + const propType = right.properties[i].type; + + if ( + propType === "SpreadElement" || + propType === "ExperimentalSpreadProperty" + ) { + startJ = i + 1; + break; + } + } + + for (let i = 0; i < left.properties.length; ++i) { + for (let j = startJ; j < right.properties.length; ++j) { + eachSelfAssignment( + left.properties[i], + right.properties[j], + props, + report, + ); + } + } + } else if ( + left.type === "Property" && + right.type === "Property" && + right.kind === "init" && + !right.method + ) { + const leftName = astUtils.getStaticPropertyName(left); + + if ( + leftName !== null && + leftName === astUtils.getStaticPropertyName(right) + ) { + eachSelfAssignment(left.value, right.value, props, report); + } + } else if ( + props && + astUtils.skipChainExpression(left).type === "MemberExpression" && + astUtils.skipChainExpression(right).type === "MemberExpression" && + astUtils.isSameReference(left, right) + ) { + report(right); + } } //------------------------------------------------------------------------------ @@ -126,59 +127,60 @@ function eachSelfAssignment(left, right, props, report) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ props: true }], - - docs: { - description: "Disallow assignments where both sides are exactly the same", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-self-assign" - }, - - schema: [ - { - type: "object", - properties: { - props: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - selfAssignment: "'{{name}}' is assigned to itself." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ props }] = context.options; - - /** - * Reports a given node as self assignments. - * @param {ASTNode} node A node to report. This is an Identifier node. - * @returns {void} - */ - function report(node) { - context.report({ - node, - messageId: "selfAssignment", - data: { - name: sourceCode.getText(node).replace(SPACES, "") - } - }); - } - - return { - AssignmentExpression(node) { - if (["=", "&&=", "||=", "??="].includes(node.operator)) { - eachSelfAssignment(node.left, node.right, props, report); - } - } - }; - } + meta: { + type: "problem", + + defaultOptions: [{ props: true }], + + docs: { + description: + "Disallow assignments where both sides are exactly the same", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-self-assign", + }, + + schema: [ + { + type: "object", + properties: { + props: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + selfAssignment: "'{{name}}' is assigned to itself.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ props }] = context.options; + + /** + * Reports a given node as self assignments. + * @param {ASTNode} node A node to report. This is an Identifier node. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "selfAssignment", + data: { + name: sourceCode.getText(node).replace(SPACES, ""), + }, + }); + } + + return { + AssignmentExpression(node) { + if (["=", "&&=", "||=", "??="].includes(node.operator)) { + eachSelfAssignment(node.left, node.right, props, report); + } + }, + }; + }, }; diff --git a/lib/rules/no-self-compare.js b/lib/rules/no-self-compare.js index 3b076eba83c7..3782d62d381a 100644 --- a/lib/rules/no-self-compare.js +++ b/lib/rules/no-self-compare.js @@ -12,49 +12,66 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow comparisons where both sides are exactly the same", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-self-compare" - }, - - schema: [], - - messages: { - comparingToSelf: "Comparing to itself is potentially pointless." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Determines whether two nodes are composed of the same tokens. - * @param {ASTNode} nodeA The first node - * @param {ASTNode} nodeB The second node - * @returns {boolean} true if the nodes have identical token representations - */ - function hasSameTokens(nodeA, nodeB) { - const tokensA = sourceCode.getTokens(nodeA); - const tokensB = sourceCode.getTokens(nodeB); - - return tokensA.length === tokensB.length && - tokensA.every((token, index) => token.type === tokensB[index].type && token.value === tokensB[index].value); - } - - return { - - BinaryExpression(node) { - const operators = new Set(["===", "==", "!==", "!=", ">", "<", ">=", "<="]); - - if (operators.has(node.operator) && hasSameTokens(node.left, node.right)) { - context.report({ node, messageId: "comparingToSelf" }); - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: + "Disallow comparisons where both sides are exactly the same", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-self-compare", + }, + + schema: [], + + messages: { + comparingToSelf: "Comparing to itself is potentially pointless.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Determines whether two nodes are composed of the same tokens. + * @param {ASTNode} nodeA The first node + * @param {ASTNode} nodeB The second node + * @returns {boolean} true if the nodes have identical token representations + */ + function hasSameTokens(nodeA, nodeB) { + const tokensA = sourceCode.getTokens(nodeA); + const tokensB = sourceCode.getTokens(nodeB); + + return ( + tokensA.length === tokensB.length && + tokensA.every( + (token, index) => + token.type === tokensB[index].type && + token.value === tokensB[index].value, + ) + ); + } + + return { + BinaryExpression(node) { + const operators = new Set([ + "===", + "==", + "!==", + "!=", + ">", + "<", + ">=", + "<=", + ]); + + if ( + operators.has(node.operator) && + hasSameTokens(node.left, node.right) + ) { + context.report({ node, messageId: "comparingToSelf" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-sequences.js b/lib/rules/no-sequences.js index 5843b7a09341..a3e6bb825a06 100644 --- a/lib/rules/no-sequences.js +++ b/lib/rules/no-sequences.js @@ -15,125 +15,144 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow comma operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-sequences" - }, - - schema: [{ - type: "object", - properties: { - allowInParentheses: { - type: "boolean" - } - }, - additionalProperties: false - }], - - defaultOptions: [{ - allowInParentheses: true - }], - - messages: { - unexpectedCommaExpression: "Unexpected use of comma operator." - } - }, - - create(context) { - const [{ allowInParentheses }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Parts of the grammar that are required to have parens. - */ - const parenthesized = { - DoWhileStatement: "test", - IfStatement: "test", - SwitchStatement: "discriminant", - WhileStatement: "test", - WithStatement: "object", - ArrowFunctionExpression: "body" - - /* - * Omitting CallExpression - commas are parsed as argument separators - * Omitting NewExpression - commas are parsed as argument separators - * Omitting ForInStatement - parts aren't individually parenthesised - * Omitting ForStatement - parts aren't individually parenthesised - */ - }; - - /** - * Determines whether a node is required by the grammar to be wrapped in - * parens, e.g. the test of an if statement. - * @param {ASTNode} node The AST node - * @returns {boolean} True if parens around node belong to parent node. - */ - function requiresExtraParens(node) { - return node.parent && parenthesized[node.parent.type] && - node === node.parent[parenthesized[node.parent.type]]; - } - - /** - * Check if a node is wrapped in parens. - * @param {ASTNode} node The AST node - * @returns {boolean} True if the node has a paren on each side. - */ - function isParenthesised(node) { - return astUtils.isParenthesised(sourceCode, node); - } - - /** - * Check if a node is wrapped in two levels of parens. - * @param {ASTNode} node The AST node - * @returns {boolean} True if two parens surround the node on each side. - */ - function isParenthesisedTwice(node) { - const previousToken = sourceCode.getTokenBefore(node, 1), - nextToken = sourceCode.getTokenAfter(node, 1); - - return isParenthesised(node) && previousToken && nextToken && - astUtils.isOpeningParenToken(previousToken) && previousToken.range[1] <= node.range[0] && - astUtils.isClosingParenToken(nextToken) && nextToken.range[0] >= node.range[1]; - } - - return { - SequenceExpression(node) { - - // Always allow sequences in for statement update - if (node.parent.type === "ForStatement" && - (node === node.parent.init || node === node.parent.update)) { - return; - } - - // Wrapping a sequence in extra parens indicates intent - if (allowInParentheses) { - if (requiresExtraParens(node)) { - if (isParenthesisedTwice(node)) { - return; - } - } else { - if (isParenthesised(node)) { - return; - } - } - } - - const firstCommaToken = sourceCode.getTokenAfter(node.expressions[0], astUtils.isCommaToken); - - context.report({ node, loc: firstCommaToken.loc, messageId: "unexpectedCommaExpression" }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow comma operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-sequences", + }, + + schema: [ + { + type: "object", + properties: { + allowInParentheses: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [ + { + allowInParentheses: true, + }, + ], + + messages: { + unexpectedCommaExpression: "Unexpected use of comma operator.", + }, + }, + + create(context) { + const [{ allowInParentheses }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Parts of the grammar that are required to have parens. + */ + const parenthesized = { + DoWhileStatement: "test", + IfStatement: "test", + SwitchStatement: "discriminant", + WhileStatement: "test", + WithStatement: "object", + ArrowFunctionExpression: "body", + + /* + * Omitting CallExpression - commas are parsed as argument separators + * Omitting NewExpression - commas are parsed as argument separators + * Omitting ForInStatement - parts aren't individually parenthesised + * Omitting ForStatement - parts aren't individually parenthesised + */ + }; + + /** + * Determines whether a node is required by the grammar to be wrapped in + * parens, e.g. the test of an if statement. + * @param {ASTNode} node The AST node + * @returns {boolean} True if parens around node belong to parent node. + */ + function requiresExtraParens(node) { + return ( + node.parent && + parenthesized[node.parent.type] && + node === node.parent[parenthesized[node.parent.type]] + ); + } + + /** + * Check if a node is wrapped in parens. + * @param {ASTNode} node The AST node + * @returns {boolean} True if the node has a paren on each side. + */ + function isParenthesised(node) { + return astUtils.isParenthesised(sourceCode, node); + } + + /** + * Check if a node is wrapped in two levels of parens. + * @param {ASTNode} node The AST node + * @returns {boolean} True if two parens surround the node on each side. + */ + function isParenthesisedTwice(node) { + const previousToken = sourceCode.getTokenBefore(node, 1), + nextToken = sourceCode.getTokenAfter(node, 1); + + return ( + isParenthesised(node) && + previousToken && + nextToken && + astUtils.isOpeningParenToken(previousToken) && + previousToken.range[1] <= node.range[0] && + astUtils.isClosingParenToken(nextToken) && + nextToken.range[0] >= node.range[1] + ); + } + + return { + SequenceExpression(node) { + // Always allow sequences in for statement update + if ( + node.parent.type === "ForStatement" && + (node === node.parent.init || node === node.parent.update) + ) { + return; + } + + // Wrapping a sequence in extra parens indicates intent + if (allowInParentheses) { + if (requiresExtraParens(node)) { + if (isParenthesisedTwice(node)) { + return; + } + } else { + if (isParenthesised(node)) { + return; + } + } + } + + const firstCommaToken = sourceCode.getTokenAfter( + node.expressions[0], + astUtils.isCommaToken, + ); + + context.report({ + node, + loc: firstCommaToken.loc, + messageId: "unexpectedCommaExpression", + }); + }, + }; + }, }; diff --git a/lib/rules/no-setter-return.js b/lib/rules/no-setter-return.js index a5abaaa7e53f..c89c3864acc0 100644 --- a/lib/rules/no-setter-return.js +++ b/lib/rules/no-setter-return.js @@ -23,9 +23,13 @@ const { findVariable } = require("@eslint-community/eslint-utils"); * @returns {boolean} True if the identifier is a reference to a global variable. */ function isGlobalReference(node, scope) { - const variable = findVariable(scope, node); + const variable = findVariable(scope, node); - return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; + return ( + variable !== null && + variable.scope.type === "global" && + variable.defs.length === 0 + ); } /** @@ -38,13 +42,28 @@ function isGlobalReference(node, scope) { * @param {number} index The given position. * @returns {boolean} `true` if the node is argument at the given position. */ -function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index) { - const callNode = node.parent; - - return callNode.type === "CallExpression" && - callNode.arguments[index] === node && - astUtils.isSpecificMemberAccess(callNode.callee, objectName, methodName) && - isGlobalReference(astUtils.skipChainExpression(callNode.callee).object, scope); +function isArgumentOfGlobalMethodCall( + node, + scope, + objectName, + methodName, + index, +) { + const callNode = node.parent; + + return ( + callNode.type === "CallExpression" && + callNode.arguments[index] === node && + astUtils.isSpecificMemberAccess( + callNode.callee, + objectName, + methodName, + ) && + isGlobalReference( + astUtils.skipChainExpression(callNode.callee).object, + scope, + ) + ); } /** @@ -54,33 +73,52 @@ function isArgumentOfGlobalMethodCall(node, scope, objectName, methodName, index * @returns {boolean} `true` if the node is a property descriptor. */ function isPropertyDescriptor(node, scope) { - if ( - isArgumentOfGlobalMethodCall(node, scope, "Object", "defineProperty", 2) || - isArgumentOfGlobalMethodCall(node, scope, "Reflect", "defineProperty", 2) - ) { - return true; - } - - const parent = node.parent; - - if ( - parent.type === "Property" && - parent.value === node - ) { - const grandparent = parent.parent; - - if ( - grandparent.type === "ObjectExpression" && - ( - isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "create", 1) || - isArgumentOfGlobalMethodCall(grandparent, scope, "Object", "defineProperties", 1) - ) - ) { - return true; - } - } - - return false; + if ( + isArgumentOfGlobalMethodCall( + node, + scope, + "Object", + "defineProperty", + 2, + ) || + isArgumentOfGlobalMethodCall( + node, + scope, + "Reflect", + "defineProperty", + 2, + ) + ) { + return true; + } + + const parent = node.parent; + + if (parent.type === "Property" && parent.value === node) { + const grandparent = parent.parent; + + if ( + grandparent.type === "ObjectExpression" && + (isArgumentOfGlobalMethodCall( + grandparent, + scope, + "Object", + "create", + 1, + ) || + isArgumentOfGlobalMethodCall( + grandparent, + scope, + "Object", + "defineProperties", + 1, + )) + ) { + return true; + } + } + + return false; } /** @@ -90,31 +128,29 @@ function isPropertyDescriptor(node, scope) { * @returns {boolean} `true` if the node is a setter. */ function isSetter(node, scope) { - const parent = node.parent; - - if ( - (parent.type === "Property" || parent.type === "MethodDefinition") && - parent.kind === "set" && - parent.value === node - ) { - - // Setter in an object literal or in a class - return true; - } - - if ( - parent.type === "Property" && - parent.value === node && - astUtils.getStaticPropertyName(parent) === "set" && - parent.parent.type === "ObjectExpression" && - isPropertyDescriptor(parent.parent, scope) - ) { - - // Setter in a property descriptor - return true; - } - - return false; + const parent = node.parent; + + if ( + (parent.type === "Property" || parent.type === "MethodDefinition") && + parent.kind === "set" && + parent.value === node + ) { + // Setter in an object literal or in a class + return true; + } + + if ( + parent.type === "Property" && + parent.value === node && + astUtils.getStaticPropertyName(parent) === "set" && + parent.parent.type === "ObjectExpression" && + isPropertyDescriptor(parent.parent, scope) + ) { + // Setter in a property descriptor + return true; + } + + return false; } /** @@ -123,13 +159,13 @@ function isSetter(node, scope) { * @returns {Scope} Function's outer scope. */ function getOuterScope(scope) { - const upper = scope.upper; + const upper = scope.upper; - if (upper.type === "function-expression-name") { - return upper.upper; - } + if (upper.type === "function-expression-name") { + return upper.upper; + } - return upper; + return upper; } //------------------------------------------------------------------------------ @@ -138,89 +174,86 @@ function getOuterScope(scope) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow returning values from setters", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-setter-return" - }, - - schema: [], - - messages: { - returnsValue: "Setter cannot return a value." - } - }, - - create(context) { - let funcInfo = null; - const sourceCode = context.sourceCode; - - /** - * Creates and pushes to the stack a function info object for the given function node. - * @param {ASTNode} node The function node. - * @returns {void} - */ - function enterFunction(node) { - const outerScope = getOuterScope(sourceCode.getScope(node)); - - funcInfo = { - upper: funcInfo, - isSetter: isSetter(node, outerScope) - }; - } - - /** - * Pops the current function info object from the stack. - * @returns {void} - */ - function exitFunction() { - funcInfo = funcInfo.upper; - } - - /** - * Reports the given node. - * @param {ASTNode} node Node to report. - * @returns {void} - */ - function report(node) { - context.report({ node, messageId: "returnsValue" }); - } - - return { - - /* - * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid - * false positives, because a ReturnStatement node can belong to a function declaration inside a setter. - * - * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor, - * but that's out of scope for this rule. - */ - FunctionDeclaration: enterFunction, - FunctionExpression: enterFunction, - ArrowFunctionExpression(node) { - enterFunction(node); - - if (funcInfo.isSetter && node.expression) { - - // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement. - report(node.body); - } - }, - - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression:exit": exitFunction, - "ArrowFunctionExpression:exit": exitFunction, - - ReturnStatement(node) { - - // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`. - if (funcInfo && funcInfo.isSetter && node.argument) { - report(node); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow returning values from setters", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-setter-return", + }, + + schema: [], + + messages: { + returnsValue: "Setter cannot return a value.", + }, + }, + + create(context) { + let funcInfo = null; + const sourceCode = context.sourceCode; + + /** + * Creates and pushes to the stack a function info object for the given function node. + * @param {ASTNode} node The function node. + * @returns {void} + */ + function enterFunction(node) { + const outerScope = getOuterScope(sourceCode.getScope(node)); + + funcInfo = { + upper: funcInfo, + isSetter: isSetter(node, outerScope), + }; + } + + /** + * Pops the current function info object from the stack. + * @returns {void} + */ + function exitFunction() { + funcInfo = funcInfo.upper; + } + + /** + * Reports the given node. + * @param {ASTNode} node Node to report. + * @returns {void} + */ + function report(node) { + context.report({ node, messageId: "returnsValue" }); + } + + return { + /* + * Function declarations cannot be setters, but we still have to track them in the `funcInfo` stack to avoid + * false positives, because a ReturnStatement node can belong to a function declaration inside a setter. + * + * Note: A previously declared function can be referenced and actually used as a setter in a property descriptor, + * but that's out of scope for this rule. + */ + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression(node) { + enterFunction(node); + + if (funcInfo.isSetter && node.expression) { + // { set: foo => bar } property descriptor. Report implicit return 'bar' as the equivalent for a return statement. + report(node.body); + } + }, + + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + "ArrowFunctionExpression:exit": exitFunction, + + ReturnStatement(node) { + // Global returns (e.g., at the top level of a Node module) don't have `funcInfo`. + if (funcInfo && funcInfo.isSetter && node.argument) { + report(node); + } + }, + }; + }, }; diff --git a/lib/rules/no-shadow-restricted-names.js b/lib/rules/no-shadow-restricted-names.js index cdf96ce9e10a..d43bf3582f38 100644 --- a/lib/rules/no-shadow-restricted-names.js +++ b/lib/rules/no-shadow-restricted-names.js @@ -12,9 +12,15 @@ * @returns {boolean} true if this variable safely shadows `undefined` */ function safelyShadowsUndefined(variable) { - return variable.name === "undefined" && - variable.references.every(ref => !ref.isWrite()) && - variable.defs.every(def => def.node.type === "VariableDeclarator" && def.node.init === null); + return ( + variable.name === "undefined" && + variable.references.every(ref => !ref.isWrite()) && + variable.defs.every( + def => + def.node.type === "VariableDeclarator" && + def.node.init === null, + ) + ); } //------------------------------------------------------------------------------ @@ -23,53 +29,62 @@ function safelyShadowsUndefined(variable) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow identifiers from shadowing restricted names", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names" - }, + docs: { + description: "Disallow identifiers from shadowing restricted names", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-shadow-restricted-names", + }, - schema: [], + schema: [], - messages: { - shadowingRestrictedName: "Shadowing of global property '{{name}}'." - } - }, + messages: { + shadowingRestrictedName: "Shadowing of global property '{{name}}'.", + }, + }, - create(context) { + create(context) { + const RESTRICTED = new Set([ + "undefined", + "NaN", + "Infinity", + "arguments", + "eval", + ]); + const sourceCode = context.sourceCode; + // Track reported nodes to avoid duplicate reports. For example, on class declarations. + const reportedNodes = new Set(); - const RESTRICTED = new Set(["undefined", "NaN", "Infinity", "arguments", "eval"]); - const sourceCode = context.sourceCode; + return { + "VariableDeclaration, :function, CatchClause, ImportDeclaration, ClassDeclaration, ClassExpression"( + node, + ) { + for (const variable of sourceCode.getDeclaredVariables(node)) { + if ( + variable.defs.length > 0 && + RESTRICTED.has(variable.name) && + !safelyShadowsUndefined(variable) + ) { + for (const def of variable.defs) { + const nodeToReport = def.name; - // Track reported nodes to avoid duplicate reports. For example, on class declarations. - const reportedNodes = new Set(); - - return { - "VariableDeclaration, :function, CatchClause, ImportDeclaration, ClassDeclaration, ClassExpression"(node) { - for (const variable of sourceCode.getDeclaredVariables(node)) { - if (variable.defs.length > 0 && RESTRICTED.has(variable.name) && !safelyShadowsUndefined(variable)) { - for (const def of variable.defs) { - const nodeToReport = def.name; - - if (!reportedNodes.has(nodeToReport)) { - reportedNodes.add(nodeToReport); - context.report({ - node: nodeToReport, - messageId: "shadowingRestrictedName", - data: { - name: variable.name - } - }); - } - } - } - } - } - }; - - } + if (!reportedNodes.has(nodeToReport)) { + reportedNodes.add(nodeToReport); + context.report({ + node: nodeToReport, + messageId: "shadowingRestrictedName", + data: { + name: variable.name, + }, + }); + } + } + } + } + }, + }; + }, }; diff --git a/lib/rules/no-shadow.js b/lib/rules/no-shadow.js index 67e9695ca4d3..98fdaeebb7d2 100644 --- a/lib/rules/no-shadow.js +++ b/lib/rules/no-shadow.js @@ -15,10 +15,14 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const FUNC_EXPR_NODE_TYPES = new Set(["ArrowFunctionExpression", "FunctionExpression"]); +const FUNC_EXPR_NODE_TYPES = new Set([ + "ArrowFunctionExpression", + "FunctionExpression", +]); const CALL_EXPR_NODE_TYPE = new Set(["CallExpression"]); const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; -const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; +const SENTINEL_TYPE = + /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -26,317 +30,339 @@ const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFun /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - allow: [], - builtinGlobals: false, - hoist: "functions", - ignoreOnInitialization: false - }], - - docs: { - description: "Disallow variable declarations from shadowing variables declared in the outer scope", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-shadow" - }, - - schema: [ - { - type: "object", - properties: { - builtinGlobals: { type: "boolean" }, - hoist: { enum: ["all", "functions", "never"] }, - allow: { - type: "array", - items: { - type: "string" - } - }, - ignoreOnInitialization: { type: "boolean" } - }, - additionalProperties: false - } - ], - - messages: { - noShadow: "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", - noShadowGlobal: "'{{name}}' is already a global variable." - } - }, - - create(context) { - const [{ - builtinGlobals, - hoist, - allow, - ignoreOnInitialization - }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Checks whether or not a given location is inside of the range of a given node. - * @param {ASTNode} node An node to check. - * @param {number} location A location to check. - * @returns {boolean} `true` if the location is inside of the range of the node. - */ - function isInRange(node, location) { - return node && node.range[0] <= location && location <= node.range[1]; - } - - /** - * Searches from the current node through its ancestry to find a matching node. - * @param {ASTNode} node a node to get. - * @param {(node: ASTNode) => boolean} match a callback that checks whether or not the node verifies its condition or not. - * @returns {ASTNode|null} the matching node. - */ - function findSelfOrAncestor(node, match) { - let currentNode = node; - - while (currentNode && !match(currentNode)) { - currentNode = currentNode.parent; - } - return currentNode; - } - - /** - * Finds function's outer scope. - * @param {Scope} scope Function's own scope. - * @returns {Scope} Function's outer scope. - */ - function getOuterScope(scope) { - const upper = scope.upper; - - if (upper.type === "function-expression-name") { - return upper.upper; - } - return upper; - } - - /** - * Checks if a variable and a shadowedVariable have the same init pattern ancestor. - * @param {Object} variable a variable to check. - * @param {Object} shadowedVariable a shadowedVariable to check. - * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor. - */ - function isInitPatternNode(variable, shadowedVariable) { - const outerDef = shadowedVariable.defs[0]; - - if (!outerDef) { - return false; - } - - const { variableScope } = variable.scope; - - - if (!(FUNC_EXPR_NODE_TYPES.has(variableScope.block.type) && getOuterScope(variableScope) === shadowedVariable.scope)) { - return false; - } - - const fun = variableScope.block; - const { parent } = fun; - - const callExpression = findSelfOrAncestor( - parent, - node => CALL_EXPR_NODE_TYPE.has(node.type) - ); - - if (!callExpression) { - return false; - } - - let node = outerDef.name; - const location = callExpression.range[1]; - - while (node) { - if (node.type === "VariableDeclarator") { - if (isInRange(node.init, location)) { - return true; - } - if (FOR_IN_OF_TYPE.test(node.parent.parent.type) && - isInRange(node.parent.parent.right, location) - ) { - return true; - } - break; - } else if (node.type === "AssignmentPattern") { - if (isInRange(node.right, location)) { - return true; - } - } else if (SENTINEL_TYPE.test(node.type)) { - break; - } - - node = node.parent; - } - - return false; - } - - /** - * Check if variable name is allowed. - * @param {ASTNode} variable The variable to check. - * @returns {boolean} Whether or not the variable name is allowed. - */ - function isAllowed(variable) { - return allow.includes(variable.name); - } - - /** - * Checks if a variable of the class name in the class scope of ClassDeclaration. - * - * ClassDeclaration creates two variables of its name into its outer scope and its class scope. - * So we should ignore the variable in the class scope. - * @param {Object} variable The variable to check. - * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. - */ - function isDuplicatedClassNameVariable(variable) { - const block = variable.scope.block; - - return block.type === "ClassDeclaration" && block.id === variable.identifiers[0]; - } - - /** - * Checks if a variable is inside the initializer of scopeVar. - * - * To avoid reporting at declarations such as `var a = function a() {};`. - * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The scope variable to look for. - * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. - */ - function isOnInitializer(variable, scopeVar) { - const outerScope = scopeVar.scope; - const outerDef = scopeVar.defs[0]; - const outer = outerDef && outerDef.parent && outerDef.parent.range; - const innerScope = variable.scope; - const innerDef = variable.defs[0]; - const inner = innerDef && innerDef.name.range; - - return ( - outer && - inner && - outer[0] < inner[0] && - inner[1] < outer[1] && - ((innerDef.type === "FunctionName" && innerDef.node.type === "FunctionExpression") || innerDef.node.type === "ClassExpression") && - outerScope === innerScope.upper - ); - } - - /** - * Get a range of a variable's identifier node. - * @param {Object} variable The variable to get. - * @returns {Array|undefined} The range of the variable's identifier node. - */ - function getNameRange(variable) { - const def = variable.defs[0]; - - return def && def.name.range; - } - - /** - * Get declared line and column of a variable. - * @param {eslint-scope.Variable} variable The variable to get. - * @returns {Object} The declared line and column of the variable. - */ - function getDeclaredLocation(variable) { - const identifier = variable.identifiers[0]; - let obj; - - if (identifier) { - obj = { - global: false, - line: identifier.loc.start.line, - column: identifier.loc.start.column + 1 - }; - } else { - obj = { - global: true - }; - } - return obj; - } - - /** - * Checks if a variable is in TDZ of scopeVar. - * @param {Object} variable The variable to check. - * @param {Object} scopeVar The variable of TDZ. - * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. - */ - function isInTdz(variable, scopeVar) { - const outerDef = scopeVar.defs[0]; - const inner = getNameRange(variable); - const outer = getNameRange(scopeVar); - - return ( - inner && - outer && - inner[1] < outer[0] && - - // Excepts FunctionDeclaration if is {"hoist":"function"}. - (hoist !== "functions" || !outerDef || outerDef.node.type !== "FunctionDeclaration") - ); - } - - /** - * Checks the current context for shadowed variables. - * @param {Scope} scope Fixme - * @returns {void} - */ - function checkForShadows(scope) { - const variables = scope.variables; - - for (let i = 0; i < variables.length; ++i) { - const variable = variables[i]; - - // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. - if (variable.identifiers.length === 0 || - isDuplicatedClassNameVariable(variable) || - isAllowed(variable) - ) { - continue; - } - - // Gets shadowed variable. - const shadowed = astUtils.getVariableByName(scope.upper, variable.name); - - if (shadowed && - (shadowed.identifiers.length > 0 || (builtinGlobals && "writeable" in shadowed)) && - !isOnInitializer(variable, shadowed) && - !(ignoreOnInitialization && isInitPatternNode(variable, shadowed)) && - !(hoist !== "all" && isInTdz(variable, shadowed)) - ) { - const location = getDeclaredLocation(shadowed); - const messageId = location.global ? "noShadowGlobal" : "noShadow"; - const data = { name: variable.name }; - - if (!location.global) { - data.shadowedLine = location.line; - data.shadowedColumn = location.column; - } - context.report({ - node: variable.identifiers[0], - messageId, - data - }); - } - } - } - - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - const stack = globalScope.childScopes.slice(); - - while (stack.length) { - const scope = stack.pop(); - - stack.push(...scope.childScopes); - checkForShadows(scope); - } - } - }; - - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + allow: [], + builtinGlobals: false, + hoist: "functions", + ignoreOnInitialization: false, + }, + ], + + docs: { + description: + "Disallow variable declarations from shadowing variables declared in the outer scope", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-shadow", + }, + + schema: [ + { + type: "object", + properties: { + builtinGlobals: { type: "boolean" }, + hoist: { enum: ["all", "functions", "never"] }, + allow: { + type: "array", + items: { + type: "string", + }, + }, + ignoreOnInitialization: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + + messages: { + noShadow: + "'{{name}}' is already declared in the upper scope on line {{shadowedLine}} column {{shadowedColumn}}.", + noShadowGlobal: "'{{name}}' is already a global variable.", + }, + }, + + create(context) { + const [{ builtinGlobals, hoist, allow, ignoreOnInitialization }] = + context.options; + const sourceCode = context.sourceCode; + + /** + * Checks whether or not a given location is inside of the range of a given node. + * @param {ASTNode} node An node to check. + * @param {number} location A location to check. + * @returns {boolean} `true` if the location is inside of the range of the node. + */ + function isInRange(node, location) { + return ( + node && node.range[0] <= location && location <= node.range[1] + ); + } + + /** + * Searches from the current node through its ancestry to find a matching node. + * @param {ASTNode} node a node to get. + * @param {(node: ASTNode) => boolean} match a callback that checks whether or not the node verifies its condition or not. + * @returns {ASTNode|null} the matching node. + */ + function findSelfOrAncestor(node, match) { + let currentNode = node; + + while (currentNode && !match(currentNode)) { + currentNode = currentNode.parent; + } + return currentNode; + } + + /** + * Finds function's outer scope. + * @param {Scope} scope Function's own scope. + * @returns {Scope} Function's outer scope. + */ + function getOuterScope(scope) { + const upper = scope.upper; + + if (upper.type === "function-expression-name") { + return upper.upper; + } + return upper; + } + + /** + * Checks if a variable and a shadowedVariable have the same init pattern ancestor. + * @param {Object} variable a variable to check. + * @param {Object} shadowedVariable a shadowedVariable to check. + * @returns {boolean} Whether or not the variable and the shadowedVariable have the same init pattern ancestor. + */ + function isInitPatternNode(variable, shadowedVariable) { + const outerDef = shadowedVariable.defs[0]; + + if (!outerDef) { + return false; + } + + const { variableScope } = variable.scope; + + if ( + !( + FUNC_EXPR_NODE_TYPES.has(variableScope.block.type) && + getOuterScope(variableScope) === shadowedVariable.scope + ) + ) { + return false; + } + + const fun = variableScope.block; + const { parent } = fun; + + const callExpression = findSelfOrAncestor(parent, node => + CALL_EXPR_NODE_TYPE.has(node.type), + ); + + if (!callExpression) { + return false; + } + + let node = outerDef.name; + const location = callExpression.range[1]; + + while (node) { + if (node.type === "VariableDeclarator") { + if (isInRange(node.init, location)) { + return true; + } + if ( + FOR_IN_OF_TYPE.test(node.parent.parent.type) && + isInRange(node.parent.parent.right, location) + ) { + return true; + } + break; + } else if (node.type === "AssignmentPattern") { + if (isInRange(node.right, location)) { + return true; + } + } else if (SENTINEL_TYPE.test(node.type)) { + break; + } + + node = node.parent; + } + + return false; + } + + /** + * Check if variable name is allowed. + * @param {ASTNode} variable The variable to check. + * @returns {boolean} Whether or not the variable name is allowed. + */ + function isAllowed(variable) { + return allow.includes(variable.name); + } + + /** + * Checks if a variable of the class name in the class scope of ClassDeclaration. + * + * ClassDeclaration creates two variables of its name into its outer scope and its class scope. + * So we should ignore the variable in the class scope. + * @param {Object} variable The variable to check. + * @returns {boolean} Whether or not the variable of the class name in the class scope of ClassDeclaration. + */ + function isDuplicatedClassNameVariable(variable) { + const block = variable.scope.block; + + return ( + block.type === "ClassDeclaration" && + block.id === variable.identifiers[0] + ); + } + + /** + * Checks if a variable is inside the initializer of scopeVar. + * + * To avoid reporting at declarations such as `var a = function a() {};`. + * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The scope variable to look for. + * @returns {boolean} Whether or not the variable is inside initializer of scopeVar. + */ + function isOnInitializer(variable, scopeVar) { + const outerScope = scopeVar.scope; + const outerDef = scopeVar.defs[0]; + const outer = outerDef && outerDef.parent && outerDef.parent.range; + const innerScope = variable.scope; + const innerDef = variable.defs[0]; + const inner = innerDef && innerDef.name.range; + + return ( + outer && + inner && + outer[0] < inner[0] && + inner[1] < outer[1] && + ((innerDef.type === "FunctionName" && + innerDef.node.type === "FunctionExpression") || + innerDef.node.type === "ClassExpression") && + outerScope === innerScope.upper + ); + } + + /** + * Get a range of a variable's identifier node. + * @param {Object} variable The variable to get. + * @returns {Array|undefined} The range of the variable's identifier node. + */ + function getNameRange(variable) { + const def = variable.defs[0]; + + return def && def.name.range; + } + + /** + * Get declared line and column of a variable. + * @param {eslint-scope.Variable} variable The variable to get. + * @returns {Object} The declared line and column of the variable. + */ + function getDeclaredLocation(variable) { + const identifier = variable.identifiers[0]; + let obj; + + if (identifier) { + obj = { + global: false, + line: identifier.loc.start.line, + column: identifier.loc.start.column + 1, + }; + } else { + obj = { + global: true, + }; + } + return obj; + } + + /** + * Checks if a variable is in TDZ of scopeVar. + * @param {Object} variable The variable to check. + * @param {Object} scopeVar The variable of TDZ. + * @returns {boolean} Whether or not the variable is in TDZ of scopeVar. + */ + function isInTdz(variable, scopeVar) { + const outerDef = scopeVar.defs[0]; + const inner = getNameRange(variable); + const outer = getNameRange(scopeVar); + + return ( + inner && + outer && + inner[1] < outer[0] && + // Excepts FunctionDeclaration if is {"hoist":"function"}. + (hoist !== "functions" || + !outerDef || + outerDef.node.type !== "FunctionDeclaration") + ); + } + + /** + * Checks the current context for shadowed variables. + * @param {Scope} scope Fixme + * @returns {void} + */ + function checkForShadows(scope) { + const variables = scope.variables; + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + // Skips "arguments" or variables of a class name in the class scope of ClassDeclaration. + if ( + variable.identifiers.length === 0 || + isDuplicatedClassNameVariable(variable) || + isAllowed(variable) + ) { + continue; + } + + // Gets shadowed variable. + const shadowed = astUtils.getVariableByName( + scope.upper, + variable.name, + ); + + if ( + shadowed && + (shadowed.identifiers.length > 0 || + (builtinGlobals && "writeable" in shadowed)) && + !isOnInitializer(variable, shadowed) && + !( + ignoreOnInitialization && + isInitPatternNode(variable, shadowed) + ) && + !(hoist !== "all" && isInTdz(variable, shadowed)) + ) { + const location = getDeclaredLocation(shadowed); + const messageId = location.global + ? "noShadowGlobal" + : "noShadow"; + const data = { name: variable.name }; + + if (!location.global) { + data.shadowedLine = location.line; + data.shadowedColumn = location.column; + } + context.report({ + node: variable.identifiers[0], + messageId, + data, + }); + } + } + } + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + const stack = globalScope.childScopes.slice(); + + while (stack.length) { + const scope = stack.pop(); + + stack.push(...scope.childScopes); + checkForShadows(scope); + } + }, + }; + }, }; diff --git a/lib/rules/no-spaced-func.js b/lib/rules/no-spaced-func.js index a2103911bfa2..d47825404e2b 100644 --- a/lib/rules/no-spaced-func.js +++ b/lib/rules/no-spaced-func.js @@ -12,89 +12,94 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", + meta: { + type: "layout", - docs: { - description: "Disallow spacing between function identifiers and their applications (deprecated)", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-spaced-func" - }, + docs: { + description: + "Disallow spacing between function identifiers and their applications (deprecated)", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-spaced-func", + }, - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "function-call-spacing", - url: "https://eslint.style/rules/js/function-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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "function-call-spacing", + url: "https://eslint.style/rules/js/function-call-spacing", + }, + }, + ], + }, - fixable: "whitespace", - schema: [], + fixable: "whitespace", + schema: [], - messages: { - noSpacedFunction: "Unexpected space between function name and paren." - } - }, + messages: { + noSpacedFunction: + "Unexpected space between function name and paren.", + }, + }, - create(context) { + create(context) { + const sourceCode = context.sourceCode; - const sourceCode = context.sourceCode; + /** + * Check if open space is present in a function name + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function detectOpenSpaces(node) { + const lastCalleeToken = sourceCode.getLastToken(node.callee); + let prevToken = lastCalleeToken, + parenToken = sourceCode.getTokenAfter(lastCalleeToken); - /** - * Check if open space is present in a function name - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function detectOpenSpaces(node) { - const lastCalleeToken = sourceCode.getLastToken(node.callee); - let prevToken = lastCalleeToken, - parenToken = sourceCode.getTokenAfter(lastCalleeToken); + // advances to an open parenthesis. + while ( + parenToken && + parenToken.range[1] < node.range[1] && + parenToken.value !== "(" + ) { + prevToken = parenToken; + parenToken = sourceCode.getTokenAfter(parenToken); + } - // advances to an open parenthesis. - while ( - parenToken && - parenToken.range[1] < node.range[1] && - parenToken.value !== "(" - ) { - prevToken = parenToken; - parenToken = sourceCode.getTokenAfter(parenToken); - } + // look for a space between the callee and the open paren + if ( + parenToken && + parenToken.range[1] < node.range[1] && + sourceCode.isSpaceBetweenTokens(prevToken, parenToken) + ) { + context.report({ + node, + loc: lastCalleeToken.loc.start, + messageId: "noSpacedFunction", + fix(fixer) { + return fixer.removeRange([ + prevToken.range[1], + parenToken.range[0], + ]); + }, + }); + } + } - // look for a space between the callee and the open paren - if (parenToken && - parenToken.range[1] < node.range[1] && - sourceCode.isSpaceBetweenTokens(prevToken, parenToken) - ) { - context.report({ - node, - loc: lastCalleeToken.loc.start, - messageId: "noSpacedFunction", - fix(fixer) { - return fixer.removeRange([prevToken.range[1], parenToken.range[0]]); - } - }); - } - } - - return { - CallExpression: detectOpenSpaces, - NewExpression: detectOpenSpaces - }; - - } + return { + CallExpression: detectOpenSpaces, + NewExpression: detectOpenSpaces, + }; + }, }; diff --git a/lib/rules/no-sparse-arrays.js b/lib/rules/no-sparse-arrays.js index 6d24842fb247..698366852fc7 100644 --- a/lib/rules/no-sparse-arrays.js +++ b/lib/rules/no-sparse-arrays.js @@ -12,62 +12,57 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow sparse arrays", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-sparse-arrays" - }, - - schema: [], - - messages: { - unexpectedSparseArray: "Unexpected comma in middle of array." - } - }, - - create(context) { - - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - ArrayExpression(node) { - if (!node.elements.includes(null)) { - return; - } - - const { sourceCode } = context; - let commaToken; - - for (const [index, element] of node.elements.entries()) { - if (index === node.elements.length - 1 && element) { - return; - } - - commaToken = sourceCode.getTokenAfter( - element ?? commaToken ?? sourceCode.getFirstToken(node), - astUtils.isCommaToken - ); - - if (element) { - continue; - } - - context.report({ - node, - loc: commaToken.loc, - messageId: "unexpectedSparseArray" - }); - } - } - - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow sparse arrays", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-sparse-arrays", + }, + + schema: [], + + messages: { + unexpectedSparseArray: "Unexpected comma in middle of array.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ArrayExpression(node) { + if (!node.elements.includes(null)) { + return; + } + + const { sourceCode } = context; + let commaToken; + + for (const [index, element] of node.elements.entries()) { + if (index === node.elements.length - 1 && element) { + return; + } + + commaToken = sourceCode.getTokenAfter( + element ?? commaToken ?? sourceCode.getFirstToken(node), + astUtils.isCommaToken, + ); + + if (element) { + continue; + } + + context.report({ + node, + loc: commaToken.loc, + messageId: "unexpectedSparseArray", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js index b8f5c2cb2588..9d388e52e04d 100644 --- a/lib/rules/no-sync.js +++ b/lib/rules/no-sync.js @@ -12,69 +12,70 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - } - } - ] - }, + meta: { + 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", + type: "suggestion", - docs: { - description: "Disallow synchronous methods", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-sync" - }, + docs: { + description: "Disallow synchronous methods", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-sync", + }, - schema: [ - { - type: "object", - properties: { - allowAtRootLevel: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + allowAtRootLevel: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], - messages: { - noSync: "Unexpected sync method: '{{propertyName}}'." - } - }, + messages: { + noSync: "Unexpected sync method: '{{propertyName}}'.", + }, + }, - create(context) { - const selector = context.options[0] && context.options[0].allowAtRootLevel - ? ":function MemberExpression[property.name=/.*Sync$/]" - : "MemberExpression[property.name=/.*Sync$/]"; + create(context) { + const selector = + context.options[0] && context.options[0].allowAtRootLevel + ? ":function MemberExpression[property.name=/.*Sync$/]" + : "MemberExpression[property.name=/.*Sync$/]"; - return { - [selector](node) { - context.report({ - node, - messageId: "noSync", - data: { - propertyName: node.property.name - } - }); - } - }; - - } + return { + [selector](node) { + context.report({ + node, + messageId: "noSync", + data: { + propertyName: node.property.name, + }, + }); + }, + }; + }, }; diff --git a/lib/rules/no-tabs.js b/lib/rules/no-tabs.js index e38262fbdff0..77a8bde769e2 100644 --- a/lib/rules/no-tabs.js +++ b/lib/rules/no-tabs.js @@ -19,81 +19,92 @@ const anyNonWhitespaceRegex = /\S/u; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-tabs", - url: "https://eslint.style/rules/js/no-tabs" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-tabs", + url: "https://eslint.style/rules/js/no-tabs", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Disallow all tabs", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-tabs" - }, - schema: [{ - type: "object", - properties: { - allowIndentationTabs: { - type: "boolean", - default: false - } - }, - additionalProperties: false - }], + docs: { + description: "Disallow all tabs", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-tabs", + }, + schema: [ + { + type: "object", + properties: { + allowIndentationTabs: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], - messages: { - unexpectedTab: "Unexpected tab character." - } - }, + messages: { + unexpectedTab: "Unexpected tab character.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; - const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs; + create(context) { + const sourceCode = context.sourceCode; + const allowIndentationTabs = + context.options && + context.options[0] && + context.options[0].allowIndentationTabs; - return { - Program(node) { - sourceCode.getLines().forEach((line, index) => { - let match; + return { + Program(node) { + sourceCode.getLines().forEach((line, index) => { + let match; - while ((match = tabRegex.exec(line)) !== null) { - if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) { - continue; - } + while ((match = tabRegex.exec(line)) !== null) { + if ( + allowIndentationTabs && + !anyNonWhitespaceRegex.test( + line.slice(0, match.index), + ) + ) { + continue; + } - context.report({ - node, - loc: { - start: { - line: index + 1, - column: match.index - }, - end: { - line: index + 1, - column: match.index + match[0].length - } - }, - messageId: "unexpectedTab" - }); - } - }); - } - }; - } + context.report({ + node, + loc: { + start: { + line: index + 1, + column: match.index, + }, + end: { + line: index + 1, + column: match.index + match[0].length, + }, + }, + messageId: "unexpectedTab", + }); + } + }); + }, + }; + }, }; diff --git a/lib/rules/no-template-curly-in-string.js b/lib/rules/no-template-curly-in-string.js index 92b4c1c86cde..c9b45672e8ea 100644 --- a/lib/rules/no-template-curly-in-string.js +++ b/lib/rules/no-template-curly-in-string.js @@ -10,35 +10,36 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow template literal placeholder syntax in regular strings", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-template-curly-in-string" - }, - - schema: [], - - messages: { - unexpectedTemplateExpression: "Unexpected template string expression." - } - }, - - create(context) { - const regex = /\$\{[^}]+\}/u; - - return { - Literal(node) { - if (typeof node.value === "string" && regex.test(node.value)) { - context.report({ - node, - messageId: "unexpectedTemplateExpression" - }); - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: + "Disallow template literal placeholder syntax in regular strings", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-template-curly-in-string", + }, + + schema: [], + + messages: { + unexpectedTemplateExpression: + "Unexpected template string expression.", + }, + }, + + create(context) { + const regex = /\$\{[^}]+\}/u; + + return { + Literal(node) { + if (typeof node.value === "string" && regex.test(node.value)) { + context.report({ + node, + messageId: "unexpectedTemplateExpression", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-ternary.js b/lib/rules/no-ternary.js index 26c00ff041ac..95eb65775476 100644 --- a/lib/rules/no-ternary.js +++ b/lib/rules/no-ternary.js @@ -11,32 +11,28 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow ternary operators", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-ternary" - }, - - schema: [], - - messages: { - noTernaryOperator: "Ternary operator used." - } - }, - - create(context) { - - return { - - ConditionalExpression(node) { - context.report({ node, messageId: "noTernaryOperator" }); - } - - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow ternary operators", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-ternary", + }, + + schema: [], + + messages: { + noTernaryOperator: "Ternary operator used.", + }, + }, + + create(context) { + return { + ConditionalExpression(node) { + context.report({ node, messageId: "noTernaryOperator" }); + }, + }; + }, }; diff --git a/lib/rules/no-this-before-super.js b/lib/rules/no-this-before-super.js index 01e355acbd14..6709b00031f1 100644 --- a/lib/rules/no-this-before-super.js +++ b/lib/rules/no-this-before-super.js @@ -23,11 +23,11 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is a constructor. */ function isConstructorFunction(node) { - return ( - node.type === "FunctionExpression" && - node.parent.type === "MethodDefinition" && - node.parent.kind === "constructor" - ); + return ( + node.type === "FunctionExpression" && + node.parent.type === "MethodDefinition" && + node.parent.kind === "constructor" + ); } /* @@ -39,18 +39,17 @@ function isConstructorFunction(node) { * */ class SegmentInfo { - - /** - * Indicates whether `super()` is called in all code paths. - * @type {boolean} - */ - superCalled = false; - - /** - * The array of invalid ThisExpression and Super nodes. - * @type {ASTNode[]} - */ - invalidNodes = []; + /** + * Indicates whether `super()` is called in all code paths. + * @type {boolean} + */ + superCalled = false; + + /** + * The array of invalid ThisExpression and Super nodes. + * @type {ASTNode[]} + */ + invalidNodes = []; } //------------------------------------------------------------------------------ @@ -59,305 +58,308 @@ class SegmentInfo { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow `this`/`super` before calling `super()` in constructors", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-this-before-super" - }, - - schema: [], - - messages: { - noBeforeSuper: "'{{kind}}' is not allowed before 'super()'." - } - }, - - create(context) { - - /* - * Information for each constructor. - * - upper: Information of the upper constructor. - * - hasExtends: A flag which shows whether the owner class has a valid - * `extends` part. - * - scope: The scope of the owner class. - * - codePath: The code path of this constructor. - */ - let funcInfo = null; - - /** @type {Record} */ - let segInfoMap = Object.create(null); - - /** - * Gets whether or not `super()` is called in a given code path segment. - * @param {CodePathSegment} segment A code path segment to get. - * @returns {boolean} `true` if `super()` is called. - */ - function isCalled(segment) { - return !segment.reachable || segInfoMap[segment.id]?.superCalled; - } - - /** - * Checks whether or not this is in a constructor. - * @returns {boolean} `true` if this is in a constructor. - */ - function isInConstructorOfDerivedClass() { - return Boolean(funcInfo && funcInfo.isConstructor && funcInfo.hasExtends); - } - - /** - * Determines if every segment in a set has been called. - * @param {Set} segments The segments to search. - * @returns {boolean} True if every segment has been called; false otherwise. - */ - function isEverySegmentCalled(segments) { - for (const segment of segments) { - if (!isCalled(segment)) { - return false; - } - } - - return true; - } - - /** - * Checks whether or not this is before `super()` is called. - * @returns {boolean} `true` if this is before `super()` is called. - */ - function isBeforeCallOfSuper() { - return ( - isInConstructorOfDerivedClass() && - !isEverySegmentCalled(funcInfo.currentSegments) - ); - } - - /** - * Sets a given node as invalid. - * @param {ASTNode} node A node to set as invalid. This is one of - * a ThisExpression and a Super. - * @returns {void} - */ - function setInvalid(node) { - const segments = funcInfo.currentSegments; - - for (const segment of segments) { - if (segment.reachable) { - segInfoMap[segment.id].invalidNodes.push(node); - } - } - } - - /** - * Sets the current segment as `super` was called. - * @returns {void} - */ - function setSuperCalled() { - const segments = funcInfo.currentSegments; - - for (const segment of segments) { - if (segment.reachable) { - segInfoMap[segment.id].superCalled = true; - } - } - } - - return { - - /** - * Adds information of a constructor into the stack. - * @param {CodePath} codePath A code path which was started. - * @param {ASTNode} node The current node. - * @returns {void} - */ - onCodePathStart(codePath, node) { - if (isConstructorFunction(node)) { - - // Class > ClassBody > MethodDefinition > FunctionExpression - const classNode = node.parent.parent.parent; - - funcInfo = { - upper: funcInfo, - isConstructor: true, - hasExtends: Boolean( - classNode.superClass && - !astUtils.isNullOrUndefined(classNode.superClass) - ), - codePath, - currentSegments: new Set() - }; - } else { - funcInfo = { - upper: funcInfo, - isConstructor: false, - hasExtends: false, - codePath, - currentSegments: new Set() - }; - } - }, - - /** - * Removes the top of stack item. - * - * And this traverses all segments of this code path then reports every - * invalid node. - * @param {CodePath} codePath A code path which was ended. - * @returns {void} - */ - onCodePathEnd(codePath) { - const isDerivedClass = funcInfo.hasExtends; - - funcInfo = funcInfo.upper; - if (!isDerivedClass) { - return; - } - - /** - * A collection of nodes to avoid duplicate reports. - * @type {Set} - */ - const reported = new Set(); - - codePath.traverseSegments((segment, controller) => { - const info = segInfoMap[segment.id]; - const invalidNodes = info.invalidNodes - .filter( - - /* - * Avoid duplicate reports. - * When there is a `finally`, invalidNodes may contain already reported node. - */ - node => !reported.has(node) - ); - - for (const invalidNode of invalidNodes) { - reported.add(invalidNode); - - context.report({ - messageId: "noBeforeSuper", - node: invalidNode, - data: { - kind: invalidNode.type === "Super" ? "super" : "this" - } - }); - } - - if (info.superCalled) { - controller.skip(); - } - }); - }, - - /** - * Initialize information of a given code path segment. - * @param {CodePathSegment} segment A code path segment to initialize. - * @returns {void} - */ - onCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - - if (!isInConstructorOfDerivedClass()) { - return; - } - - // Initialize info. - segInfoMap[segment.id] = { - superCalled: ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ), - invalidNodes: [] - }; - }, - - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - /** - * Update information of the code path segment when a code path was - * looped. - * @param {CodePathSegment} fromSegment The code path segment of the - * end of a loop. - * @param {CodePathSegment} toSegment A code path segment of the head - * of a loop. - * @returns {void} - */ - onCodePathSegmentLoop(fromSegment, toSegment) { - if (!isInConstructorOfDerivedClass()) { - return; - } - - // Update information inside of the loop. - funcInfo.codePath.traverseSegments( - { first: toSegment, last: fromSegment }, - (segment, controller) => { - const info = segInfoMap[segment.id] ?? new SegmentInfo(); - - if (info.superCalled) { - controller.skip(); - } else if ( - segment.prevSegments.length > 0 && - segment.prevSegments.every(isCalled) - ) { - info.superCalled = true; - } - - segInfoMap[segment.id] = info; - } - ); - }, - - /** - * Reports if this is before `super()`. - * @param {ASTNode} node A target node. - * @returns {void} - */ - ThisExpression(node) { - if (isBeforeCallOfSuper()) { - setInvalid(node); - } - }, - - /** - * Reports if this is before `super()`. - * @param {ASTNode} node A target node. - * @returns {void} - */ - Super(node) { - if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { - setInvalid(node); - } - }, - - /** - * Marks `super()` called. - * @param {ASTNode} node A target node. - * @returns {void} - */ - "CallExpression:exit"(node) { - if (node.callee.type === "Super" && isBeforeCallOfSuper()) { - setSuperCalled(); - } - }, - - /** - * Resets state. - * @returns {void} - */ - "Program:exit"() { - segInfoMap = Object.create(null); - } - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow `this`/`super` before calling `super()` in constructors", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-this-before-super", + }, + + schema: [], + + messages: { + noBeforeSuper: "'{{kind}}' is not allowed before 'super()'.", + }, + }, + + create(context) { + /* + * Information for each constructor. + * - upper: Information of the upper constructor. + * - hasExtends: A flag which shows whether the owner class has a valid + * `extends` part. + * - scope: The scope of the owner class. + * - codePath: The code path of this constructor. + */ + let funcInfo = null; + + /** @type {Record} */ + let segInfoMap = Object.create(null); + + /** + * Gets whether or not `super()` is called in a given code path segment. + * @param {CodePathSegment} segment A code path segment to get. + * @returns {boolean} `true` if `super()` is called. + */ + function isCalled(segment) { + return !segment.reachable || segInfoMap[segment.id]?.superCalled; + } + + /** + * Checks whether or not this is in a constructor. + * @returns {boolean} `true` if this is in a constructor. + */ + function isInConstructorOfDerivedClass() { + return Boolean( + funcInfo && funcInfo.isConstructor && funcInfo.hasExtends, + ); + } + + /** + * Determines if every segment in a set has been called. + * @param {Set} segments The segments to search. + * @returns {boolean} True if every segment has been called; false otherwise. + */ + function isEverySegmentCalled(segments) { + for (const segment of segments) { + if (!isCalled(segment)) { + return false; + } + } + + return true; + } + + /** + * Checks whether or not this is before `super()` is called. + * @returns {boolean} `true` if this is before `super()` is called. + */ + function isBeforeCallOfSuper() { + return ( + isInConstructorOfDerivedClass() && + !isEverySegmentCalled(funcInfo.currentSegments) + ); + } + + /** + * Sets a given node as invalid. + * @param {ASTNode} node A node to set as invalid. This is one of + * a ThisExpression and a Super. + * @returns {void} + */ + function setInvalid(node) { + const segments = funcInfo.currentSegments; + + for (const segment of segments) { + if (segment.reachable) { + segInfoMap[segment.id].invalidNodes.push(node); + } + } + } + + /** + * Sets the current segment as `super` was called. + * @returns {void} + */ + function setSuperCalled() { + const segments = funcInfo.currentSegments; + + for (const segment of segments) { + if (segment.reachable) { + segInfoMap[segment.id].superCalled = true; + } + } + } + + return { + /** + * Adds information of a constructor into the stack. + * @param {CodePath} codePath A code path which was started. + * @param {ASTNode} node The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + if (isConstructorFunction(node)) { + // Class > ClassBody > MethodDefinition > FunctionExpression + const classNode = node.parent.parent.parent; + + funcInfo = { + upper: funcInfo, + isConstructor: true, + hasExtends: Boolean( + classNode.superClass && + !astUtils.isNullOrUndefined( + classNode.superClass, + ), + ), + codePath, + currentSegments: new Set(), + }; + } else { + funcInfo = { + upper: funcInfo, + isConstructor: false, + hasExtends: false, + codePath, + currentSegments: new Set(), + }; + } + }, + + /** + * Removes the top of stack item. + * + * And this traverses all segments of this code path then reports every + * invalid node. + * @param {CodePath} codePath A code path which was ended. + * @returns {void} + */ + onCodePathEnd(codePath) { + const isDerivedClass = funcInfo.hasExtends; + + funcInfo = funcInfo.upper; + if (!isDerivedClass) { + return; + } + + /** + * A collection of nodes to avoid duplicate reports. + * @type {Set} + */ + const reported = new Set(); + + codePath.traverseSegments((segment, controller) => { + const info = segInfoMap[segment.id]; + const invalidNodes = info.invalidNodes.filter( + /* + * Avoid duplicate reports. + * When there is a `finally`, invalidNodes may contain already reported node. + */ + node => !reported.has(node), + ); + + for (const invalidNode of invalidNodes) { + reported.add(invalidNode); + + context.report({ + messageId: "noBeforeSuper", + node: invalidNode, + data: { + kind: + invalidNode.type === "Super" + ? "super" + : "this", + }, + }); + } + + if (info.superCalled) { + controller.skip(); + } + }); + }, + + /** + * Initialize information of a given code path segment. + * @param {CodePathSegment} segment A code path segment to initialize. + * @returns {void} + */ + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + + if (!isInConstructorOfDerivedClass()) { + return; + } + + // Initialize info. + segInfoMap[segment.id] = { + superCalled: + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled), + invalidNodes: [], + }; + }, + + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + /** + * Update information of the code path segment when a code path was + * looped. + * @param {CodePathSegment} fromSegment The code path segment of the + * end of a loop. + * @param {CodePathSegment} toSegment A code path segment of the head + * of a loop. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment) { + if (!isInConstructorOfDerivedClass()) { + return; + } + + // Update information inside of the loop. + funcInfo.codePath.traverseSegments( + { first: toSegment, last: fromSegment }, + (segment, controller) => { + const info = + segInfoMap[segment.id] ?? new SegmentInfo(); + + if (info.superCalled) { + controller.skip(); + } else if ( + segment.prevSegments.length > 0 && + segment.prevSegments.every(isCalled) + ) { + info.superCalled = true; + } + + segInfoMap[segment.id] = info; + }, + ); + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node A target node. + * @returns {void} + */ + ThisExpression(node) { + if (isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Reports if this is before `super()`. + * @param {ASTNode} node A target node. + * @returns {void} + */ + Super(node) { + if (!astUtils.isCallee(node) && isBeforeCallOfSuper()) { + setInvalid(node); + } + }, + + /** + * Marks `super()` called. + * @param {ASTNode} node A target node. + * @returns {void} + */ + "CallExpression:exit"(node) { + if (node.callee.type === "Super" && isBeforeCallOfSuper()) { + setSuperCalled(); + } + }, + + /** + * Resets state. + * @returns {void} + */ + "Program:exit"() { + segInfoMap = Object.create(null); + }, + }; + }, }; diff --git a/lib/rules/no-throw-literal.js b/lib/rules/no-throw-literal.js index 07a0df62dff7..c93c2aa92d1a 100644 --- a/lib/rules/no-throw-literal.js +++ b/lib/rules/no-throw-literal.js @@ -13,39 +13,34 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow throwing literals as exceptions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-throw-literal" - }, - - schema: [], - - messages: { - object: "Expected an error object to be thrown.", - undef: "Do not throw undefined." - } - }, - - create(context) { - - return { - - ThrowStatement(node) { - if (!astUtils.couldBeError(node.argument)) { - context.report({ node, messageId: "object" }); - } else if (node.argument.type === "Identifier") { - if (node.argument.name === "undefined") { - context.report({ node, messageId: "undef" }); - } - } - - } - - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow throwing literals as exceptions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-throw-literal", + }, + + schema: [], + + messages: { + object: "Expected an error object to be thrown.", + undef: "Do not throw undefined.", + }, + }, + + create(context) { + return { + ThrowStatement(node) { + if (!astUtils.couldBeError(node.argument)) { + context.report({ node, messageId: "object" }); + } else if (node.argument.type === "Identifier") { + if (node.argument.name === "undefined") { + context.report({ node, messageId: "undef" }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-trailing-spaces.js b/lib/rules/no-trailing-spaces.js index 7cdc310f95fc..972a46f6f7c3 100644 --- a/lib/rules/no-trailing-spaces.js +++ b/lib/rules/no-trailing-spaces.js @@ -17,194 +17,202 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-trailing-spaces", - url: "https://eslint.style/rules/js/no-trailing-spaces" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow trailing whitespace at the end of lines", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-trailing-spaces" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - skipBlankLines: { - type: "boolean", - default: false - }, - ignoreComments: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - messages: { - trailingSpace: "Trailing spaces not allowed." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", - SKIP_BLANK = `^${BLANK_CLASS}*$`, - NONBLANK = `${BLANK_CLASS}+$`; - - const options = context.options[0] || {}, - skipBlankLines = options.skipBlankLines || false, - ignoreComments = options.ignoreComments || false; - - /** - * Report the error message - * @param {ASTNode} node node to report - * @param {int[]} location range information - * @param {int[]} fixRange Range based on the whole program - * @returns {void} - */ - function report(node, location, fixRange) { - - /* - * Passing node is a bit dirty, because message data will contain big - * text in `source`. But... who cares :) ? - * One more kludge will not make worse the bloody wizardry of this - * plugin. - */ - context.report({ - node, - loc: location, - messageId: "trailingSpace", - fix(fixer) { - return fixer.removeRange(fixRange); - } - }); - } - - /** - * Given a list of comment nodes, return the line numbers for those comments. - * @param {Array} comments An array of comment nodes. - * @returns {number[]} An array of line numbers containing comments. - */ - function getCommentLineNumbers(comments) { - const lines = new Set(); - - comments.forEach(comment => { - const endLine = comment.type === "Block" - ? comment.loc.end.line - 1 - : comment.loc.end.line; - - for (let i = comment.loc.start.line; i <= endLine; i++) { - lines.add(i); - } - }); - - return lines; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - Program: function checkTrailingSpaces(node) { - - /* - * Let's hack. Since Espree does not return whitespace nodes, - * fetch the source code and do matching via regexps. - */ - - const re = new RegExp(NONBLANK, "u"), - skipMatch = new RegExp(SKIP_BLANK, "u"), - lines = sourceCode.lines, - linebreaks = sourceCode.getText().match(astUtils.createGlobalLinebreakMatcher()), - comments = sourceCode.getAllComments(), - commentLineNumbers = getCommentLineNumbers(comments); - - let totalLength = 0; - - for (let i = 0, ii = lines.length; i < ii; i++) { - const lineNumber = i + 1; - - /* - * Always add linebreak length to line length to accommodate for line break (\n or \r\n) - * Because during the fix time they also reserve one spot in the array. - * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) - */ - const linebreakLength = linebreaks && linebreaks[i] ? linebreaks[i].length : 1; - const lineLength = lines[i].length + linebreakLength; - - const matches = re.exec(lines[i]); - - if (matches) { - const location = { - start: { - line: lineNumber, - column: matches.index - }, - end: { - line: lineNumber, - column: lineLength - linebreakLength - } - }; - - const rangeStart = totalLength + location.start.column; - const rangeEnd = totalLength + location.end.column; - const containingNode = sourceCode.getNodeByRangeIndex(rangeStart); - - if (containingNode && containingNode.type === "TemplateElement" && - rangeStart > containingNode.parent.range[0] && - rangeEnd < containingNode.parent.range[1]) { - totalLength += lineLength; - continue; - } - - /* - * If the line has only whitespace, and skipBlankLines - * is true, don't report it - */ - if (skipBlankLines && skipMatch.test(lines[i])) { - totalLength += lineLength; - continue; - } - - const fixRange = [rangeStart, rangeEnd]; - - if (!ignoreComments || !commentLineNumbers.has(lineNumber)) { - report(node, location, fixRange); - } - } - - totalLength += lineLength; - } - } - - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-trailing-spaces", + url: "https://eslint.style/rules/js/no-trailing-spaces", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow trailing whitespace at the end of lines", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-trailing-spaces", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + skipBlankLines: { + type: "boolean", + default: false, + }, + ignoreComments: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + trailingSpace: "Trailing spaces not allowed.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const BLANK_CLASS = "[ \t\u00a0\u2000-\u200b\u3000]", + SKIP_BLANK = `^${BLANK_CLASS}*$`, + NONBLANK = `${BLANK_CLASS}+$`; + + const options = context.options[0] || {}, + skipBlankLines = options.skipBlankLines || false, + ignoreComments = options.ignoreComments || false; + + /** + * Report the error message + * @param {ASTNode} node node to report + * @param {int[]} location range information + * @param {int[]} fixRange Range based on the whole program + * @returns {void} + */ + function report(node, location, fixRange) { + /* + * Passing node is a bit dirty, because message data will contain big + * text in `source`. But... who cares :) ? + * One more kludge will not make worse the bloody wizardry of this + * plugin. + */ + context.report({ + node, + loc: location, + messageId: "trailingSpace", + fix(fixer) { + return fixer.removeRange(fixRange); + }, + }); + } + + /** + * Given a list of comment nodes, return the line numbers for those comments. + * @param {Array} comments An array of comment nodes. + * @returns {number[]} An array of line numbers containing comments. + */ + function getCommentLineNumbers(comments) { + const lines = new Set(); + + comments.forEach(comment => { + const endLine = + comment.type === "Block" + ? comment.loc.end.line - 1 + : comment.loc.end.line; + + for (let i = comment.loc.start.line; i <= endLine; i++) { + lines.add(i); + } + }); + + return lines; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkTrailingSpaces(node) { + /* + * Let's hack. Since Espree does not return whitespace nodes, + * fetch the source code and do matching via regexps. + */ + + const re = new RegExp(NONBLANK, "u"), + skipMatch = new RegExp(SKIP_BLANK, "u"), + lines = sourceCode.lines, + linebreaks = sourceCode + .getText() + .match(astUtils.createGlobalLinebreakMatcher()), + comments = sourceCode.getAllComments(), + commentLineNumbers = getCommentLineNumbers(comments); + + let totalLength = 0; + + for (let i = 0, ii = lines.length; i < ii; i++) { + const lineNumber = i + 1; + + /* + * Always add linebreak length to line length to accommodate for line break (\n or \r\n) + * Because during the fix time they also reserve one spot in the array. + * Usually linebreak length is 2 for \r\n (CRLF) and 1 for \n (LF) + */ + const linebreakLength = + linebreaks && linebreaks[i] ? linebreaks[i].length : 1; + const lineLength = lines[i].length + linebreakLength; + + const matches = re.exec(lines[i]); + + if (matches) { + const location = { + start: { + line: lineNumber, + column: matches.index, + }, + end: { + line: lineNumber, + column: lineLength - linebreakLength, + }, + }; + + const rangeStart = totalLength + location.start.column; + const rangeEnd = totalLength + location.end.column; + const containingNode = + sourceCode.getNodeByRangeIndex(rangeStart); + + if ( + containingNode && + containingNode.type === "TemplateElement" && + rangeStart > containingNode.parent.range[0] && + rangeEnd < containingNode.parent.range[1] + ) { + totalLength += lineLength; + continue; + } + + /* + * If the line has only whitespace, and skipBlankLines + * is true, don't report it + */ + if (skipBlankLines && skipMatch.test(lines[i])) { + totalLength += lineLength; + continue; + } + + const fixRange = [rangeStart, rangeEnd]; + + if ( + !ignoreComments || + !commentLineNumbers.has(lineNumber) + ) { + report(node, location, fixRange); + } + } + + totalLength += lineLength; + } + }, + }; + }, }; diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js index e16793bc3de0..41fc08c55f6d 100644 --- a/lib/rules/no-undef-init.js +++ b/lib/rules/no-undef-init.js @@ -13,64 +13,79 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow initializing variables to `undefined`", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-undef-init" - }, - - schema: [], - fixable: "code", - - messages: { - unnecessaryUndefinedInit: "It's not necessary to initialize '{{name}}' to undefined." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - return { - - VariableDeclarator(node) { - const name = sourceCode.getText(node.id), - init = node.init && node.init.name, - scope = sourceCode.getScope(node), - undefinedVar = astUtils.getVariableByName(scope, "undefined"), - shadowed = undefinedVar && undefinedVar.defs.length > 0, - lastToken = sourceCode.getLastToken(node); - - if (init === "undefined" && node.parent.kind !== "const" && !shadowed) { - context.report({ - node, - messageId: "unnecessaryUndefinedInit", - data: { name }, - fix(fixer) { - if (node.parent.kind === "var") { - return null; - } - - if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") { - - // Don't fix destructuring assignment to `undefined`. - return null; - } - - if (sourceCode.commentsExistBetween(node.id, lastToken)) { - return null; - } - - return fixer.removeRange([node.id.range[1], node.range[1]]); - } - }); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow initializing variables to `undefined`", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-undef-init", + }, + + schema: [], + fixable: "code", + + messages: { + unnecessaryUndefinedInit: + "It's not necessary to initialize '{{name}}' to undefined.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + VariableDeclarator(node) { + const name = sourceCode.getText(node.id), + init = node.init && node.init.name, + scope = sourceCode.getScope(node), + undefinedVar = astUtils.getVariableByName( + scope, + "undefined", + ), + shadowed = undefinedVar && undefinedVar.defs.length > 0, + lastToken = sourceCode.getLastToken(node); + + if ( + init === "undefined" && + node.parent.kind !== "const" && + !shadowed + ) { + context.report({ + node, + messageId: "unnecessaryUndefinedInit", + data: { name }, + fix(fixer) { + if (node.parent.kind === "var") { + return null; + } + + if ( + node.id.type === "ArrayPattern" || + node.id.type === "ObjectPattern" + ) { + // Don't fix destructuring assignment to `undefined`. + return null; + } + + if ( + sourceCode.commentsExistBetween( + node.id, + lastToken, + ) + ) { + return null; + } + + return fixer.removeRange([ + node.id.range[1], + node.range[1], + ]); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-undef.js b/lib/rules/no-undef.js index b11a3d43566e..0c3148479258 100644 --- a/lib/rules/no-undef.js +++ b/lib/rules/no-undef.js @@ -14,9 +14,9 @@ * @returns {boolean} Whether or not the node is the argument of a typeof operator. */ function hasTypeOfOperator(node) { - const parent = node.parent; + const parent = node.parent; - return parent.type === "UnaryExpression" && parent.operator === "typeof"; + return parent.type === "UnaryExpression" && parent.operator === "typeof"; } //------------------------------------------------------------------------------ @@ -25,57 +25,60 @@ function hasTypeOfOperator(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - defaultOptions: [{ - typeof: false - }], + defaultOptions: [ + { + typeof: false, + }, + ], - docs: { - description: "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-undef" - }, + docs: { + description: + "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-undef", + }, - schema: [ - { - type: "object", - properties: { - typeof: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - messages: { - undef: "'{{name}}' is not defined." - } - }, + schema: [ + { + type: "object", + properties: { + typeof: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + undef: "'{{name}}' is not defined.", + }, + }, - create(context) { - const [{ typeof: considerTypeOf }] = context.options; - const sourceCode = context.sourceCode; + create(context) { + const [{ typeof: considerTypeOf }] = context.options; + const sourceCode = context.sourceCode; - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); - globalScope.through.forEach(ref => { - const identifier = ref.identifier; + globalScope.through.forEach(ref => { + const identifier = ref.identifier; - if (!considerTypeOf && hasTypeOfOperator(identifier)) { - return; - } + if (!considerTypeOf && hasTypeOfOperator(identifier)) { + return; + } - context.report({ - node: identifier, - messageId: "undef", - data: identifier - }); - }); - } - }; - } + context.report({ + node: identifier, + messageId: "undef", + data: identifier, + }); + }); + }, + }; + }, }; diff --git a/lib/rules/no-undefined.js b/lib/rules/no-undefined.js index 4fa769915961..b294add30531 100644 --- a/lib/rules/no-undefined.js +++ b/lib/rules/no-undefined.js @@ -10,78 +10,76 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow the use of `undefined` as an identifier", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-undefined" - }, - - schema: [], - - messages: { - unexpectedUndefined: "Unexpected use of undefined." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Report an invalid "undefined" identifier node. - * @param {ASTNode} node The node to report. - * @returns {void} - */ - function report(node) { - context.report({ - node, - messageId: "unexpectedUndefined" - }); - } - - /** - * Checks the given scope for references to `undefined` and reports - * all references found. - * @param {eslint-scope.Scope} scope The scope to check. - * @returns {void} - */ - function checkScope(scope) { - const undefinedVar = scope.set.get("undefined"); - - if (!undefinedVar) { - return; - } - - const references = undefinedVar.references; - - const defs = undefinedVar.defs; - - // Report non-initializing references (those are covered in defs below) - references - .filter(ref => !ref.init) - .forEach(ref => report(ref.identifier)); - - defs.forEach(def => report(def.name)); - } - - return { - "Program:exit"(node) { - const globalScope = sourceCode.getScope(node); - - const stack = [globalScope]; - - while (stack.length) { - const scope = stack.pop(); - - stack.push(...scope.childScopes); - checkScope(scope); - } - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow the use of `undefined` as an identifier", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-undefined", + }, + + schema: [], + + messages: { + unexpectedUndefined: "Unexpected use of undefined.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Report an invalid "undefined" identifier node. + * @param {ASTNode} node The node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "unexpectedUndefined", + }); + } + + /** + * Checks the given scope for references to `undefined` and reports + * all references found. + * @param {eslint-scope.Scope} scope The scope to check. + * @returns {void} + */ + function checkScope(scope) { + const undefinedVar = scope.set.get("undefined"); + + if (!undefinedVar) { + return; + } + + const references = undefinedVar.references; + + const defs = undefinedVar.defs; + + // Report non-initializing references (those are covered in defs below) + references + .filter(ref => !ref.init) + .forEach(ref => report(ref.identifier)); + + defs.forEach(def => report(def.name)); + } + + return { + "Program:exit"(node) { + const globalScope = sourceCode.getScope(node); + + const stack = [globalScope]; + + while (stack.length) { + const scope = stack.pop(); + + stack.push(...scope.childScopes); + checkScope(scope); + } + }, + }; + }, }; diff --git a/lib/rules/no-underscore-dangle.js b/lib/rules/no-underscore-dangle.js index 702027d78775..7c34cc89e80e 100644 --- a/lib/rules/no-underscore-dangle.js +++ b/lib/rules/no-underscore-dangle.js @@ -11,330 +11,373 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - allow: { - type: "array", - items: { - type: "string" - } - }, - allowAfterThis: { - type: "boolean" - }, - allowAfterSuper: { - type: "boolean" - }, - allowAfterThisConstructor: { - type: "boolean" - }, - enforceInMethodNames: { - type: "boolean" - }, - allowFunctionParams: { - type: "boolean" - }, - enforceInClassFields: { - type: "boolean" - }, - allowInArrayDestructuring: { - type: "boolean" - }, - allowInObjectDestructuring: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedUnderscore: "Unexpected dangling '_' in '{{identifier}}'." - } - }, - - create(context) { - const [{ - allow, - allowAfterSuper, - allowAfterThis, - allowAfterThisConstructor, - allowFunctionParams, - allowInArrayDestructuring, - allowInObjectDestructuring, - enforceInClassFields, - enforceInMethodNames - }] = context.options; - const sourceCode = context.sourceCode; - - //------------------------------------------------------------------------- - // Helpers - //------------------------------------------------------------------------- - - /** - * Check if identifier is present inside the allowed option - * @param {string} identifier name of the node - * @returns {boolean} true if its is present - * @private - */ - function isAllowed(identifier) { - return allow.includes(identifier); - } - - /** - * Check if identifier has a dangling underscore - * @param {string} identifier name of the node - * @returns {boolean} true if its is present - * @private - */ - function hasDanglingUnderscore(identifier) { - const len = identifier.length; - - return identifier !== "_" && (identifier[0] === "_" || identifier[len - 1] === "_"); - } - - /** - * Check if identifier is a special case member expression - * @param {string} identifier name of the node - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierForMemberExpression(identifier) { - return identifier === "__proto__"; - } - - /** - * Check if identifier is a special case variable expression - * @param {string} identifier name of the node - * @returns {boolean} true if its is a special case - * @private - */ - function isSpecialCaseIdentifierInVariableExpression(identifier) { - - // Checks for the underscore library usage here - return identifier === "_"; - } - - /** - * Check if a node is a member reference of this.constructor - * @param {ASTNode} node node to evaluate - * @returns {boolean} true if it is a reference on this.constructor - * @private - */ - function isThisConstructorReference(node) { - return node.object.type === "MemberExpression" && - node.object.property.name === "constructor" && - node.object.object.type === "ThisExpression"; - } - - /** - * Check if function parameter has a dangling underscore. - * @param {ASTNode} node function node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInFunctionParameters(node) { - if (!allowFunctionParams) { - node.params.forEach(param => { - const { type } = param; - let nodeToCheck; - - if (type === "RestElement") { - nodeToCheck = param.argument; - } else if (type === "AssignmentPattern") { - nodeToCheck = param.left; - } else { - nodeToCheck = param; - } - - if (nodeToCheck.type === "Identifier") { - const identifier = nodeToCheck.name; - - if (hasDanglingUnderscore(identifier) && !isAllowed(identifier)) { - context.report({ - node: param, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } - } - }); - } - } - - /** - * Check if function has a dangling underscore - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInFunction(node) { - if (node.type === "FunctionDeclaration" && node.id) { - const identifier = node.id.name; - - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } - } - checkForDanglingUnderscoreInFunctionParameters(node); - } - - - /** - * Check if variable expression has a dangling underscore - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInVariableExpression(node) { - sourceCode.getDeclaredVariables(node).forEach(variable => { - const definition = variable.defs.find(def => def.node === node); - const identifierNode = definition.name; - const identifier = identifierNode.name; - let parent = identifierNode.parent; - - while (!["VariableDeclarator", "ArrayPattern", "ObjectPattern"].includes(parent.type)) { - parent = parent.parent; - } - - if ( - hasDanglingUnderscore(identifier) && - !isSpecialCaseIdentifierInVariableExpression(identifier) && - !isAllowed(identifier) && - !(allowInArrayDestructuring && parent.type === "ArrayPattern") && - !(allowInObjectDestructuring && parent.type === "ObjectPattern") - ) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } - }); - } - - /** - * Check if member expression has a dangling underscore - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInMemberExpression(node) { - const identifier = node.property.name, - isMemberOfThis = node.object.type === "ThisExpression", - isMemberOfSuper = node.object.type === "Super", - isMemberOfThisConstructor = isThisConstructorReference(node); - - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && - !(isMemberOfThis && allowAfterThis) && - !(isMemberOfSuper && allowAfterSuper) && - !(isMemberOfThisConstructor && allowAfterThisConstructor) && - !isSpecialCaseIdentifierForMemberExpression(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier - } - }); - } - } - - /** - * Check if method declaration or method property has a dangling underscore - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInMethod(node) { - const identifier = node.key.name; - const isMethod = node.type === "MethodDefinition" || node.type === "Property" && node.method; - - if (typeof identifier !== "undefined" && enforceInMethodNames && isMethod && hasDanglingUnderscore(identifier) && !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier: node.key.type === "PrivateIdentifier" - ? `#${identifier}` - : identifier - } - }); - } - } - - /** - * Check if a class field has a dangling underscore - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkForDanglingUnderscoreInClassField(node) { - const identifier = node.key.name; - - if (typeof identifier !== "undefined" && hasDanglingUnderscore(identifier) && - enforceInClassFields && - !isAllowed(identifier)) { - context.report({ - node, - messageId: "unexpectedUnderscore", - data: { - identifier: node.key.type === "PrivateIdentifier" - ? `#${identifier}` - : identifier - } - }); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - FunctionDeclaration: checkForDanglingUnderscoreInFunction, - VariableDeclarator: checkForDanglingUnderscoreInVariableExpression, - MemberExpression: checkForDanglingUnderscoreInMemberExpression, - MethodDefinition: checkForDanglingUnderscoreInMethod, - PropertyDefinition: checkForDanglingUnderscoreInClassField, - Property: checkForDanglingUnderscoreInMethod, - FunctionExpression: checkForDanglingUnderscoreInFunction, - ArrowFunctionExpression: checkForDanglingUnderscoreInFunction - }; - - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + allow: { + type: "array", + items: { + type: "string", + }, + }, + allowAfterThis: { + type: "boolean", + }, + allowAfterSuper: { + type: "boolean", + }, + allowAfterThisConstructor: { + type: "boolean", + }, + enforceInMethodNames: { + type: "boolean", + }, + allowFunctionParams: { + type: "boolean", + }, + enforceInClassFields: { + type: "boolean", + }, + allowInArrayDestructuring: { + type: "boolean", + }, + allowInObjectDestructuring: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedUnderscore: + "Unexpected dangling '_' in '{{identifier}}'.", + }, + }, + + create(context) { + const [ + { + allow, + allowAfterSuper, + allowAfterThis, + allowAfterThisConstructor, + allowFunctionParams, + allowInArrayDestructuring, + allowInObjectDestructuring, + enforceInClassFields, + enforceInMethodNames, + }, + ] = context.options; + const sourceCode = context.sourceCode; + + //------------------------------------------------------------------------- + // Helpers + //------------------------------------------------------------------------- + + /** + * Check if identifier is present inside the allowed option + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function isAllowed(identifier) { + return allow.includes(identifier); + } + + /** + * Check if identifier has a dangling underscore + * @param {string} identifier name of the node + * @returns {boolean} true if its is present + * @private + */ + function hasDanglingUnderscore(identifier) { + const len = identifier.length; + + return ( + identifier !== "_" && + (identifier[0] === "_" || identifier[len - 1] === "_") + ); + } + + /** + * Check if identifier is a special case member expression + * @param {string} identifier name of the node + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierForMemberExpression(identifier) { + return identifier === "__proto__"; + } + + /** + * Check if identifier is a special case variable expression + * @param {string} identifier name of the node + * @returns {boolean} true if its is a special case + * @private + */ + function isSpecialCaseIdentifierInVariableExpression(identifier) { + // Checks for the underscore library usage here + return identifier === "_"; + } + + /** + * Check if a node is a member reference of this.constructor + * @param {ASTNode} node node to evaluate + * @returns {boolean} true if it is a reference on this.constructor + * @private + */ + function isThisConstructorReference(node) { + return ( + node.object.type === "MemberExpression" && + node.object.property.name === "constructor" && + node.object.object.type === "ThisExpression" + ); + } + + /** + * Check if function parameter has a dangling underscore. + * @param {ASTNode} node function node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInFunctionParameters(node) { + if (!allowFunctionParams) { + node.params.forEach(param => { + const { type } = param; + let nodeToCheck; + + if (type === "RestElement") { + nodeToCheck = param.argument; + } else if (type === "AssignmentPattern") { + nodeToCheck = param.left; + } else { + nodeToCheck = param; + } + + if (nodeToCheck.type === "Identifier") { + const identifier = nodeToCheck.name; + + if ( + hasDanglingUnderscore(identifier) && + !isAllowed(identifier) + ) { + context.report({ + node: param, + messageId: "unexpectedUnderscore", + data: { + identifier, + }, + }); + } + } + }); + } + } + + /** + * Check if function has a dangling underscore + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInFunction(node) { + if (node.type === "FunctionDeclaration" && node.id) { + const identifier = node.id.name; + + if ( + typeof identifier !== "undefined" && + hasDanglingUnderscore(identifier) && + !isAllowed(identifier) + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier, + }, + }); + } + } + checkForDanglingUnderscoreInFunctionParameters(node); + } + + /** + * Check if variable expression has a dangling underscore + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInVariableExpression(node) { + sourceCode.getDeclaredVariables(node).forEach(variable => { + const definition = variable.defs.find(def => def.node === node); + const identifierNode = definition.name; + const identifier = identifierNode.name; + let parent = identifierNode.parent; + + while ( + ![ + "VariableDeclarator", + "ArrayPattern", + "ObjectPattern", + ].includes(parent.type) + ) { + parent = parent.parent; + } + + if ( + hasDanglingUnderscore(identifier) && + !isSpecialCaseIdentifierInVariableExpression(identifier) && + !isAllowed(identifier) && + !( + allowInArrayDestructuring && + parent.type === "ArrayPattern" + ) && + !( + allowInObjectDestructuring && + parent.type === "ObjectPattern" + ) + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier, + }, + }); + } + }); + } + + /** + * Check if member expression has a dangling underscore + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInMemberExpression(node) { + const identifier = node.property.name, + isMemberOfThis = node.object.type === "ThisExpression", + isMemberOfSuper = node.object.type === "Super", + isMemberOfThisConstructor = isThisConstructorReference(node); + + if ( + typeof identifier !== "undefined" && + hasDanglingUnderscore(identifier) && + !(isMemberOfThis && allowAfterThis) && + !(isMemberOfSuper && allowAfterSuper) && + !(isMemberOfThisConstructor && allowAfterThisConstructor) && + !isSpecialCaseIdentifierForMemberExpression(identifier) && + !isAllowed(identifier) + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier, + }, + }); + } + } + + /** + * Check if method declaration or method property has a dangling underscore + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInMethod(node) { + const identifier = node.key.name; + const isMethod = + node.type === "MethodDefinition" || + (node.type === "Property" && node.method); + + if ( + typeof identifier !== "undefined" && + enforceInMethodNames && + isMethod && + hasDanglingUnderscore(identifier) && + !isAllowed(identifier) + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier: + node.key.type === "PrivateIdentifier" + ? `#${identifier}` + : identifier, + }, + }); + } + } + + /** + * Check if a class field has a dangling underscore + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkForDanglingUnderscoreInClassField(node) { + const identifier = node.key.name; + + if ( + typeof identifier !== "undefined" && + hasDanglingUnderscore(identifier) && + enforceInClassFields && + !isAllowed(identifier) + ) { + context.report({ + node, + messageId: "unexpectedUnderscore", + data: { + identifier: + node.key.type === "PrivateIdentifier" + ? `#${identifier}` + : identifier, + }, + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + FunctionDeclaration: checkForDanglingUnderscoreInFunction, + VariableDeclarator: checkForDanglingUnderscoreInVariableExpression, + MemberExpression: checkForDanglingUnderscoreInMemberExpression, + MethodDefinition: checkForDanglingUnderscoreInMethod, + PropertyDefinition: checkForDanglingUnderscoreInClassField, + Property: checkForDanglingUnderscoreInMethod, + FunctionExpression: checkForDanglingUnderscoreInFunction, + ArrowFunctionExpression: checkForDanglingUnderscoreInFunction, + }; + }, }; diff --git a/lib/rules/no-unexpected-multiline.js b/lib/rules/no-unexpected-multiline.js index 810c08b011b6..89d9ac5e9e11 100644 --- a/lib/rules/no-unexpected-multiline.js +++ b/lib/rules/no-unexpected-multiline.js @@ -16,105 +16,115 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow confusing multiline expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline" - }, - - schema: [], - messages: { - function: "Unexpected newline between function and ( of function call.", - property: "Unexpected newline between object and [ of property access.", - taggedTemplate: "Unexpected newline between template tag and template literal.", - division: "Unexpected newline between numerator and division operator." - } - }, - - create(context) { - - const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u; - - const sourceCode = context.sourceCode; - - /** - * Check to see if there is a newline between the node and the following open bracket - * line's expression - * @param {ASTNode} node The node to check. - * @param {string} messageId The error messageId to use. - * @returns {void} - * @private - */ - function checkForBreakAfter(node, messageId) { - const openParen = sourceCode.getTokenAfter(node, astUtils.isNotClosingParenToken); - const nodeExpressionEnd = sourceCode.getTokenBefore(openParen); - - if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { - context.report({ - node, - loc: openParen.loc, - messageId - }); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - - MemberExpression(node) { - if (!node.computed || node.optional) { - return; - } - checkForBreakAfter(node.object, "property"); - }, - - TaggedTemplateExpression(node) { - const { quasi } = node; - - // handles common tags, parenthesized tags, and typescript's generic type arguments - const tokenBefore = sourceCode.getTokenBefore(quasi); - - if (tokenBefore.loc.end.line !== quasi.loc.start.line) { - context.report({ - node, - loc: { - start: quasi.loc.start, - end: { - line: quasi.loc.start.line, - column: quasi.loc.start.column + 1 - } - }, - messageId: "taggedTemplate" - }); - } - }, - - CallExpression(node) { - if (node.arguments.length === 0 || node.optional) { - return; - } - checkForBreakAfter(node.callee, "function"); - }, - - "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"(node) { - const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/"); - const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash); - - if ( - tokenAfterOperator.type === "Identifier" && - REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) && - secondSlash.range[1] === tokenAfterOperator.range[0] - ) { - checkForBreakAfter(node.left, "division"); - } - } - }; - - } + meta: { + type: "problem", + + docs: { + description: "Disallow confusing multiline expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unexpected-multiline", + }, + + schema: [], + messages: { + function: + "Unexpected newline between function and ( of function call.", + property: + "Unexpected newline between object and [ of property access.", + taggedTemplate: + "Unexpected newline between template tag and template literal.", + division: + "Unexpected newline between numerator and division operator.", + }, + }, + + create(context) { + const REGEX_FLAG_MATCHER = /^[gimsuy]+$/u; + + const sourceCode = context.sourceCode; + + /** + * Check to see if there is a newline between the node and the following open bracket + * line's expression + * @param {ASTNode} node The node to check. + * @param {string} messageId The error messageId to use. + * @returns {void} + * @private + */ + function checkForBreakAfter(node, messageId) { + const openParen = sourceCode.getTokenAfter( + node, + astUtils.isNotClosingParenToken, + ); + const nodeExpressionEnd = sourceCode.getTokenBefore(openParen); + + if (openParen.loc.start.line !== nodeExpressionEnd.loc.end.line) { + context.report({ + node, + loc: openParen.loc, + messageId, + }); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + MemberExpression(node) { + if (!node.computed || node.optional) { + return; + } + checkForBreakAfter(node.object, "property"); + }, + + TaggedTemplateExpression(node) { + const { quasi } = node; + + // handles common tags, parenthesized tags, and typescript's generic type arguments + const tokenBefore = sourceCode.getTokenBefore(quasi); + + if (tokenBefore.loc.end.line !== quasi.loc.start.line) { + context.report({ + node, + loc: { + start: quasi.loc.start, + end: { + line: quasi.loc.start.line, + column: quasi.loc.start.column + 1, + }, + }, + messageId: "taggedTemplate", + }); + } + }, + + CallExpression(node) { + if (node.arguments.length === 0 || node.optional) { + return; + } + checkForBreakAfter(node.callee, "function"); + }, + + "BinaryExpression[operator='/'] > BinaryExpression[operator='/'].left"( + node, + ) { + const secondSlash = sourceCode.getTokenAfter( + node, + token => token.value === "/", + ); + const tokenAfterOperator = + sourceCode.getTokenAfter(secondSlash); + + if ( + tokenAfterOperator.type === "Identifier" && + REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) && + secondSlash.range[1] === tokenAfterOperator.range[0] + ) { + checkForBreakAfter(node.left, "division"); + } + }, + }; + }, }; diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js index 768a15573acc..33ff1115d37d 100644 --- a/lib/rules/no-unmodified-loop-condition.js +++ b/lib/rules/no-unmodified-loop-condition.js @@ -10,13 +10,14 @@ //------------------------------------------------------------------------------ const Traverser = require("../shared/traverser"), - astUtils = require("./utils/ast-utils"); + astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/u; +const SENTINEL_PATTERN = + /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/u; const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/u; // for-in/of statements don't have `test` property. const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/u; const SKIP_PATTERN = /^(?:ArrowFunction|Class|Function)Expression$/u; @@ -39,14 +40,14 @@ const DYNAMIC_PATTERN = /^(?:Call|Member|New|TaggedTemplate|Yield)Expression$/u; * @returns {boolean} `true` if the reference is a write reference. */ function isWriteReference(reference) { - if (reference.init) { - const def = reference.resolved && reference.resolved.defs[0]; - - if (!def || def.type !== "Variable" || def.parent.kind !== "var") { - return false; - } - } - return reference.isWrite(); + if (reference.init) { + const def = reference.resolved && reference.resolved.defs[0]; + + if (!def || def.type !== "Variable" || def.parent.kind !== "var") { + return false; + } + } + return reference.isWrite(); } /** @@ -56,7 +57,7 @@ function isWriteReference(reference) { * @returns {boolean} `true` if the loop condition info is "unmodified". */ function isUnmodified(condition) { - return !condition.modified; + return !condition.modified; } /** @@ -66,7 +67,7 @@ function isUnmodified(condition) { * @returns {boolean} `true` if the loop condition info is "unmodified". */ function isUnmodifiedAndNotBelongToGroup(condition) { - return !(condition.modified || condition.group); + return !(condition.modified || condition.group); } /** @@ -76,10 +77,10 @@ function isUnmodifiedAndNotBelongToGroup(condition) { * @returns {boolean} `true` if the reference is inside of the node. */ function isInRange(node, reference) { - const or = node.range; - const ir = reference.identifier.range; + const or = node.range; + const ir = reference.identifier.range; - return or[0] <= ir[0] && ir[1] <= or[1]; + return or[0] <= ir[0] && ir[1] <= or[1]; } /** @@ -90,14 +91,14 @@ function isInRange(node, reference) { * condition. */ const isInLoop = { - WhileStatement: isInRange, - DoWhileStatement: isInRange, - ForStatement(node, reference) { - return ( - isInRange(node, reference) && - !(node.init && isInRange(node.init, reference)) - ); - } + WhileStatement: isInRange, + DoWhileStatement: isInRange, + ForStatement(node, reference) { + return ( + isInRange(node, reference) && + !(node.init && isInRange(node.init, reference)) + ); + }, }; /** @@ -107,17 +108,17 @@ const isInLoop = { * @returns {ASTNode|null} The function node or null. */ function getEncloseFunctionDeclaration(reference) { - let node = reference.identifier; + let node = reference.identifier; - while (node) { - if (node.type === "FunctionDeclaration") { - return node.id ? node : null; - } + while (node) { + if (node.type === "FunctionDeclaration") { + return node.id ? node : null; + } - node = node.parent; - } + node = node.parent; + } - return null; + return null; } /** @@ -127,29 +128,33 @@ function getEncloseFunctionDeclaration(reference) { * @returns {void} */ function updateModifiedFlag(conditions, modifiers) { - - for (let i = 0; i < conditions.length; ++i) { - const condition = conditions[i]; - - for (let j = 0; !condition.modified && j < modifiers.length; ++j) { - const modifier = modifiers[j]; - let funcNode, funcVar; - - /* - * Besides checking for the condition being in the loop, we want to - * check the function that this modifier is belonging to is called - * in the loop. - * FIXME: This should probably be extracted to a function. - */ - const inLoop = condition.isInLoop(modifier) || Boolean( - (funcNode = getEncloseFunctionDeclaration(modifier)) && - (funcVar = astUtils.getVariableByName(modifier.from.upper, funcNode.id.name)) && - funcVar.references.some(condition.isInLoop) - ); - - condition.modified = inLoop; - } - } + for (let i = 0; i < conditions.length; ++i) { + const condition = conditions[i]; + + for (let j = 0; !condition.modified && j < modifiers.length; ++j) { + const modifier = modifiers[j]; + let funcNode, funcVar; + + /* + * Besides checking for the condition being in the loop, we want to + * check the function that this modifier is belonging to is called + * in the loop. + * FIXME: This should probably be extracted to a function. + */ + const inLoop = + condition.isInLoop(modifier) || + Boolean( + (funcNode = getEncloseFunctionDeclaration(modifier)) && + (funcVar = astUtils.getVariableByName( + modifier.from.upper, + funcNode.id.name, + )) && + funcVar.references.some(condition.isInLoop), + ); + + condition.modified = inLoop; + } + } } //------------------------------------------------------------------------------ @@ -158,203 +163,198 @@ function updateModifiedFlag(conditions, modifiers) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow unmodified loop conditions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition" - }, - - schema: [], - - messages: { - loopConditionNotModified: "'{{name}}' is not modified in this loop." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - let groupMap = null; - - /** - * Reports a given condition info. - * @param {LoopConditionInfo} condition A loop condition info to report. - * @returns {void} - */ - function report(condition) { - const node = condition.reference.identifier; - - context.report({ - node, - messageId: "loopConditionNotModified", - data: node - }); - } - - /** - * Registers given conditions to the group the condition belongs to. - * @param {LoopConditionInfo[]} conditions A loop condition info to - * register. - * @returns {void} - */ - function registerConditionsToGroup(conditions) { - for (let i = 0; i < conditions.length; ++i) { - const condition = conditions[i]; - - if (condition.group) { - let group = groupMap.get(condition.group); - - if (!group) { - group = []; - groupMap.set(condition.group, group); - } - group.push(condition); - } - } - } - - /** - * Reports references which are inside of unmodified groups. - * @param {LoopConditionInfo[]} conditions A loop condition info to report. - * @returns {void} - */ - function checkConditionsInGroup(conditions) { - if (conditions.every(isUnmodified)) { - conditions.forEach(report); - } - } - - /** - * Checks whether or not a given group node has any dynamic elements. - * @param {ASTNode} root A node to check. - * This node is one of BinaryExpression or ConditionalExpression. - * @returns {boolean} `true` if the node is dynamic. - */ - function hasDynamicExpressions(root) { - let retv = false; - - Traverser.traverse(root, { - visitorKeys: sourceCode.visitorKeys, - enter(node) { - if (DYNAMIC_PATTERN.test(node.type)) { - retv = true; - this.break(); - } else if (SKIP_PATTERN.test(node.type)) { - this.skip(); - } - } - }); - - return retv; - } - - /** - * Creates the loop condition information from a given reference. - * @param {eslint-scope.Reference} reference A reference to create. - * @returns {LoopConditionInfo|null} Created loop condition info, or null. - */ - function toLoopCondition(reference) { - if (reference.init) { - return null; - } - - let group = null; - let child = reference.identifier; - let node = child.parent; - - while (node) { - if (SENTINEL_PATTERN.test(node.type)) { - if (LOOP_PATTERN.test(node.type) && node.test === child) { - - // This reference is inside of a loop condition. - return { - reference, - group, - isInLoop: isInLoop[node.type].bind(null, node), - modified: false - }; - } - - // This reference is outside of a loop condition. - break; - } - - /* - * If it's inside of a group, OK if either operand is modified. - * So stores the group this reference belongs to. - */ - if (GROUP_PATTERN.test(node.type)) { - - // If this expression is dynamic, no need to check. - if (hasDynamicExpressions(node)) { - break; - } else { - group = node; - } - } - - child = node; - node = node.parent; - } - - return null; - } - - /** - * Finds unmodified references which are inside of a loop condition. - * Then reports the references which are outside of groups. - * @param {eslint-scope.Variable} variable A variable to report. - * @returns {void} - */ - function checkReferences(variable) { - - // Gets references that exist in loop conditions. - const conditions = variable - .references - .map(toLoopCondition) - .filter(Boolean); - - if (conditions.length === 0) { - return; - } - - // Registers the conditions to belonging groups. - registerConditionsToGroup(conditions); - - // Check the conditions are modified. - const modifiers = variable.references.filter(isWriteReference); - - if (modifiers.length > 0) { - updateModifiedFlag(conditions, modifiers); - } - - /* - * Reports the conditions which are not belonging to groups. - * Others will be reported after all variables are done. - */ - conditions - .filter(isUnmodifiedAndNotBelongToGroup) - .forEach(report); - } - - return { - "Program:exit"(node) { - const queue = [sourceCode.getScope(node)]; - - groupMap = new Map(); - - let scope; - - while ((scope = queue.pop())) { - queue.push(...scope.childScopes); - scope.variables.forEach(checkReferences); - } - - groupMap.forEach(checkConditionsInGroup); - groupMap = null; - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow unmodified loop conditions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-unmodified-loop-condition", + }, + + schema: [], + + messages: { + loopConditionNotModified: + "'{{name}}' is not modified in this loop.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + let groupMap = null; + + /** + * Reports a given condition info. + * @param {LoopConditionInfo} condition A loop condition info to report. + * @returns {void} + */ + function report(condition) { + const node = condition.reference.identifier; + + context.report({ + node, + messageId: "loopConditionNotModified", + data: node, + }); + } + + /** + * Registers given conditions to the group the condition belongs to. + * @param {LoopConditionInfo[]} conditions A loop condition info to + * register. + * @returns {void} + */ + function registerConditionsToGroup(conditions) { + for (let i = 0; i < conditions.length; ++i) { + const condition = conditions[i]; + + if (condition.group) { + let group = groupMap.get(condition.group); + + if (!group) { + group = []; + groupMap.set(condition.group, group); + } + group.push(condition); + } + } + } + + /** + * Reports references which are inside of unmodified groups. + * @param {LoopConditionInfo[]} conditions A loop condition info to report. + * @returns {void} + */ + function checkConditionsInGroup(conditions) { + if (conditions.every(isUnmodified)) { + conditions.forEach(report); + } + } + + /** + * Checks whether or not a given group node has any dynamic elements. + * @param {ASTNode} root A node to check. + * This node is one of BinaryExpression or ConditionalExpression. + * @returns {boolean} `true` if the node is dynamic. + */ + function hasDynamicExpressions(root) { + let retv = false; + + Traverser.traverse(root, { + visitorKeys: sourceCode.visitorKeys, + enter(node) { + if (DYNAMIC_PATTERN.test(node.type)) { + retv = true; + this.break(); + } else if (SKIP_PATTERN.test(node.type)) { + this.skip(); + } + }, + }); + + return retv; + } + + /** + * Creates the loop condition information from a given reference. + * @param {eslint-scope.Reference} reference A reference to create. + * @returns {LoopConditionInfo|null} Created loop condition info, or null. + */ + function toLoopCondition(reference) { + if (reference.init) { + return null; + } + + let group = null; + let child = reference.identifier; + let node = child.parent; + + while (node) { + if (SENTINEL_PATTERN.test(node.type)) { + if (LOOP_PATTERN.test(node.type) && node.test === child) { + // This reference is inside of a loop condition. + return { + reference, + group, + isInLoop: isInLoop[node.type].bind(null, node), + modified: false, + }; + } + + // This reference is outside of a loop condition. + break; + } + + /* + * If it's inside of a group, OK if either operand is modified. + * So stores the group this reference belongs to. + */ + if (GROUP_PATTERN.test(node.type)) { + // If this expression is dynamic, no need to check. + if (hasDynamicExpressions(node)) { + break; + } else { + group = node; + } + } + + child = node; + node = node.parent; + } + + return null; + } + + /** + * Finds unmodified references which are inside of a loop condition. + * Then reports the references which are outside of groups. + * @param {eslint-scope.Variable} variable A variable to report. + * @returns {void} + */ + function checkReferences(variable) { + // Gets references that exist in loop conditions. + const conditions = variable.references + .map(toLoopCondition) + .filter(Boolean); + + if (conditions.length === 0) { + return; + } + + // Registers the conditions to belonging groups. + registerConditionsToGroup(conditions); + + // Check the conditions are modified. + const modifiers = variable.references.filter(isWriteReference); + + if (modifiers.length > 0) { + updateModifiedFlag(conditions, modifiers); + } + + /* + * Reports the conditions which are not belonging to groups. + * Others will be reported after all variables are done. + */ + conditions.filter(isUnmodifiedAndNotBelongToGroup).forEach(report); + } + + return { + "Program:exit"(node) { + const queue = [sourceCode.getScope(node)]; + + groupMap = new Map(); + + let scope; + + while ((scope = queue.pop())) { + queue.push(...scope.childScopes); + scope.variables.forEach(checkReferences); + } + + groupMap.forEach(checkConditionsInGroup); + groupMap = null; + }, + }; + }, }; diff --git a/lib/rules/no-unneeded-ternary.js b/lib/rules/no-unneeded-ternary.js index c64c14767d35..a1434375ad59 100644 --- a/lib/rules/no-unneeded-ternary.js +++ b/lib/rules/no-unneeded-ternary.js @@ -8,16 +8,30 @@ const astUtils = require("./utils/ast-utils"); // Operators that always result in a boolean value -const BOOLEAN_OPERATORS = new Set(["==", "===", "!=", "!==", ">", ">=", "<", "<=", "in", "instanceof"]); +const BOOLEAN_OPERATORS = new Set([ + "==", + "===", + "!=", + "!==", + ">", + ">=", + "<", + "<=", + "in", + "instanceof", +]); const OPERATOR_INVERSES = { - "==": "!=", - "!=": "==", - "===": "!==", - "!==": "===" + "==": "!=", + "!=": "==", + "===": "!==", + "!==": "===", - // Operators like < and >= are not true inverses, since both will return false with NaN. + // Operators like < and >= are not true inverses, since both will return false with NaN. }; -const OR_PRECEDENCE = astUtils.getPrecedence({ type: "LogicalExpression", operator: "||" }); +const OR_PRECEDENCE = astUtils.getPrecedence({ + type: "LogicalExpression", + operator: "||", +}); //------------------------------------------------------------------------------ // Rule Definition @@ -25,143 +39,194 @@ const OR_PRECEDENCE = astUtils.getPrecedence({ type: "LogicalExpression", operat /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - defaultAssignment: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - unnecessaryConditionalExpression: "Unnecessary use of boolean literals in conditional expression.", - unnecessaryConditionalAssignment: "Unnecessary use of conditional expression for default assignment." - } - }, - - create(context) { - const [{ defaultAssignment }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Test if the node is a boolean literal - * @param {ASTNode} node The node to report. - * @returns {boolean} True if the its a boolean literal - * @private - */ - function isBooleanLiteral(node) { - return node.type === "Literal" && typeof node.value === "boolean"; - } - - /** - * Creates an expression that represents the boolean inverse of the expression represented by the original node - * @param {ASTNode} node A node representing an expression - * @returns {string} A string representing an inverted expression - */ - function invertExpression(node) { - if (node.type === "BinaryExpression" && Object.hasOwn(OPERATOR_INVERSES, node.operator)) { - const operatorToken = sourceCode.getFirstTokenBetween( - node.left, - node.right, - token => token.value === node.operator - ); - const text = sourceCode.getText(); - - return text.slice(node.range[0], - operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]); - } - - if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) { - return `!(${astUtils.getParenthesisedText(sourceCode, node)})`; - } - return `!${astUtils.getParenthesisedText(sourceCode, node)}`; - } - - /** - * Tests if a given node always evaluates to a boolean value - * @param {ASTNode} node An expression node - * @returns {boolean} True if it is determined that the node will always evaluate to a boolean value - */ - function isBooleanExpression(node) { - return node.type === "BinaryExpression" && BOOLEAN_OPERATORS.has(node.operator) || - node.type === "UnaryExpression" && node.operator === "!"; - } - - /** - * Test if the node matches the pattern id ? id : expression - * @param {ASTNode} node The ConditionalExpression to check. - * @returns {boolean} True if the pattern is matched, and false otherwise - * @private - */ - function matchesDefaultAssignment(node) { - return node.test.type === "Identifier" && - node.consequent.type === "Identifier" && - node.test.name === node.consequent.name; - } - - return { - - ConditionalExpression(node) { - if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) { - context.report({ - node, - messageId: "unnecessaryConditionalExpression", - fix(fixer) { - if (node.consequent.value === node.alternate.value) { - - // Replace `foo ? true : true` with just `true`, but don't replace `foo() ? true : true` - return node.test.type === "Identifier" ? fixer.replaceText(node, node.consequent.value.toString()) : null; - } - if (node.alternate.value) { - - // Replace `foo() ? false : true` with `!(foo())` - return fixer.replaceText(node, invertExpression(node.test)); - } - - // Replace `foo ? true : false` with `foo` if `foo` is guaranteed to be a boolean, or `!!foo` otherwise. - - return fixer.replaceText(node, isBooleanExpression(node.test) ? astUtils.getParenthesisedText(sourceCode, node.test) : `!${invertExpression(node.test)}`); - } - }); - } else if (!defaultAssignment && matchesDefaultAssignment(node)) { - context.report({ - node, - messageId: "unnecessaryConditionalAssignment", - fix(fixer) { - const shouldParenthesizeAlternate = - ( - astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE || - astUtils.isCoalesceExpression(node.alternate) - ) && - !astUtils.isParenthesised(sourceCode, node.alternate); - const alternateText = shouldParenthesizeAlternate - ? `(${sourceCode.getText(node.alternate)})` - : astUtils.getParenthesisedText(sourceCode, node.alternate); - const testText = astUtils.getParenthesisedText(sourceCode, node.test); - - return fixer.replaceText(node, `${testText} || ${alternateText}`); - } - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + defaultAssignment: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + unnecessaryConditionalExpression: + "Unnecessary use of boolean literals in conditional expression.", + unnecessaryConditionalAssignment: + "Unnecessary use of conditional expression for default assignment.", + }, + }, + + create(context) { + const [{ defaultAssignment }] = context.options; + const sourceCode = context.sourceCode; + + /** + * Test if the node is a boolean literal + * @param {ASTNode} node The node to report. + * @returns {boolean} True if the its a boolean literal + * @private + */ + function isBooleanLiteral(node) { + return node.type === "Literal" && typeof node.value === "boolean"; + } + + /** + * Creates an expression that represents the boolean inverse of the expression represented by the original node + * @param {ASTNode} node A node representing an expression + * @returns {string} A string representing an inverted expression + */ + function invertExpression(node) { + if ( + node.type === "BinaryExpression" && + Object.hasOwn(OPERATOR_INVERSES, node.operator) + ) { + const operatorToken = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + const text = sourceCode.getText(); + + return ( + text.slice(node.range[0], operatorToken.range[0]) + + OPERATOR_INVERSES[node.operator] + + text.slice(operatorToken.range[1], node.range[1]) + ); + } + + if ( + astUtils.getPrecedence(node) < + astUtils.getPrecedence({ type: "UnaryExpression" }) + ) { + return `!(${astUtils.getParenthesisedText(sourceCode, node)})`; + } + return `!${astUtils.getParenthesisedText(sourceCode, node)}`; + } + + /** + * Tests if a given node always evaluates to a boolean value + * @param {ASTNode} node An expression node + * @returns {boolean} True if it is determined that the node will always evaluate to a boolean value + */ + function isBooleanExpression(node) { + return ( + (node.type === "BinaryExpression" && + BOOLEAN_OPERATORS.has(node.operator)) || + (node.type === "UnaryExpression" && node.operator === "!") + ); + } + + /** + * Test if the node matches the pattern id ? id : expression + * @param {ASTNode} node The ConditionalExpression to check. + * @returns {boolean} True if the pattern is matched, and false otherwise + * @private + */ + function matchesDefaultAssignment(node) { + return ( + node.test.type === "Identifier" && + node.consequent.type === "Identifier" && + node.test.name === node.consequent.name + ); + } + + return { + ConditionalExpression(node) { + if ( + isBooleanLiteral(node.alternate) && + isBooleanLiteral(node.consequent) + ) { + context.report({ + node, + messageId: "unnecessaryConditionalExpression", + fix(fixer) { + if ( + node.consequent.value === node.alternate.value + ) { + // Replace `foo ? true : true` with just `true`, but don't replace `foo() ? true : true` + return node.test.type === "Identifier" + ? fixer.replaceText( + node, + node.consequent.value.toString(), + ) + : null; + } + if (node.alternate.value) { + // Replace `foo() ? false : true` with `!(foo())` + return fixer.replaceText( + node, + invertExpression(node.test), + ); + } + + // Replace `foo ? true : false` with `foo` if `foo` is guaranteed to be a boolean, or `!!foo` otherwise. + + return fixer.replaceText( + node, + isBooleanExpression(node.test) + ? astUtils.getParenthesisedText( + sourceCode, + node.test, + ) + : `!${invertExpression(node.test)}`, + ); + }, + }); + } else if ( + !defaultAssignment && + matchesDefaultAssignment(node) + ) { + context.report({ + node, + messageId: "unnecessaryConditionalAssignment", + fix(fixer) { + const shouldParenthesizeAlternate = + (astUtils.getPrecedence(node.alternate) < + OR_PRECEDENCE || + astUtils.isCoalesceExpression( + node.alternate, + )) && + !astUtils.isParenthesised( + sourceCode, + node.alternate, + ); + const alternateText = shouldParenthesizeAlternate + ? `(${sourceCode.getText(node.alternate)})` + : astUtils.getParenthesisedText( + sourceCode, + node.alternate, + ); + const testText = astUtils.getParenthesisedText( + sourceCode, + node.test, + ); + + return fixer.replaceText( + node, + `${testText} || ${alternateText}`, + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-unreachable-loop.js b/lib/rules/no-unreachable-loop.js index f5507ecc957f..84967464b8d2 100644 --- a/lib/rules/no-unreachable-loop.js +++ b/lib/rules/no-unreachable-loop.js @@ -9,7 +9,13 @@ // Helpers //------------------------------------------------------------------------------ -const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"]; +const allLoopTypes = [ + "WhileStatement", + "DoWhileStatement", + "ForStatement", + "ForInStatement", + "ForOfStatement", +]; /** * Checks all segments in a set and returns true if any are reachable. @@ -17,14 +23,13 @@ const allLoopTypes = ["WhileStatement", "DoWhileStatement", "ForStatement", "For * @returns {boolean} True if any segment is reachable; false otherwise. */ function isAnySegmentReachable(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; } /** @@ -34,25 +39,25 @@ function isAnySegmentReachable(segments) { * @returns {boolean} `true` if the node is a looping target. */ function isLoopingTarget(node) { - const parent = node.parent; - - if (parent) { - switch (parent.type) { - case "WhileStatement": - return node === parent.test; - case "DoWhileStatement": - return node === parent.body; - case "ForStatement": - return node === (parent.update || parent.test || parent.body); - case "ForInStatement": - case "ForOfStatement": - return node === parent.left; - - // no default - } - } - - return false; + const parent = node.parent; + + if (parent) { + switch (parent.type) { + case "WhileStatement": + return node === parent.test; + case "DoWhileStatement": + return node === parent.body; + case "ForStatement": + return node === (parent.update || parent.test || parent.body); + case "ForInStatement": + case "ForOfStatement": + return node === parent.left; + + // no default + } + } + + return false; } /** @@ -62,7 +67,7 @@ function isLoopingTarget(node) { * @returns {Array} a new array that represents `arrA \ arrB`. */ function getDifference(arrA, arrB) { - return arrA.filter(a => !arrB.includes(a)); + return arrA.filter(a => !arrB.includes(a)); } //------------------------------------------------------------------------------ @@ -71,117 +76,115 @@ function getDifference(arrA, arrB) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ ignore: [] }], - - docs: { - description: "Disallow loops with a body that allows only one iteration", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-unreachable-loop" - }, - - schema: [{ - type: "object", - properties: { - ignore: { - type: "array", - items: { - enum: allLoopTypes - }, - uniqueItems: true - } - }, - additionalProperties: false - }], - - messages: { - invalid: "Invalid loop. Its body allows only one iteration." - } - }, - - create(context) { - const [{ ignore: ignoredLoopTypes }] = context.options; - const loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes), - loopSelector = loopTypesToCheck.join(","), - loopsByTargetSegments = new Map(), - loopsToReport = new Set(); - - const codePathSegments = []; - let currentCodePathSegments = new Set(); - - return { - - onCodePathStart() { - codePathSegments.push(currentCodePathSegments); - currentCodePathSegments = new Set(); - }, - - onCodePathEnd() { - currentCodePathSegments = codePathSegments.pop(); - }, - - onUnreachableCodePathSegmentStart(segment) { - currentCodePathSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - onCodePathSegmentStart(segment, node) { - - currentCodePathSegments.add(segment); - - if (isLoopingTarget(node)) { - const loop = node.parent; - - loopsByTargetSegments.set(segment, loop); - } - }, - - onCodePathSegmentLoop(_, toSegment, node) { - const loop = loopsByTargetSegments.get(toSegment); - - /** - * The second iteration is reachable, meaning that the loop is valid by the logic of this rule, - * only if there is at least one loop event with the appropriate target (which has been already - * determined in the `loopsByTargetSegments` map), raised from either: - * - * - the end of the loop's body (in which case `node === loop`) - * - a `continue` statement - * - * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes. - */ - if (node === loop || node.type === "ContinueStatement") { - - // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw. - loopsToReport.delete(loop); - } - }, - - [loopSelector](node) { - - /** - * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. - * For unreachable segments, the code path analysis does not raise events required for this implementation. - */ - if (isAnySegmentReachable(currentCodePathSegments)) { - loopsToReport.add(node); - } - }, - - - "Program:exit"() { - loopsToReport.forEach( - node => context.report({ node, messageId: "invalid" }) - ); - } - }; - } + meta: { + type: "problem", + + defaultOptions: [{ ignore: [] }], + + docs: { + description: + "Disallow loops with a body that allows only one iteration", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-unreachable-loop", + }, + + schema: [ + { + type: "object", + properties: { + ignore: { + type: "array", + items: { + enum: allLoopTypes, + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + invalid: "Invalid loop. Its body allows only one iteration.", + }, + }, + + create(context) { + const [{ ignore: ignoredLoopTypes }] = context.options; + const loopTypesToCheck = getDifference(allLoopTypes, ignoredLoopTypes), + loopSelector = loopTypesToCheck.join(","), + loopsByTargetSegments = new Map(), + loopsToReport = new Set(); + + const codePathSegments = []; + let currentCodePathSegments = new Set(); + + return { + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); + }, + + onCodePathEnd() { + currentCodePathSegments = codePathSegments.pop(); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentStart(segment, node) { + currentCodePathSegments.add(segment); + + if (isLoopingTarget(node)) { + const loop = node.parent; + + loopsByTargetSegments.set(segment, loop); + } + }, + + onCodePathSegmentLoop(_, toSegment, node) { + const loop = loopsByTargetSegments.get(toSegment); + + /** + * The second iteration is reachable, meaning that the loop is valid by the logic of this rule, + * only if there is at least one loop event with the appropriate target (which has been already + * determined in the `loopsByTargetSegments` map), raised from either: + * + * - the end of the loop's body (in which case `node === loop`) + * - a `continue` statement + * + * This condition skips loop events raised from `ForInStatement > .right` and `ForOfStatement > .right` nodes. + */ + if (node === loop || node.type === "ContinueStatement") { + // Removes loop if it exists in the set. Otherwise, `Set#delete` has no effect and doesn't throw. + loopsToReport.delete(loop); + } + }, + + [loopSelector](node) { + /** + * Ignore unreachable loop statements to avoid unnecessary complexity in the implementation, or false positives otherwise. + * For unreachable segments, the code path analysis does not raise events required for this implementation. + */ + if (isAnySegmentReachable(currentCodePathSegments)) { + loopsToReport.add(node); + } + }, + + "Program:exit"() { + loopsToReport.forEach(node => + context.report({ node, messageId: "invalid" }), + ); + }, + }; + }, }; diff --git a/lib/rules/no-unreachable.js b/lib/rules/no-unreachable.js index 0cf750e4251e..a335bdb16ee4 100644 --- a/lib/rules/no-unreachable.js +++ b/lib/rules/no-unreachable.js @@ -20,7 +20,7 @@ * @returns {boolean} `true` if the node has the initializer. */ function isInitialized(node) { - return Boolean(node.init); + return Boolean(node.init); } /** @@ -29,83 +29,82 @@ function isInitialized(node) { * @returns {boolean} True if all segments are unreachable; false otherwise. */ function areAllSegmentsUnreachable(segments) { + for (const segment of segments) { + if (segment.reachable) { + return false; + } + } - for (const segment of segments) { - if (segment.reachable) { - return false; - } - } - - return true; + return true; } /** * The class to distinguish consecutive unreachable statements. */ class ConsecutiveRange { - constructor(sourceCode) { - this.sourceCode = sourceCode; - this.startNode = null; - this.endNode = null; - } - - /** - * The location object of this range. - * @type {Object} - */ - get location() { - return { - start: this.startNode.loc.start, - end: this.endNode.loc.end - }; - } - - /** - * `true` if this range is empty. - * @type {boolean} - */ - get isEmpty() { - return !(this.startNode && this.endNode); - } - - /** - * Checks whether the given node is inside of this range. - * @param {ASTNode|Token} node The node to check. - * @returns {boolean} `true` if the node is inside of this range. - */ - contains(node) { - return ( - node.range[0] >= this.startNode.range[0] && - node.range[1] <= this.endNode.range[1] - ); - } - - /** - * Checks whether the given node is consecutive to this range. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if the node is consecutive to this range. - */ - isConsecutive(node) { - return this.contains(this.sourceCode.getTokenBefore(node)); - } - - /** - * Merges the given node to this range. - * @param {ASTNode} node The node to merge. - * @returns {void} - */ - merge(node) { - this.endNode = node; - } - - /** - * Resets this range by the given node or null. - * @param {ASTNode|null} node The node to reset, or null. - * @returns {void} - */ - reset(node) { - this.startNode = this.endNode = node; - } + constructor(sourceCode) { + this.sourceCode = sourceCode; + this.startNode = null; + this.endNode = null; + } + + /** + * The location object of this range. + * @type {Object} + */ + get location() { + return { + start: this.startNode.loc.start, + end: this.endNode.loc.end, + }; + } + + /** + * `true` if this range is empty. + * @type {boolean} + */ + get isEmpty() { + return !(this.startNode && this.endNode); + } + + /** + * Checks whether the given node is inside of this range. + * @param {ASTNode|Token} node The node to check. + * @returns {boolean} `true` if the node is inside of this range. + */ + contains(node) { + return ( + node.range[0] >= this.startNode.range[0] && + node.range[1] <= this.endNode.range[1] + ); + } + + /** + * Checks whether the given node is consecutive to this range. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is consecutive to this range. + */ + isConsecutive(node) { + return this.contains(this.sourceCode.getTokenBefore(node)); + } + + /** + * Merges the given node to this range. + * @param {ASTNode} node The node to merge. + * @returns {void} + */ + merge(node) { + this.endNode = node; + } + + /** + * Resets this range by the given node or null. + * @param {ASTNode|null} node The node to reset, or null. + * @returns {void} + */ + reset(node) { + this.startNode = this.endNode = node; + } } //------------------------------------------------------------------------------ @@ -114,180 +113,188 @@ class ConsecutiveRange { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unreachable" - }, - - schema: [], - - messages: { - unreachableCode: "Unreachable code." - } - }, - - create(context) { - - /** @type {ConstructorInfo | null} */ - let constructorInfo = null; - - /** @type {ConsecutiveRange} */ - const range = new ConsecutiveRange(context.sourceCode); - - /** @type {Array>} */ - const codePathSegments = []; - - /** @type {Set} */ - let currentCodePathSegments = new Set(); - - /** - * Reports a given node if it's unreachable. - * @param {ASTNode} node A statement node to report. - * @returns {void} - */ - function reportIfUnreachable(node) { - let nextNode = null; - - if (node && (node.type === "PropertyDefinition" || areAllSegmentsUnreachable(currentCodePathSegments))) { - - // Store this statement to distinguish consecutive statements. - if (range.isEmpty) { - range.reset(node); - return; - } - - // Skip if this statement is inside of the current range. - if (range.contains(node)) { - return; - } - - // Merge if this statement is consecutive to the current range. - if (range.isConsecutive(node)) { - range.merge(node); - return; - } - - nextNode = node; - } - - /* - * Report the current range since this statement is reachable or is - * not consecutive to the current range. - */ - if (!range.isEmpty) { - context.report({ - messageId: "unreachableCode", - loc: range.location, - node: range.startNode - }); - } - - // Update the current range. - range.reset(nextNode); - } - - return { - - // Manages the current code path. - onCodePathStart() { - codePathSegments.push(currentCodePathSegments); - currentCodePathSegments = new Set(); - }, - - onCodePathEnd() { - currentCodePathSegments = codePathSegments.pop(); - }, - - onUnreachableCodePathSegmentStart(segment) { - currentCodePathSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - currentCodePathSegments.delete(segment); - }, - - onCodePathSegmentStart(segment) { - currentCodePathSegments.add(segment); - }, - - // Registers for all statement nodes (excludes FunctionDeclaration). - BlockStatement: reportIfUnreachable, - BreakStatement: reportIfUnreachable, - ClassDeclaration: reportIfUnreachable, - ContinueStatement: reportIfUnreachable, - DebuggerStatement: reportIfUnreachable, - DoWhileStatement: reportIfUnreachable, - ExpressionStatement: reportIfUnreachable, - ForInStatement: reportIfUnreachable, - ForOfStatement: reportIfUnreachable, - ForStatement: reportIfUnreachable, - IfStatement: reportIfUnreachable, - ImportDeclaration: reportIfUnreachable, - LabeledStatement: reportIfUnreachable, - ReturnStatement: reportIfUnreachable, - SwitchStatement: reportIfUnreachable, - ThrowStatement: reportIfUnreachable, - TryStatement: reportIfUnreachable, - - VariableDeclaration(node) { - if (node.kind !== "var" || node.declarations.some(isInitialized)) { - reportIfUnreachable(node); - } - }, - - WhileStatement: reportIfUnreachable, - WithStatement: reportIfUnreachable, - ExportNamedDeclaration: reportIfUnreachable, - ExportDefaultDeclaration: reportIfUnreachable, - ExportAllDeclaration: reportIfUnreachable, - - "Program:exit"() { - reportIfUnreachable(); - }, - - /* - * Instance fields defined in a subclass are never created if the constructor of the subclass - * doesn't call `super()`, so their definitions are unreachable code. - */ - "MethodDefinition[kind='constructor']"() { - constructorInfo = { - upper: constructorInfo, - hasSuperCall: false - }; - }, - "MethodDefinition[kind='constructor']:exit"(node) { - const { hasSuperCall } = constructorInfo; - - constructorInfo = constructorInfo.upper; - - // skip typescript constructors without the body - if (!node.value.body) { - return; - } - - const classDefinition = node.parent.parent; - - if (classDefinition.superClass && !hasSuperCall) { - for (const element of classDefinition.body.body) { - if (element.type === "PropertyDefinition" && !element.static) { - reportIfUnreachable(element); - } - } - } - }, - "CallExpression > Super.callee"() { - if (constructorInfo) { - constructorInfo.hasSuperCall = true; - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unreachable", + }, + + schema: [], + + messages: { + unreachableCode: "Unreachable code.", + }, + }, + + create(context) { + /** @type {ConstructorInfo | null} */ + let constructorInfo = null; + + /** @type {ConsecutiveRange} */ + const range = new ConsecutiveRange(context.sourceCode); + + /** @type {Array>} */ + const codePathSegments = []; + + /** @type {Set} */ + let currentCodePathSegments = new Set(); + + /** + * Reports a given node if it's unreachable. + * @param {ASTNode} node A statement node to report. + * @returns {void} + */ + function reportIfUnreachable(node) { + let nextNode = null; + + if ( + node && + (node.type === "PropertyDefinition" || + areAllSegmentsUnreachable(currentCodePathSegments)) + ) { + // Store this statement to distinguish consecutive statements. + if (range.isEmpty) { + range.reset(node); + return; + } + + // Skip if this statement is inside of the current range. + if (range.contains(node)) { + return; + } + + // Merge if this statement is consecutive to the current range. + if (range.isConsecutive(node)) { + range.merge(node); + return; + } + + nextNode = node; + } + + /* + * Report the current range since this statement is reachable or is + * not consecutive to the current range. + */ + if (!range.isEmpty) { + context.report({ + messageId: "unreachableCode", + loc: range.location, + node: range.startNode, + }); + } + + // Update the current range. + range.reset(nextNode); + } + + return { + // Manages the current code path. + onCodePathStart() { + codePathSegments.push(currentCodePathSegments); + currentCodePathSegments = new Set(); + }, + + onCodePathEnd() { + currentCodePathSegments = codePathSegments.pop(); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + currentCodePathSegments.delete(segment); + }, + + onCodePathSegmentStart(segment) { + currentCodePathSegments.add(segment); + }, + + // Registers for all statement nodes (excludes FunctionDeclaration). + BlockStatement: reportIfUnreachable, + BreakStatement: reportIfUnreachable, + ClassDeclaration: reportIfUnreachable, + ContinueStatement: reportIfUnreachable, + DebuggerStatement: reportIfUnreachable, + DoWhileStatement: reportIfUnreachable, + ExpressionStatement: reportIfUnreachable, + ForInStatement: reportIfUnreachable, + ForOfStatement: reportIfUnreachable, + ForStatement: reportIfUnreachable, + IfStatement: reportIfUnreachable, + ImportDeclaration: reportIfUnreachable, + LabeledStatement: reportIfUnreachable, + ReturnStatement: reportIfUnreachable, + SwitchStatement: reportIfUnreachable, + ThrowStatement: reportIfUnreachable, + TryStatement: reportIfUnreachable, + + VariableDeclaration(node) { + if ( + node.kind !== "var" || + node.declarations.some(isInitialized) + ) { + reportIfUnreachable(node); + } + }, + + WhileStatement: reportIfUnreachable, + WithStatement: reportIfUnreachable, + ExportNamedDeclaration: reportIfUnreachable, + ExportDefaultDeclaration: reportIfUnreachable, + ExportAllDeclaration: reportIfUnreachable, + + "Program:exit"() { + reportIfUnreachable(); + }, + + /* + * Instance fields defined in a subclass are never created if the constructor of the subclass + * doesn't call `super()`, so their definitions are unreachable code. + */ + "MethodDefinition[kind='constructor']"() { + constructorInfo = { + upper: constructorInfo, + hasSuperCall: false, + }; + }, + "MethodDefinition[kind='constructor']:exit"(node) { + const { hasSuperCall } = constructorInfo; + + constructorInfo = constructorInfo.upper; + + // skip typescript constructors without the body + if (!node.value.body) { + return; + } + + const classDefinition = node.parent.parent; + + if (classDefinition.superClass && !hasSuperCall) { + for (const element of classDefinition.body.body) { + if ( + element.type === "PropertyDefinition" && + !element.static + ) { + reportIfUnreachable(element); + } + } + } + }, + "CallExpression > Super.callee"() { + if (constructorInfo) { + constructorInfo.hasSuperCall = true; + } + }, + }; + }, }; diff --git a/lib/rules/no-unsafe-finally.js b/lib/rules/no-unsafe-finally.js index ebd24328ffbb..8e5d437c8d93 100644 --- a/lib/rules/no-unsafe-finally.js +++ b/lib/rules/no-unsafe-finally.js @@ -9,10 +9,12 @@ // Helpers //------------------------------------------------------------------------------ -const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u; -const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u; -const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u; - +const SENTINEL_NODE_TYPE_RETURN_THROW = + /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u; +const SENTINEL_NODE_TYPE_BREAK = + /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u; +const SENTINEL_NODE_TYPE_CONTINUE = + /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u; //------------------------------------------------------------------------------ // Rule Definition @@ -20,92 +22,98 @@ const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaratio /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow control flow statements in `finally` blocks", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unsafe-finally" - }, + meta: { + type: "problem", - schema: [], + docs: { + description: "Disallow control flow statements in `finally` blocks", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unsafe-finally", + }, - messages: { - unsafeUsage: "Unsafe usage of {{nodeType}}." - } - }, - create(context) { + schema: [], - /** - * Checks if the node is the finalizer of a TryStatement - * @param {ASTNode} node node to check. - * @returns {boolean} - true if the node is the finalizer of a TryStatement - */ - function isFinallyBlock(node) { - return node.parent.type === "TryStatement" && node.parent.finalizer === node; - } + messages: { + unsafeUsage: "Unsafe usage of {{nodeType}}.", + }, + }, + create(context) { + /** + * Checks if the node is the finalizer of a TryStatement + * @param {ASTNode} node node to check. + * @returns {boolean} - true if the node is the finalizer of a TryStatement + */ + function isFinallyBlock(node) { + return ( + node.parent.type === "TryStatement" && + node.parent.finalizer === node + ); + } - /** - * Climbs up the tree if the node is not a sentinel node - * @param {ASTNode} node node to check. - * @param {string} label label of the break or continue statement - * @returns {boolean} - return whether the node is a finally block or a sentinel node - */ - function isInFinallyBlock(node, label) { - let labelInside = false; - let sentinelNodeType; + /** + * Climbs up the tree if the node is not a sentinel node + * @param {ASTNode} node node to check. + * @param {string} label label of the break or continue statement + * @returns {boolean} - return whether the node is a finally block or a sentinel node + */ + function isInFinallyBlock(node, label) { + let labelInside = false; + let sentinelNodeType; - if (node.type === "BreakStatement" && !node.label) { - sentinelNodeType = SENTINEL_NODE_TYPE_BREAK; - } else if (node.type === "ContinueStatement") { - sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE; - } else { - sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW; - } + if (node.type === "BreakStatement" && !node.label) { + sentinelNodeType = SENTINEL_NODE_TYPE_BREAK; + } else if (node.type === "ContinueStatement") { + sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE; + } else { + sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW; + } - for ( - let currentNode = node; - currentNode && !sentinelNodeType.test(currentNode.type); - currentNode = currentNode.parent - ) { - if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) { - labelInside = true; - } - if (isFinallyBlock(currentNode)) { - if (label && labelInside) { - return false; - } - return true; - } - } - return false; - } + for ( + let currentNode = node; + currentNode && !sentinelNodeType.test(currentNode.type); + currentNode = currentNode.parent + ) { + if ( + currentNode.parent.label && + label && + currentNode.parent.label.name === label.name + ) { + labelInside = true; + } + if (isFinallyBlock(currentNode)) { + if (label && labelInside) { + return false; + } + return true; + } + } + return false; + } - /** - * Checks whether the possibly-unsafe statement is inside a finally block. - * @param {ASTNode} node node to check. - * @returns {void} - */ - function check(node) { - if (isInFinallyBlock(node, node.label)) { - context.report({ - messageId: "unsafeUsage", - data: { - nodeType: node.type - }, - node, - line: node.loc.line, - column: node.loc.column - }); - } - } + /** + * Checks whether the possibly-unsafe statement is inside a finally block. + * @param {ASTNode} node node to check. + * @returns {void} + */ + function check(node) { + if (isInFinallyBlock(node, node.label)) { + context.report({ + messageId: "unsafeUsage", + data: { + nodeType: node.type, + }, + node, + line: node.loc.line, + column: node.loc.column, + }); + } + } - return { - ReturnStatement: check, - ThrowStatement: check, - BreakStatement: check, - ContinueStatement: check - }; - } + return { + ReturnStatement: check, + ThrowStatement: check, + BreakStatement: check, + ContinueStatement: check, + }; + }, }; diff --git a/lib/rules/no-unsafe-negation.js b/lib/rules/no-unsafe-negation.js index 10f4a5301bce..f754c7fd4ddf 100644 --- a/lib/rules/no-unsafe-negation.js +++ b/lib/rules/no-unsafe-negation.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the operator is `in` or `instanceof` */ function isInOrInstanceOfOperator(op) { - return op === "in" || op === "instanceof"; + return op === "in" || op === "instanceof"; } /** @@ -30,7 +30,7 @@ function isInOrInstanceOfOperator(op) { * @returns {boolean} `true` if the operator is an ordering relational operator. */ function isOrderingRelationalOperator(op) { - return op === "<" || op === ">" || op === ">=" || op === "<="; + return op === "<" || op === ">" || op === ">=" || op === "<="; } /** @@ -39,7 +39,7 @@ function isOrderingRelationalOperator(op) { * @returns {boolean} `true` if the node is a logical negation expression. */ function isNegation(node) { - return node.type === "UnaryExpression" && node.operator === "!"; + return node.type === "UnaryExpression" && node.operator === "!"; } //------------------------------------------------------------------------------ @@ -48,83 +48,105 @@ function isNegation(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - enforceForOrderingRelations: false - }], - - docs: { - description: "Disallow negating the left operand of relational operators", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unsafe-negation" - }, - - hasSuggestions: true, - - schema: [ - { - type: "object", - properties: { - enforceForOrderingRelations: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: null, - - messages: { - unexpected: "Unexpected negating the left operand of '{{operator}}' operator.", - suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.", - suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ enforceForOrderingRelations }] = context.options; - - return { - BinaryExpression(node) { - const operator = node.operator; - const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator); - - if ( - (isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) && - isNegation(node.left) && - !astUtils.isParenthesised(sourceCode, node.left) - ) { - context.report({ - node, - loc: node.left.loc, - messageId: "unexpected", - data: { operator }, - suggest: [ - { - messageId: "suggestNegatedExpression", - data: { operator }, - fix(fixer) { - const negationToken = sourceCode.getFirstToken(node.left); - const fixRange = [negationToken.range[1], node.range[1]]; - const text = sourceCode.text.slice(fixRange[0], fixRange[1]); - - return fixer.replaceTextRange(fixRange, `(${text})`); - } - }, - { - messageId: "suggestParenthesisedNegation", - fix(fixer) { - return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`); - } - } - ] - }); - } - } - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + enforceForOrderingRelations: false, + }, + ], + + docs: { + description: + "Disallow negating the left operand of relational operators", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unsafe-negation", + }, + + hasSuggestions: true, + + schema: [ + { + type: "object", + properties: { + enforceForOrderingRelations: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: null, + + messages: { + unexpected: + "Unexpected negating the left operand of '{{operator}}' operator.", + suggestNegatedExpression: + "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.", + suggestParenthesisedNegation: + "Wrap negation in '()' to make the intention explicit. This preserves the current behavior.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ enforceForOrderingRelations }] = context.options; + + return { + BinaryExpression(node) { + const operator = node.operator; + const orderingRelationRuleApplies = + enforceForOrderingRelations && + isOrderingRelationalOperator(operator); + + if ( + (isInOrInstanceOfOperator(operator) || + orderingRelationRuleApplies) && + isNegation(node.left) && + !astUtils.isParenthesised(sourceCode, node.left) + ) { + context.report({ + node, + loc: node.left.loc, + messageId: "unexpected", + data: { operator }, + suggest: [ + { + messageId: "suggestNegatedExpression", + data: { operator }, + fix(fixer) { + const negationToken = + sourceCode.getFirstToken(node.left); + const fixRange = [ + negationToken.range[1], + node.range[1], + ]; + const text = sourceCode.text.slice( + fixRange[0], + fixRange[1], + ); + + return fixer.replaceTextRange( + fixRange, + `(${text})`, + ); + }, + }, + { + messageId: "suggestParenthesisedNegation", + fix(fixer) { + return fixer.replaceText( + node.left, + `(${sourceCode.getText(node.left)})`, + ); + }, + }, + ], + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-unsafe-optional-chaining.js b/lib/rules/no-unsafe-optional-chaining.js index c5d1f9999659..822864432fb1 100644 --- a/lib/rules/no-unsafe-optional-chaining.js +++ b/lib/rules/no-unsafe-optional-chaining.js @@ -6,7 +6,14 @@ "use strict"; const UNSAFE_ARITHMETIC_OPERATORS = new Set(["+", "-", "/", "*", "%", "**"]); -const UNSAFE_ASSIGNMENT_OPERATORS = new Set(["+=", "-=", "/=", "*=", "%=", "**="]); +const UNSAFE_ASSIGNMENT_OPERATORS = new Set([ + "+=", + "-=", + "/=", + "*=", + "%=", + "**=", +]); const UNSAFE_RELATIONAL_OPERATORS = new Set(["in", "instanceof"]); /** @@ -15,193 +22,200 @@ const UNSAFE_RELATIONAL_OPERATORS = new Set(["in", "instanceof"]); * @returns {boolean} `true` if a node is a destructuring pattern, otherwise `false` */ function isDestructuringPattern(node) { - return node.type === "ObjectPattern" || node.type === "ArrayPattern"; + return node.type === "ObjectPattern" || node.type === "ArrayPattern"; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", + meta: { + type: "problem", - defaultOptions: [{ - disallowArithmeticOperators: false - }], + defaultOptions: [ + { + disallowArithmeticOperators: false, + }, + ], - docs: { - description: "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining" - }, - schema: [{ - type: "object", - properties: { - disallowArithmeticOperators: { - type: "boolean" - } - }, - additionalProperties: false - }], - fixable: null, - messages: { - unsafeOptionalChain: "Unsafe usage of optional chaining. If it short-circuits with 'undefined' the evaluation will throw TypeError.", - unsafeArithmetic: "Unsafe arithmetic operation on optional chaining. It can result in NaN." - } - }, + docs: { + description: + "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining", + }, + schema: [ + { + type: "object", + properties: { + disallowArithmeticOperators: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + fixable: null, + messages: { + unsafeOptionalChain: + "Unsafe usage of optional chaining. If it short-circuits with 'undefined' the evaluation will throw TypeError.", + unsafeArithmetic: + "Unsafe arithmetic operation on optional chaining. It can result in NaN.", + }, + }, - create(context) { - const [{ disallowArithmeticOperators }] = context.options; + create(context) { + const [{ disallowArithmeticOperators }] = context.options; - /** - * Reports unsafe usage of optional chaining - * @param {ASTNode} node node to report - * @returns {void} - */ - function reportUnsafeUsage(node) { - context.report({ - messageId: "unsafeOptionalChain", - node - }); - } + /** + * Reports unsafe usage of optional chaining + * @param {ASTNode} node node to report + * @returns {void} + */ + function reportUnsafeUsage(node) { + context.report({ + messageId: "unsafeOptionalChain", + node, + }); + } - /** - * Reports unsafe arithmetic operation on optional chaining - * @param {ASTNode} node node to report - * @returns {void} - */ - function reportUnsafeArithmetic(node) { - context.report({ - messageId: "unsafeArithmetic", - node - }); - } + /** + * Reports unsafe arithmetic operation on optional chaining + * @param {ASTNode} node node to report + * @returns {void} + */ + function reportUnsafeArithmetic(node) { + context.report({ + messageId: "unsafeArithmetic", + node, + }); + } - /** - * Checks and reports if a node can short-circuit with `undefined` by optional chaining. - * @param {ASTNode} [node] node to check - * @param {Function} reportFunc report function - * @returns {void} - */ - function checkUndefinedShortCircuit(node, reportFunc) { - if (!node) { - return; - } - switch (node.type) { - case "LogicalExpression": - if (node.operator === "||" || node.operator === "??") { - checkUndefinedShortCircuit(node.right, reportFunc); - } else if (node.operator === "&&") { - checkUndefinedShortCircuit(node.left, reportFunc); - checkUndefinedShortCircuit(node.right, reportFunc); - } - break; - case "SequenceExpression": - checkUndefinedShortCircuit( - node.expressions.at(-1), - reportFunc - ); - break; - case "ConditionalExpression": - checkUndefinedShortCircuit(node.consequent, reportFunc); - checkUndefinedShortCircuit(node.alternate, reportFunc); - break; - case "AwaitExpression": - checkUndefinedShortCircuit(node.argument, reportFunc); - break; - case "ChainExpression": - reportFunc(node); - break; - default: - break; - } - } + /** + * Checks and reports if a node can short-circuit with `undefined` by optional chaining. + * @param {ASTNode} [node] node to check + * @param {Function} reportFunc report function + * @returns {void} + */ + function checkUndefinedShortCircuit(node, reportFunc) { + if (!node) { + return; + } + switch (node.type) { + case "LogicalExpression": + if (node.operator === "||" || node.operator === "??") { + checkUndefinedShortCircuit(node.right, reportFunc); + } else if (node.operator === "&&") { + checkUndefinedShortCircuit(node.left, reportFunc); + checkUndefinedShortCircuit(node.right, reportFunc); + } + break; + case "SequenceExpression": + checkUndefinedShortCircuit( + node.expressions.at(-1), + reportFunc, + ); + break; + case "ConditionalExpression": + checkUndefinedShortCircuit(node.consequent, reportFunc); + checkUndefinedShortCircuit(node.alternate, reportFunc); + break; + case "AwaitExpression": + checkUndefinedShortCircuit(node.argument, reportFunc); + break; + case "ChainExpression": + reportFunc(node); + break; + default: + break; + } + } - /** - * Checks unsafe usage of optional chaining - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkUnsafeUsage(node) { - checkUndefinedShortCircuit(node, reportUnsafeUsage); - } + /** + * Checks unsafe usage of optional chaining + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkUnsafeUsage(node) { + checkUndefinedShortCircuit(node, reportUnsafeUsage); + } - /** - * Checks unsafe arithmetic operations on optional chaining - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkUnsafeArithmetic(node) { - checkUndefinedShortCircuit(node, reportUnsafeArithmetic); - } + /** + * Checks unsafe arithmetic operations on optional chaining + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkUnsafeArithmetic(node) { + checkUndefinedShortCircuit(node, reportUnsafeArithmetic); + } - return { - "AssignmentExpression, AssignmentPattern"(node) { - if (isDestructuringPattern(node.left)) { - checkUnsafeUsage(node.right); - } - }, - "ClassDeclaration, ClassExpression"(node) { - checkUnsafeUsage(node.superClass); - }, - CallExpression(node) { - if (!node.optional) { - checkUnsafeUsage(node.callee); - } - }, - NewExpression(node) { - checkUnsafeUsage(node.callee); - }, - VariableDeclarator(node) { - if (isDestructuringPattern(node.id)) { - checkUnsafeUsage(node.init); - } - }, - MemberExpression(node) { - if (!node.optional) { - checkUnsafeUsage(node.object); - } - }, - TaggedTemplateExpression(node) { - checkUnsafeUsage(node.tag); - }, - ForOfStatement(node) { - checkUnsafeUsage(node.right); - }, - SpreadElement(node) { - if (node.parent && node.parent.type !== "ObjectExpression") { - checkUnsafeUsage(node.argument); - } - }, - BinaryExpression(node) { - if (UNSAFE_RELATIONAL_OPERATORS.has(node.operator)) { - checkUnsafeUsage(node.right); - } - if ( - disallowArithmeticOperators && - UNSAFE_ARITHMETIC_OPERATORS.has(node.operator) - ) { - checkUnsafeArithmetic(node.right); - checkUnsafeArithmetic(node.left); - } - }, - WithStatement(node) { - checkUnsafeUsage(node.object); - }, - UnaryExpression(node) { - if ( - disallowArithmeticOperators && - UNSAFE_ARITHMETIC_OPERATORS.has(node.operator) - ) { - checkUnsafeArithmetic(node.argument); - } - }, - AssignmentExpression(node) { - if ( - disallowArithmeticOperators && - UNSAFE_ASSIGNMENT_OPERATORS.has(node.operator) - ) { - checkUnsafeArithmetic(node.right); - } - } - }; - } + return { + "AssignmentExpression, AssignmentPattern"(node) { + if (isDestructuringPattern(node.left)) { + checkUnsafeUsage(node.right); + } + }, + "ClassDeclaration, ClassExpression"(node) { + checkUnsafeUsage(node.superClass); + }, + CallExpression(node) { + if (!node.optional) { + checkUnsafeUsage(node.callee); + } + }, + NewExpression(node) { + checkUnsafeUsage(node.callee); + }, + VariableDeclarator(node) { + if (isDestructuringPattern(node.id)) { + checkUnsafeUsage(node.init); + } + }, + MemberExpression(node) { + if (!node.optional) { + checkUnsafeUsage(node.object); + } + }, + TaggedTemplateExpression(node) { + checkUnsafeUsage(node.tag); + }, + ForOfStatement(node) { + checkUnsafeUsage(node.right); + }, + SpreadElement(node) { + if (node.parent && node.parent.type !== "ObjectExpression") { + checkUnsafeUsage(node.argument); + } + }, + BinaryExpression(node) { + if (UNSAFE_RELATIONAL_OPERATORS.has(node.operator)) { + checkUnsafeUsage(node.right); + } + if ( + disallowArithmeticOperators && + UNSAFE_ARITHMETIC_OPERATORS.has(node.operator) + ) { + checkUnsafeArithmetic(node.right); + checkUnsafeArithmetic(node.left); + } + }, + WithStatement(node) { + checkUnsafeUsage(node.object); + }, + UnaryExpression(node) { + if ( + disallowArithmeticOperators && + UNSAFE_ARITHMETIC_OPERATORS.has(node.operator) + ) { + checkUnsafeArithmetic(node.argument); + } + }, + AssignmentExpression(node) { + if ( + disallowArithmeticOperators && + UNSAFE_ASSIGNMENT_OPERATORS.has(node.operator) + ) { + checkUnsafeArithmetic(node.right); + } + }, + }; + }, }; diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js index fd1437c1606a..5de73247800a 100644 --- a/lib/rules/no-unused-expressions.js +++ b/lib/rules/no-unused-expressions.js @@ -15,7 +15,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true`. */ function alwaysTrue() { - return true; + return true; } /** @@ -23,168 +23,184 @@ function alwaysTrue() { * @returns {boolean} `false`. */ function alwaysFalse() { - return false; + return false; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unused expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-unused-expressions" - }, - - schema: [ - { - type: "object", - properties: { - allowShortCircuit: { - type: "boolean" - }, - allowTernary: { - type: "boolean" - }, - allowTaggedTemplates: { - type: "boolean" - }, - enforceForJSX: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - defaultOptions: [{ - allowShortCircuit: false, - allowTernary: false, - allowTaggedTemplates: false, - enforceForJSX: false - }], - - messages: { - unusedExpression: "Expected an assignment or function call and instead saw an expression." - } - }, - - create(context) { - const [{ - allowShortCircuit, - allowTernary, - allowTaggedTemplates, - enforceForJSX - }] = context.options; - - /** - * Has AST suggesting a directive. - * @param {ASTNode} node any node - * @returns {boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === "ExpressionStatement" && - node.expression.type === "Literal" && typeof node.expression.value === "string"; - } - - /** - * Gets the leading sequence of members in a list that pass the predicate. - * @param {Function} predicate ([a] -> Boolean) the function used to make the determination - * @param {a[]} list the input list - * @returns {a[]} the leading sequence of members in the given list that pass the given predicate - */ - function takeWhile(predicate, list) { - for (let i = 0; i < list.length; ++i) { - if (!predicate(list[i])) { - return list.slice(0, i); - } - } - return list.slice(); - } - - /** - * Gets leading directives nodes in a Node body. - * @param {ASTNode} node a Program or BlockStatement node - * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body - */ - function directives(node) { - return takeWhile(looksLikeDirective, node.body); - } - - /** - * Detect if a Node is a directive. - * @param {ASTNode} node any node - * @returns {boolean} whether the given node is considered a directive in its current position - */ - function isDirective(node) { - - /** - * https://tc39.es/ecma262/#directive-prologue - * - * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue. - * Class static blocks do not have directive prologue. - */ - return astUtils.isTopLevelExpressionStatement(node) && directives(node.parent).includes(node); - } - - /** - * The member functions return `true` if the type has no side-effects. - * Unknown nodes are handled as `false`, then this rule ignores those. - */ - const Checker = Object.assign(Object.create(null), { - isDisallowed(node) { - return (Checker[node.type] || alwaysFalse)(node); - }, - - ArrayExpression: alwaysTrue, - ArrowFunctionExpression: alwaysTrue, - BinaryExpression: alwaysTrue, - ChainExpression(node) { - return Checker.isDisallowed(node.expression); - }, - ClassExpression: alwaysTrue, - ConditionalExpression(node) { - if (allowTernary) { - return Checker.isDisallowed(node.consequent) || Checker.isDisallowed(node.alternate); - } - return true; - }, - FunctionExpression: alwaysTrue, - Identifier: alwaysTrue, - JSXElement() { - return enforceForJSX; - }, - JSXFragment() { - return enforceForJSX; - }, - Literal: alwaysTrue, - LogicalExpression(node) { - if (allowShortCircuit) { - return Checker.isDisallowed(node.right); - } - return true; - }, - MemberExpression: alwaysTrue, - MetaProperty: alwaysTrue, - ObjectExpression: alwaysTrue, - SequenceExpression: alwaysTrue, - TaggedTemplateExpression() { - return !allowTaggedTemplates; - }, - TemplateLiteral: alwaysTrue, - ThisExpression: alwaysTrue, - UnaryExpression(node) { - return node.operator !== "void" && node.operator !== "delete"; - } - }); - - return { - ExpressionStatement(node) { - if (Checker.isDisallowed(node.expression) && !isDirective(node)) { - context.report({ node, messageId: "unusedExpression" }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unused expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-unused-expressions", + }, + + schema: [ + { + type: "object", + properties: { + allowShortCircuit: { + type: "boolean", + }, + allowTernary: { + type: "boolean", + }, + allowTaggedTemplates: { + type: "boolean", + }, + enforceForJSX: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [ + { + allowShortCircuit: false, + allowTernary: false, + allowTaggedTemplates: false, + enforceForJSX: false, + }, + ], + + messages: { + unusedExpression: + "Expected an assignment or function call and instead saw an expression.", + }, + }, + + create(context) { + const [ + { + allowShortCircuit, + allowTernary, + allowTaggedTemplates, + enforceForJSX, + }, + ] = context.options; + + /** + * Has AST suggesting a directive. + * @param {ASTNode} node any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return ( + node.type === "ExpressionStatement" && + node.expression.type === "Literal" && + typeof node.expression.value === "string" + ); + } + + /** + * Gets the leading sequence of members in a list that pass the predicate. + * @param {Function} predicate ([a] -> Boolean) the function used to make the determination + * @param {a[]} list the input list + * @returns {a[]} the leading sequence of members in the given list that pass the given predicate + */ + function takeWhile(predicate, list) { + for (let i = 0; i < list.length; ++i) { + if (!predicate(list[i])) { + return list.slice(0, i); + } + } + return list.slice(); + } + + /** + * Gets leading directives nodes in a Node body. + * @param {ASTNode} node a Program or BlockStatement node + * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body + */ + function directives(node) { + return takeWhile(looksLikeDirective, node.body); + } + + /** + * Detect if a Node is a directive. + * @param {ASTNode} node any node + * @returns {boolean} whether the given node is considered a directive in its current position + */ + function isDirective(node) { + /** + * https://tc39.es/ecma262/#directive-prologue + * + * Only `FunctionBody`, `ScriptBody` and `ModuleBody` can have directive prologue. + * Class static blocks do not have directive prologue. + */ + return ( + astUtils.isTopLevelExpressionStatement(node) && + directives(node.parent).includes(node) + ); + } + + /** + * The member functions return `true` if the type has no side-effects. + * Unknown nodes are handled as `false`, then this rule ignores those. + */ + const Checker = Object.assign(Object.create(null), { + isDisallowed(node) { + return (Checker[node.type] || alwaysFalse)(node); + }, + + ArrayExpression: alwaysTrue, + ArrowFunctionExpression: alwaysTrue, + BinaryExpression: alwaysTrue, + ChainExpression(node) { + return Checker.isDisallowed(node.expression); + }, + ClassExpression: alwaysTrue, + ConditionalExpression(node) { + if (allowTernary) { + return ( + Checker.isDisallowed(node.consequent) || + Checker.isDisallowed(node.alternate) + ); + } + return true; + }, + FunctionExpression: alwaysTrue, + Identifier: alwaysTrue, + JSXElement() { + return enforceForJSX; + }, + JSXFragment() { + return enforceForJSX; + }, + Literal: alwaysTrue, + LogicalExpression(node) { + if (allowShortCircuit) { + return Checker.isDisallowed(node.right); + } + return true; + }, + MemberExpression: alwaysTrue, + MetaProperty: alwaysTrue, + ObjectExpression: alwaysTrue, + SequenceExpression: alwaysTrue, + TaggedTemplateExpression() { + return !allowTaggedTemplates; + }, + TemplateLiteral: alwaysTrue, + ThisExpression: alwaysTrue, + UnaryExpression(node) { + return node.operator !== "void" && node.operator !== "delete"; + }, + }); + + return { + ExpressionStatement(node) { + if ( + Checker.isDisallowed(node.expression) && + !isDirective(node) + ) { + context.report({ node, messageId: "unusedExpression" }); + } + }, + }; + }, }; diff --git a/lib/rules/no-unused-labels.js b/lib/rules/no-unused-labels.js index be06b324c49c..c93bf9177808 100644 --- a/lib/rules/no-unused-labels.js +++ b/lib/rules/no-unused-labels.js @@ -17,127 +17,142 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unused labels", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unused-labels" - }, - - schema: [], - - fixable: "code", - - messages: { - unused: "'{{name}}:' is defined but never used." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - let scopeInfo = null; - - /** - * Adds a scope info to the stack. - * @param {ASTNode} node A node to add. This is a LabeledStatement. - * @returns {void} - */ - function enterLabeledScope(node) { - scopeInfo = { - label: node.label.name, - used: false, - upper: scopeInfo - }; - } - - /** - * Checks if a `LabeledStatement` node is fixable. - * For a node to be fixable, there must be no comments between the label and the body. - * Furthermore, is must be possible to remove the label without turning the body statement into a - * directive after other fixes are applied. - * @param {ASTNode} node The node to evaluate. - * @returns {boolean} Whether or not the node is fixable. - */ - function isFixable(node) { - - /* - * Only perform a fix if there are no comments between the label and the body. This will be the case - * when there is exactly one token/comment (the ":") between the label and the body. - */ - if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !== - sourceCode.getTokenBefore(node.body, { includeComments: true })) { - return false; - } - - // Looking for the node's deepest ancestor which is not a `LabeledStatement`. - let ancestor = node.parent; - - while (ancestor.type === "LabeledStatement") { - ancestor = ancestor.parent; - } - - if (ancestor.type === "Program" || - (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) { - const { body } = node; - - if (body.type === "ExpressionStatement" && - ((body.expression.type === "Literal" && typeof body.expression.value === "string") || - astUtils.isStaticTemplateLiteral(body.expression))) { - return false; // potential directive - } - } - return true; - } - - /** - * Removes the top of the stack. - * At the same time, this reports the label if it's never used. - * @param {ASTNode} node A node to report. This is a LabeledStatement. - * @returns {void} - */ - function exitLabeledScope(node) { - if (!scopeInfo.used) { - context.report({ - node: node.label, - messageId: "unused", - data: node.label, - fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null - }); - } - - scopeInfo = scopeInfo.upper; - } - - /** - * Marks the label of a given node as used. - * @param {ASTNode} node A node to mark. This is a BreakStatement or - * ContinueStatement. - * @returns {void} - */ - function markAsUsed(node) { - if (!node.label) { - return; - } - - const label = node.label.name; - let info = scopeInfo; - - while (info) { - if (info.label === label) { - info.used = true; - break; - } - info = info.upper; - } - } - - return { - LabeledStatement: enterLabeledScope, - "LabeledStatement:exit": exitLabeledScope, - BreakStatement: markAsUsed, - ContinueStatement: markAsUsed - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unused labels", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unused-labels", + }, + + schema: [], + + fixable: "code", + + messages: { + unused: "'{{name}}:' is defined but never used.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + let scopeInfo = null; + + /** + * Adds a scope info to the stack. + * @param {ASTNode} node A node to add. This is a LabeledStatement. + * @returns {void} + */ + function enterLabeledScope(node) { + scopeInfo = { + label: node.label.name, + used: false, + upper: scopeInfo, + }; + } + + /** + * Checks if a `LabeledStatement` node is fixable. + * For a node to be fixable, there must be no comments between the label and the body. + * Furthermore, is must be possible to remove the label without turning the body statement into a + * directive after other fixes are applied. + * @param {ASTNode} node The node to evaluate. + * @returns {boolean} Whether or not the node is fixable. + */ + function isFixable(node) { + /* + * Only perform a fix if there are no comments between the label and the body. This will be the case + * when there is exactly one token/comment (the ":") between the label and the body. + */ + if ( + sourceCode.getTokenAfter(node.label, { + includeComments: true, + }) !== + sourceCode.getTokenBefore(node.body, { includeComments: true }) + ) { + return false; + } + + // Looking for the node's deepest ancestor which is not a `LabeledStatement`. + let ancestor = node.parent; + + while (ancestor.type === "LabeledStatement") { + ancestor = ancestor.parent; + } + + if ( + ancestor.type === "Program" || + (ancestor.type === "BlockStatement" && + astUtils.isFunction(ancestor.parent)) + ) { + const { body } = node; + + if ( + body.type === "ExpressionStatement" && + ((body.expression.type === "Literal" && + typeof body.expression.value === "string") || + astUtils.isStaticTemplateLiteral(body.expression)) + ) { + return false; // potential directive + } + } + return true; + } + + /** + * Removes the top of the stack. + * At the same time, this reports the label if it's never used. + * @param {ASTNode} node A node to report. This is a LabeledStatement. + * @returns {void} + */ + function exitLabeledScope(node) { + if (!scopeInfo.used) { + context.report({ + node: node.label, + messageId: "unused", + data: node.label, + fix: isFixable(node) + ? fixer => + fixer.removeRange([ + node.range[0], + node.body.range[0], + ]) + : null, + }); + } + + scopeInfo = scopeInfo.upper; + } + + /** + * Marks the label of a given node as used. + * @param {ASTNode} node A node to mark. This is a BreakStatement or + * ContinueStatement. + * @returns {void} + */ + function markAsUsed(node) { + if (!node.label) { + return; + } + + const label = node.label.name; + let info = scopeInfo; + + while (info) { + if (info.label === label) { + info.used = true; + break; + } + info = info.upper; + } + } + + return { + LabeledStatement: enterLabeledScope, + "LabeledStatement:exit": exitLabeledScope, + BreakStatement: markAsUsed, + ContinueStatement: markAsUsed, + }; + }, }; diff --git a/lib/rules/no-unused-private-class-members.js b/lib/rules/no-unused-private-class-members.js index bc05cd251807..becca31ebb85 100644 --- a/lib/rules/no-unused-private-class-members.js +++ b/lib/rules/no-unused-private-class-members.js @@ -11,185 +11,209 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow unused private class members", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members" - }, - - schema: [], - - messages: { - unusedPrivateClassMember: "'{{classMemberName}}' is defined but never used." - } - }, - - create(context) { - const trackedClasses = []; - - /** - * Check whether the current node is in a write only assignment. - * @param {ASTNode} privateIdentifierNode Node referring to a private identifier - * @returns {boolean} Whether the node is in a write only assignment - * @private - */ - function isWriteOnlyAssignment(privateIdentifierNode) { - const parentStatement = privateIdentifierNode.parent.parent; - const isAssignmentExpression = parentStatement.type === "AssignmentExpression"; - - if (!isAssignmentExpression && - parentStatement.type !== "ForInStatement" && - parentStatement.type !== "ForOfStatement" && - parentStatement.type !== "AssignmentPattern") { - return false; - } - - // It is a write-only usage, since we still allow usages on the right for reads - if (parentStatement.left !== privateIdentifierNode.parent) { - return false; - } - - // For any other operator (such as '+=') we still consider it a read operation - if (isAssignmentExpression && parentStatement.operator !== "=") { - - /* - * However, if the read operation is "discarded" in an empty statement, then - * we consider it write only. - */ - return parentStatement.parent.type === "ExpressionStatement"; - } - - return true; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - // Collect all declared members up front and assume they are all unused - ClassBody(classBodyNode) { - const privateMembers = new Map(); - - trackedClasses.unshift(privateMembers); - for (const bodyMember of classBodyNode.body) { - if (bodyMember.type === "PropertyDefinition" || bodyMember.type === "MethodDefinition") { - if (bodyMember.key.type === "PrivateIdentifier") { - privateMembers.set(bodyMember.key.name, { - declaredNode: bodyMember, - isAccessor: bodyMember.type === "MethodDefinition" && - (bodyMember.kind === "set" || bodyMember.kind === "get") - }); - } - } - } - }, - - /* - * Process all usages of the private identifier and remove a member from - * `declaredAndUnusedPrivateMembers` if we deem it used. - */ - PrivateIdentifier(privateIdentifierNode) { - const classBody = trackedClasses.find(classProperties => classProperties.has(privateIdentifierNode.name)); - - // Can't happen, as it is a parser to have a missing class body, but let's code defensively here. - if (!classBody) { - return; - } - - // In case any other usage was already detected, we can short circuit the logic here. - const memberDefinition = classBody.get(privateIdentifierNode.name); - - if (memberDefinition.isUsed) { - return; - } - - // The definition of the class member itself - if (privateIdentifierNode.parent.type === "PropertyDefinition" || - privateIdentifierNode.parent.type === "MethodDefinition") { - return; - } - - /* - * Any usage of an accessor is considered a read, as the getter/setter can have - * side-effects in its definition. - */ - if (memberDefinition.isAccessor) { - memberDefinition.isUsed = true; - return; - } - - // Any assignments to this member, except for assignments that also read - if (isWriteOnlyAssignment(privateIdentifierNode)) { - return; - } - - const wrappingExpressionType = privateIdentifierNode.parent.parent.type; - const parentOfWrappingExpressionType = privateIdentifierNode.parent.parent.parent.type; - - // A statement which only increments (`this.#x++;`) - if (wrappingExpressionType === "UpdateExpression" && - parentOfWrappingExpressionType === "ExpressionStatement") { - return; - } - - /* - * ({ x: this.#usedInDestructuring } = bar); - * - * But should treat the following as a read: - * ({ [this.#x]: a } = foo); - */ - if (wrappingExpressionType === "Property" && - parentOfWrappingExpressionType === "ObjectPattern" && - privateIdentifierNode.parent.parent.value === privateIdentifierNode.parent) { - return; - } - - // [...this.#unusedInRestPattern] = bar; - if (wrappingExpressionType === "RestElement") { - return; - } - - // [this.#unusedInAssignmentPattern] = bar; - if (wrappingExpressionType === "ArrayPattern") { - return; - } - - /* - * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used. - * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete - * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class - * as used, which is incorrect. - */ - memberDefinition.isUsed = true; - }, - - /* - * Post-process the class members and report any remaining members. - * Since private members can only be accessed in the current class context, - * we can safely assume that all usages are within the current class body. - */ - "ClassBody:exit"() { - const unusedPrivateMembers = trackedClasses.shift(); - - for (const [classMemberName, { declaredNode, isUsed }] of unusedPrivateMembers.entries()) { - if (isUsed) { - continue; - } - context.report({ - node: declaredNode, - loc: declaredNode.key.loc, - messageId: "unusedPrivateClassMember", - data: { - classMemberName: `#${classMemberName}` - } - }); - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow unused private class members", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unused-private-class-members", + }, + + schema: [], + + messages: { + unusedPrivateClassMember: + "'{{classMemberName}}' is defined but never used.", + }, + }, + + create(context) { + const trackedClasses = []; + + /** + * Check whether the current node is in a write only assignment. + * @param {ASTNode} privateIdentifierNode Node referring to a private identifier + * @returns {boolean} Whether the node is in a write only assignment + * @private + */ + function isWriteOnlyAssignment(privateIdentifierNode) { + const parentStatement = privateIdentifierNode.parent.parent; + const isAssignmentExpression = + parentStatement.type === "AssignmentExpression"; + + if ( + !isAssignmentExpression && + parentStatement.type !== "ForInStatement" && + parentStatement.type !== "ForOfStatement" && + parentStatement.type !== "AssignmentPattern" + ) { + return false; + } + + // It is a write-only usage, since we still allow usages on the right for reads + if (parentStatement.left !== privateIdentifierNode.parent) { + return false; + } + + // For any other operator (such as '+=') we still consider it a read operation + if (isAssignmentExpression && parentStatement.operator !== "=") { + /* + * However, if the read operation is "discarded" in an empty statement, then + * we consider it write only. + */ + return parentStatement.parent.type === "ExpressionStatement"; + } + + return true; + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + // Collect all declared members up front and assume they are all unused + ClassBody(classBodyNode) { + const privateMembers = new Map(); + + trackedClasses.unshift(privateMembers); + for (const bodyMember of classBodyNode.body) { + if ( + bodyMember.type === "PropertyDefinition" || + bodyMember.type === "MethodDefinition" + ) { + if (bodyMember.key.type === "PrivateIdentifier") { + privateMembers.set(bodyMember.key.name, { + declaredNode: bodyMember, + isAccessor: + bodyMember.type === "MethodDefinition" && + (bodyMember.kind === "set" || + bodyMember.kind === "get"), + }); + } + } + } + }, + + /* + * Process all usages of the private identifier and remove a member from + * `declaredAndUnusedPrivateMembers` if we deem it used. + */ + PrivateIdentifier(privateIdentifierNode) { + const classBody = trackedClasses.find(classProperties => + classProperties.has(privateIdentifierNode.name), + ); + + // Can't happen, as it is a parser to have a missing class body, but let's code defensively here. + if (!classBody) { + return; + } + + // In case any other usage was already detected, we can short circuit the logic here. + const memberDefinition = classBody.get( + privateIdentifierNode.name, + ); + + if (memberDefinition.isUsed) { + return; + } + + // The definition of the class member itself + if ( + privateIdentifierNode.parent.type === + "PropertyDefinition" || + privateIdentifierNode.parent.type === "MethodDefinition" + ) { + return; + } + + /* + * Any usage of an accessor is considered a read, as the getter/setter can have + * side-effects in its definition. + */ + if (memberDefinition.isAccessor) { + memberDefinition.isUsed = true; + return; + } + + // Any assignments to this member, except for assignments that also read + if (isWriteOnlyAssignment(privateIdentifierNode)) { + return; + } + + const wrappingExpressionType = + privateIdentifierNode.parent.parent.type; + const parentOfWrappingExpressionType = + privateIdentifierNode.parent.parent.parent.type; + + // A statement which only increments (`this.#x++;`) + if ( + wrappingExpressionType === "UpdateExpression" && + parentOfWrappingExpressionType === "ExpressionStatement" + ) { + return; + } + + /* + * ({ x: this.#usedInDestructuring } = bar); + * + * But should treat the following as a read: + * ({ [this.#x]: a } = foo); + */ + if ( + wrappingExpressionType === "Property" && + parentOfWrappingExpressionType === "ObjectPattern" && + privateIdentifierNode.parent.parent.value === + privateIdentifierNode.parent + ) { + return; + } + + // [...this.#unusedInRestPattern] = bar; + if (wrappingExpressionType === "RestElement") { + return; + } + + // [this.#unusedInAssignmentPattern] = bar; + if (wrappingExpressionType === "ArrayPattern") { + return; + } + + /* + * We can't delete the memberDefinition, as we need to keep track of which member we are marking as used. + * In the case of nested classes, we only mark the first member we encounter as used. If you were to delete + * the member, then any subsequent usage could incorrectly mark the member of an encapsulating parent class + * as used, which is incorrect. + */ + memberDefinition.isUsed = true; + }, + + /* + * Post-process the class members and report any remaining members. + * Since private members can only be accessed in the current class context, + * we can safely assume that all usages are within the current class body. + */ + "ClassBody:exit"() { + const unusedPrivateMembers = trackedClasses.shift(); + + for (const [ + classMemberName, + { declaredNode, isUsed }, + ] of unusedPrivateMembers.entries()) { + if (isUsed) { + continue; + } + context.report({ + node: declaredNode, + loc: declaredNode.key.loc, + messageId: "unusedPrivateClassMember", + data: { + classMemberName: `#${classMemberName}`, + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index 5b7a8d08528a..cf9f411e4a2d 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -41,1452 +41,1672 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow unused variables", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-unused-vars" - }, - - hasSuggestions: true, - - schema: [ - { - oneOf: [ - { - enum: ["all", "local"] - }, - { - type: "object", - properties: { - vars: { - enum: ["all", "local"] - }, - varsIgnorePattern: { - type: "string" - }, - args: { - enum: ["all", "after-used", "none"] - }, - ignoreRestSiblings: { - type: "boolean" - }, - argsIgnorePattern: { - type: "string" - }, - caughtErrors: { - enum: ["all", "none"] - }, - caughtErrorsIgnorePattern: { - type: "string" - }, - destructuredArrayIgnorePattern: { - type: "string" - }, - ignoreClassWithStaticInitBlock: { - type: "boolean" - }, - reportUsedIgnorePattern: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - unusedVar: "'{{varName}}' is {{action}} but never used{{additional}}.", - usedIgnoredVar: "'{{varName}}' is marked as ignored but is used{{additional}}.", - removeVar: "Remove unused variable '{{varName}}'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const REST_PROPERTY_TYPE = /^(?:RestElement|(?:Experimental)?RestProperty)$/u; - - const config = { - vars: "all", - args: "after-used", - ignoreRestSiblings: false, - caughtErrors: "all", - ignoreClassWithStaticInitBlock: false, - reportUsedIgnorePattern: false - }; - - const firstOption = context.options[0]; - - if (firstOption) { - if (typeof firstOption === "string") { - config.vars = firstOption; - } else { - config.vars = firstOption.vars || config.vars; - config.args = firstOption.args || config.args; - config.ignoreRestSiblings = firstOption.ignoreRestSiblings || config.ignoreRestSiblings; - config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; - config.ignoreClassWithStaticInitBlock = firstOption.ignoreClassWithStaticInitBlock || config.ignoreClassWithStaticInitBlock; - config.reportUsedIgnorePattern = firstOption.reportUsedIgnorePattern || config.reportUsedIgnorePattern; - - if (firstOption.varsIgnorePattern) { - config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u"); - } - - if (firstOption.argsIgnorePattern) { - config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u"); - } - - if (firstOption.caughtErrorsIgnorePattern) { - config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u"); - } - - if (firstOption.destructuredArrayIgnorePattern) { - config.destructuredArrayIgnorePattern = new RegExp(firstOption.destructuredArrayIgnorePattern, "u"); - } - } - } - - /** - * Determines what variable type a def is. - * @param {Object} def the declaration to check - * @returns {VariableType} a simple name for the types of variables that this rule supports - */ - function defToVariableType(def) { - - /* - * This `destructuredArrayIgnorePattern` error report works differently from the catch - * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the - * `destructuredArrayIgnorePattern` will be checked for array destructuring. However, - * for the purposes of the report, the currently defined behavior is to only inform the - * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact - * that the `varsIgnorePattern` would also apply). If it's not present, the user will be - * informed of the `varsIgnorePattern`, assuming that's present. - */ - if (config.destructuredArrayIgnorePattern && def.name.parent.type === "ArrayPattern") { - return "array-destructure"; - } - - switch (def.type) { - case "CatchClause": - return "catch-clause"; - case "Parameter": - return "parameter"; - - default: - return "variable"; - } - } - - /** - * Gets a given variable's description and configured ignore pattern - * based on the provided variableType - * @param {VariableType} variableType a simple name for the types of variables that this rule supports - * @throws {Error} (Unreachable) - * @returns {[string | undefined, string | undefined]} the given variable's description and - * ignore pattern - */ - function getVariableDescription(variableType) { - let pattern; - let variableDescription; - - switch (variableType) { - case "array-destructure": - pattern = config.destructuredArrayIgnorePattern; - variableDescription = "elements of array destructuring"; - break; - - case "catch-clause": - pattern = config.caughtErrorsIgnorePattern; - variableDescription = "caught errors"; - break; - - case "parameter": - pattern = config.argsIgnorePattern; - variableDescription = "args"; - break; - - case "variable": - pattern = config.varsIgnorePattern; - variableDescription = "vars"; - break; - - default: - throw new Error(`Unexpected variable type: ${variableType}`); - } - - if (pattern) { - pattern = pattern.toString(); - } - - return [variableDescription, pattern]; - } - - /** - * Generates the message data about the variable being defined and unused, - * including the ignore pattern if configured. - * @param {Variable} unusedVar eslint-scope variable object. - * @returns {UnusedVarMessageData} The message data to be used with this unused variable. - */ - function getDefinedMessageData(unusedVar) { - const def = unusedVar.defs && unusedVar.defs[0]; - let additionalMessageData = ""; - - if (def) { - const [variableDescription, pattern] = getVariableDescription(defToVariableType(def)); - - if (pattern && variableDescription) { - additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; - } - } - - return { - varName: unusedVar.name, - action: "defined", - additional: additionalMessageData - }; - } - - /** - * Generate the warning message about the variable being - * assigned and unused, including the ignore pattern if configured. - * @param {Variable} unusedVar eslint-scope variable object. - * @returns {UnusedVarMessageData} The message data to be used with this unused variable. - */ - function getAssignedMessageData(unusedVar) { - const def = unusedVar.defs && unusedVar.defs[0]; - let additionalMessageData = ""; - - if (def) { - const [variableDescription, pattern] = getVariableDescription(defToVariableType(def)); - - if (pattern && variableDescription) { - additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; - } - } - - return { - varName: unusedVar.name, - action: "assigned a value", - additional: additionalMessageData - }; - } - - /** - * Generate the warning message about a variable being used even though - * it is marked as being ignored. - * @param {Variable} variable eslint-scope variable object - * @param {VariableType} variableType a simple name for the types of variables that this rule supports - * @returns {UsedIgnoredVarMessageData} The message data to be used with - * this used ignored variable. - */ - function getUsedIgnoredMessageData(variable, variableType) { - const [variableDescription, pattern] = getVariableDescription(variableType); - - let additionalMessageData = ""; - - if (pattern && variableDescription) { - additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`; - } - - return { - varName: variable.name, - additional: additionalMessageData - }; - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const STATEMENT_TYPE = /(?:Statement|Declaration)$/u; - - /** - * Determines if a given variable is being exported from a module. - * @param {Variable} variable eslint-scope variable object. - * @returns {boolean} True if the variable is exported, false if not. - * @private - */ - function isExported(variable) { - - const definition = variable.defs[0]; - - if (definition) { - - let node = definition.node; - - if (node.type === "VariableDeclarator") { - node = node.parent; - } else if (definition.type === "Parameter") { - return false; - } - - return node.parent.type.indexOf("Export") === 0; - } - return false; - - } - - /** - * Checks whether a node is a sibling of the rest property or not. - * @param {ASTNode} node a node to check - * @returns {boolean} True if the node is a sibling of the rest property, otherwise false. - */ - function hasRestSibling(node) { - return node.type === "Property" && - node.parent.type === "ObjectPattern" && - REST_PROPERTY_TYPE.test(node.parent.properties.at(-1).type); - } - - /** - * Determines if a variable has a sibling rest property - * @param {Variable} variable eslint-scope variable object. - * @returns {boolean} True if the variable has a sibling rest property, false if not. - * @private - */ - function hasRestSpreadSibling(variable) { - if (config.ignoreRestSiblings) { - const hasRestSiblingDefinition = variable.defs.some(def => hasRestSibling(def.name.parent)); - const hasRestSiblingReference = variable.references.some(ref => hasRestSibling(ref.identifier.parent)); - - return hasRestSiblingDefinition || hasRestSiblingReference; - } - - return false; - } - - /** - * Determines if a reference is a read operation. - * @param {Reference} ref An eslint-scope Reference - * @returns {boolean} whether the given reference represents a read operation - * @private - */ - function isReadRef(ref) { - return ref.isRead(); - } - - /** - * Determine if an identifier is referencing an enclosing function name. - * @param {Reference} ref The reference to check. - * @param {ASTNode[]} nodes The candidate function nodes. - * @returns {boolean} True if it's a self-reference, false if not. - * @private - */ - function isSelfReference(ref, nodes) { - let scope = ref.from; - - while (scope) { - if (nodes.includes(scope.block)) { - return true; - } - - scope = scope.upper; - } - - return false; - } - - /** - * Gets a list of function definitions for a specified variable. - * @param {Variable} variable eslint-scope variable object. - * @returns {ASTNode[]} Function nodes. - * @private - */ - function getFunctionDefinitions(variable) { - const functionDefinitions = []; - - variable.defs.forEach(def => { - const { type, node } = def; - - // FunctionDeclarations - if (type === "FunctionName") { - functionDefinitions.push(node); - } - - // FunctionExpressions - if (type === "Variable" && node.init && - (node.init.type === "FunctionExpression" || node.init.type === "ArrowFunctionExpression")) { - functionDefinitions.push(node.init); - } - }); - return functionDefinitions; - } - - /** - * Checks the position of given nodes. - * @param {ASTNode} inner A node which is expected as inside. - * @param {ASTNode} outer A node which is expected as outside. - * @returns {boolean} `true` if the `inner` node exists in the `outer` node. - * @private - */ - function isInside(inner, outer) { - return ( - inner.range[0] >= outer.range[0] && - inner.range[1] <= outer.range[1] - ); - } - - /** - * Checks whether a given node is unused expression or not. - * @param {ASTNode} node The node itself - * @returns {boolean} The node is an unused expression. - * @private - */ - function isUnusedExpression(node) { - const parent = node.parent; - - if (parent.type === "ExpressionStatement") { - return true; - } - - if (parent.type === "SequenceExpression") { - const isLastExpression = parent.expressions.at(-1) === node; - - if (!isLastExpression) { - return true; - } - return isUnusedExpression(parent); - } - - return false; - } - - /** - * If a given reference is left-hand side of an assignment, this gets - * the right-hand side node of the assignment. - * - * In the following cases, this returns null. - * - * - The reference is not the LHS of an assignment expression. - * - The reference is inside of a loop. - * - The reference is inside of a function scope which is different from - * the declaration. - * @param {eslint-scope.Reference} ref A reference to check. - * @param {ASTNode} prevRhsNode The previous RHS node. - * @returns {ASTNode|null} The RHS node or null. - * @private - */ - function getRhsNode(ref, prevRhsNode) { - const id = ref.identifier; - const parent = id.parent; - const refScope = ref.from.variableScope; - const varScope = ref.resolved.scope.variableScope; - const canBeUsedLater = refScope !== varScope || astUtils.isInLoop(id); - - /* - * Inherits the previous node if this reference is in the node. - * This is for `a = a + a`-like code. - */ - if (prevRhsNode && isInside(id, prevRhsNode)) { - return prevRhsNode; - } - - if (parent.type === "AssignmentExpression" && - isUnusedExpression(parent) && - id === parent.left && - !canBeUsedLater - ) { - return parent.right; - } - return null; - } - - /** - * Checks whether a given function node is stored to somewhere or not. - * If the function node is stored, the function can be used later. - * @param {ASTNode} funcNode A function node to check. - * @param {ASTNode} rhsNode The RHS node of the previous assignment. - * @returns {boolean} `true` if under the following conditions: - * - the funcNode is assigned to a variable. - * - the funcNode is bound as an argument of a function call. - * - the function is bound to a property and the object satisfies above conditions. - * @private - */ - function isStorableFunction(funcNode, rhsNode) { - let node = funcNode; - let parent = funcNode.parent; - - while (parent && isInside(parent, rhsNode)) { - switch (parent.type) { - case "SequenceExpression": - if (parent.expressions.at(-1) !== node) { - return false; - } - break; - - case "CallExpression": - case "NewExpression": - return parent.callee !== node; - - case "AssignmentExpression": - case "TaggedTemplateExpression": - case "YieldExpression": - return true; - - default: - if (STATEMENT_TYPE.test(parent.type)) { - - /* - * If it encountered statements, this is a complex pattern. - * Since analyzing complex patterns is hard, this returns `true` to avoid false positive. - */ - return true; - } - } - - node = parent; - parent = parent.parent; - } - - return false; - } - - /** - * Checks whether a given Identifier node exists inside of a function node which can be used later. - * - * "can be used later" means: - * - the function is assigned to a variable. - * - the function is bound to a property and the object can be used later. - * - the function is bound as an argument of a function call. - * - * If a reference exists in a function which can be used later, the reference is read when the function is called. - * @param {ASTNode} id An Identifier node to check. - * @param {ASTNode} rhsNode The RHS node of the previous assignment. - * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later. - * @private - */ - function isInsideOfStorableFunction(id, rhsNode) { - const funcNode = astUtils.getUpperFunction(id); - - return ( - funcNode && - isInside(funcNode, rhsNode) && - isStorableFunction(funcNode, rhsNode) - ); - } - - /** - * Checks whether a given reference is a read to update itself or not. - * @param {eslint-scope.Reference} ref A reference to check. - * @param {ASTNode} rhsNode The RHS node of the previous assignment. - * @returns {boolean} The reference is a read to update itself. - * @private - */ - function isReadForItself(ref, rhsNode) { - const id = ref.identifier; - const parent = id.parent; - - return ref.isRead() && ( - - // self update. e.g. `a += 1`, `a++` - ( - ( - parent.type === "AssignmentExpression" && - parent.left === id && - isUnusedExpression(parent) && - !astUtils.isLogicalAssignmentOperator(parent.operator) - ) || - ( - parent.type === "UpdateExpression" && - isUnusedExpression(parent) - ) - ) || - - // in RHS of an assignment for itself. e.g. `a = a + 1` - ( - rhsNode && - isInside(id, rhsNode) && - !isInsideOfStorableFunction(id, rhsNode) - ) - ); - } - - /** - * Determine if an identifier is used either in for-in or for-of loops. - * @param {Reference} ref The reference to check. - * @returns {boolean} whether reference is used in the for-in loops - * @private - */ - function isForInOfRef(ref) { - let target = ref.identifier.parent; - - - // "for (var ...) { return; }" - if (target.type === "VariableDeclarator") { - target = target.parent.parent; - } - - if (target.type !== "ForInStatement" && target.type !== "ForOfStatement") { - return false; - } - - // "for (...) { return; }" - if (target.body.type === "BlockStatement") { - target = target.body.body[0]; - - // "for (...) return;" - } else { - target = target.body; - } - - // For empty loop body - if (!target) { - return false; - } - - return target.type === "ReturnStatement"; - } - - /** - * Determines if the variable is used. - * @param {Variable} variable The variable to check. - * @returns {boolean} True if the variable is used - * @private - */ - function isUsedVariable(variable) { - if (variable.eslintUsed) { - return true; - } - - const functionNodes = getFunctionDefinitions(variable); - const isFunctionDefinition = functionNodes.length > 0; - - let rhsNode = null; - - return variable.references.some(ref => { - if (isForInOfRef(ref)) { - return true; - } - - const forItself = isReadForItself(ref, rhsNode); - - rhsNode = getRhsNode(ref, rhsNode); - - return ( - isReadRef(ref) && - !forItself && - !(isFunctionDefinition && isSelfReference(ref, functionNodes)) - ); - }); - } - - /** - * Checks whether the given variable is after the last used parameter. - * @param {eslint-scope.Variable} variable The variable to check. - * @returns {boolean} `true` if the variable is defined after the last - * used parameter. - */ - function isAfterLastUsedArg(variable) { - const def = variable.defs[0]; - const params = sourceCode.getDeclaredVariables(def.node); - const posteriorParams = params.slice(params.indexOf(variable) + 1); - - // If any used parameters occur after this parameter, do not report. - return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed); - } - - /** - * Gets an array of variables without read references. - * @param {Scope} scope an eslint-scope Scope object. - * @param {Variable[]} unusedVars an array that saving result. - * @returns {Variable[]} unused variables of the scope and descendant scopes. - * @private - */ - function collectUnusedVariables(scope, unusedVars) { - const variables = scope.variables; - const childScopes = scope.childScopes; - let i, l; - - if (scope.type !== "global" || config.vars === "all") { - for (i = 0, l = variables.length; i < l; ++i) { - const variable = variables[i]; - - // skip a variable of class itself name in the class scope - if (scope.type === "class" && scope.block.id === variable.identifiers[0]) { - continue; - } - - // skip function expression names - if (scope.functionExpressionScope) { - continue; - } - - // skip variables marked with markVariableAsUsed() - if (!config.reportUsedIgnorePattern && variable.eslintUsed) { - continue; - } - - // skip implicit "arguments" variable - if (scope.type === "function" && variable.name === "arguments" && variable.identifiers.length === 0) { - continue; - } - - // explicit global variables don't have definitions. - const def = variable.defs[0]; - - if (def) { - const type = def.type; - const refUsedInArrayPatterns = variable.references.some(ref => ref.identifier.parent.type === "ArrayPattern"); - - // skip elements of array destructuring patterns - if ( - ( - def.name.parent.type === "ArrayPattern" || - refUsedInArrayPatterns - ) && - config.destructuredArrayIgnorePattern && - config.destructuredArrayIgnorePattern.test(def.name.name) - ) { - if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { - context.report({ - node: def.name, - messageId: "usedIgnoredVar", - data: getUsedIgnoredMessageData(variable, "array-destructure") - }); - } - - continue; - } - - if (type === "ClassName") { - const hasStaticBlock = def.node.body.body.some(node => node.type === "StaticBlock"); - - if (config.ignoreClassWithStaticInitBlock && hasStaticBlock) { - continue; - } - } - - // skip catch variables - if (type === "CatchClause") { - if (config.caughtErrors === "none") { - continue; - } - - // skip ignored parameters - if (config.caughtErrorsIgnorePattern && config.caughtErrorsIgnorePattern.test(def.name.name)) { - if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { - context.report({ - node: def.name, - messageId: "usedIgnoredVar", - data: getUsedIgnoredMessageData(variable, "catch-clause") - }); - } - - continue; - } - } else if (type === "Parameter") { - - // skip any setter argument - if ((def.node.parent.type === "Property" || def.node.parent.type === "MethodDefinition") && def.node.parent.kind === "set") { - continue; - } - - // if "args" option is "none", skip any parameter - if (config.args === "none") { - continue; - } - - // skip ignored parameters - if (config.argsIgnorePattern && config.argsIgnorePattern.test(def.name.name)) { - if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { - context.report({ - node: def.name, - messageId: "usedIgnoredVar", - data: getUsedIgnoredMessageData(variable, "parameter") - }); - } - - continue; - } - - // if "args" option is "after-used", skip used variables - if (config.args === "after-used" && astUtils.isFunction(def.name.parent) && !isAfterLastUsedArg(variable)) { - continue; - } - } else { - - // skip ignored variables - if (config.varsIgnorePattern && config.varsIgnorePattern.test(def.name.name)) { - if (config.reportUsedIgnorePattern && isUsedVariable(variable)) { - context.report({ - node: def.name, - messageId: "usedIgnoredVar", - data: getUsedIgnoredMessageData(variable, "variable") - }); - } - - continue; - } - } - } - - if (!isUsedVariable(variable) && !isExported(variable) && !hasRestSpreadSibling(variable)) { - unusedVars.push(variable); - } - } - } - - for (i = 0, l = childScopes.length; i < l; ++i) { - collectUnusedVariables(childScopes[i], unusedVars); - } - - return unusedVars; - } - - /** - * fixes unused variables - * @param {Object} fixer fixer object - * @param {Object} unusedVar unused variable to fix - * @returns {Object} fixer object - */ - function handleFixes(fixer, unusedVar) { - const id = unusedVar.identifiers[0]; - const parent = id.parent; - const parentType = parent.type; - const tokenBefore = sourceCode.getTokenBefore(id); - const tokenAfter = sourceCode.getTokenAfter(id); - const isFunction = astUtils.isFunction; - const isLoop = astUtils.isLoop; - const allWriteReferences = unusedVar.references.filter(ref => ref.isWrite()); - - /** - * get range from token before of a given node - * @param {ASTNode} node node of identifier - * @param {number} skips number of token to skip - * @returns {number} start range of token before the identifier - */ - function getPreviousTokenStart(node, skips) { - return sourceCode.getTokenBefore(node, skips).range[0]; - } - - /** - * get range to token after of a given node - * @param {ASTNode} node node of identifier - * @param {number} skips number of token to skip - * @returns {number} end range of token after the identifier - */ - function getNextTokenEnd(node, skips) { - return sourceCode.getTokenAfter(node, skips).range[1]; - } - - /** - * get the value of token before of a given node - * @param {ASTNode} node node of identifier - * @returns {string} value of token before the identifier - */ - function getTokenBeforeValue(node) { - return sourceCode.getTokenBefore(node).value; - } - - /** - * get the value of token after of a given node - * @param {ASTNode} node node of identifier - * @returns {string} value of token after the identifier - */ - function getTokenAfterValue(node) { - return sourceCode.getTokenAfter(node).value; - } - - /** - * Check if an array has a single element with null as other element. - * @param {ASTNode} node ArrayPattern node - * @returns {boolean} true if array has single element with other null elements - */ - function hasSingleElement(node) { - return node.elements.filter(e => e !== null).length === 1; - } - - /** - * check whether import specifier has an import of particular type - * @param {ASTNode} node ImportDeclaration node - * @param {string} type type of import to check - * @returns {boolean} true if import specifier has import of specified type - */ - function hasImportOfCertainType(node, type) { - return node.specifiers.some(e => e.type === type); - } - - /** - * Check whether declaration is safe to remove or not - * @param {ASTNode} nextToken next token of unused variable - * @param {ASTNode} prevToken previous token of unused variable - * @returns {boolean} true if declaration is not safe to remove - */ - function isDeclarationNotSafeToRemove(nextToken, prevToken) { - return ( - (nextToken.type === "String") || - ( - prevToken && - !astUtils.isSemicolonToken(prevToken) && - !astUtils.isOpeningBraceToken(prevToken) - ) - ); - } - - /** - * give fixes for unused variables in function parameters - * @param {ASTNode} node node to check - * @returns {Object} fixer object - */ - function fixFunctionParameters(node) { - const parentNode = node.parent; - - if (isFunction(parentNode)) { - - // remove unused function parameter if there is only a single parameter - if (parentNode.params.length === 1) { - return fixer.removeRange(node.range); - } - - // remove first unused function parameter when there are multiple parameters - if (getTokenBeforeValue(node) === "(" && getTokenAfterValue(node) === ",") { - return fixer.removeRange([node.range[0], getNextTokenEnd(node)]); - } - - // remove unused function parameters except first one when there are multiple parameters - return fixer.removeRange([getPreviousTokenStart(node), node.range[1]]); - } - - return null; - } - - /** - * fix unused variable declarations and function parameters - * @param {ASTNode} node parent node to identifier - * @returns {Object} fixer object - */ - function fixVariables(node) { - const parentNode = node.parent; - - // remove unused declared variables such as var a = b; or var a = b, c; - if (parentNode.type === "VariableDeclarator") { - - // skip variable in for (const [ foo ] of bar); - if (isLoop(parentNode.parent.parent)) { - return null; - } - - /* - * remove unused declared variable with single declaration such as 'var a = b;' - * remove complete declaration when there is an unused variable in 'const { a } = foo;', same for arrays. - */ - if (parentNode.parent.declarations.length === 1) { - - // if next token is a string it could become a directive if node is removed -> no suggestion. - const nextToken = sourceCode.getTokenAfter(parentNode.parent); - - // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion. - const prevToken = sourceCode.getTokenBefore(parentNode.parent); - - if (nextToken && isDeclarationNotSafeToRemove(nextToken, prevToken)) { - return null; - } - - return fixer.removeRange(parentNode.parent.range); - } - - /* - * remove unused declared variable with multiple declaration except first one such as 'var a = b, c = d;' - * fix 'let bar = "hello", { a } = foo;' to 'let bar = "hello";' if 'a' is unused, same for arrays. - */ - if (getTokenBeforeValue(parentNode) === ",") { - return fixer.removeRange([getPreviousTokenStart(parentNode), parentNode.range[1]]); - } - - /* - * remove first unused declared variable when there are multiple declarations - * fix 'let { a } = foo, bar = "hello";' to 'let bar = "hello";' if 'a' is unused, same for arrays. - */ - return fixer.removeRange([parentNode.range[0], getNextTokenEnd(parentNode)]); - } - - // fixes [{a: {k}}], [{a: [k]}] - if (getTokenBeforeValue(node) === ":") { - if (parentNode.parent.type === "ObjectPattern") { - // eslint-disable-next-line no-use-before-define -- due to interdependency of functions - return fixObjectWithValueSeparator(node); - } - } - - // fix unused function parameters - return fixFunctionParameters(node); - } - - /** - * fix nested object like { a: { b } } - * @param {ASTNode} node parent node to check - * @returns {Object} fixer object - */ - function fixNestedObjectVariable(node) { - const parentNode = node.parent; - - // fix for { a: { b: { c: { d } } } } - if ( - parentNode.parent.parent.parent.type === "ObjectPattern" && - parentNode.parent.properties.length === 1 - ) { - return fixNestedObjectVariable(parentNode.parent); - } - - // fix for { a: { b } } - if (parentNode.parent.type === "ObjectPattern") { - - // fix for unused variables in dectructured object with single property in variable decalartion and function parameter - if (parentNode.parent.properties.length === 1) { - return fixVariables(parentNode.parent); - } - - // fix for first unused property when there are multiple properties such as '{ a: { b }, c }' - if (getTokenBeforeValue(parentNode) === "{") { - return fixer.removeRange( - [parentNode.range[0], getNextTokenEnd(parentNode)] - ); - } - - // fix for unused property except first one when there are multiple properties such as '{ k, a: { b } }' - return fixer.removeRange([getPreviousTokenStart(parentNode), parentNode.range[1]]); - } - - return null; - } - - /** - * fix unused variables in array and nested array - * @param {ASTNode} node parent node to check - * @returns {Object} fixer object - */ - function fixNestedArrayVariable(node) { - const parentNode = node.parent; - - // fix for nested arrays [[ a ]] - if (parentNode.parent.type === "ArrayPattern" && hasSingleElement(parentNode)) { - return fixNestedArrayVariable(parentNode); - } - - if (hasSingleElement(parentNode)) { - - // fixes { a: [{ b }] } or { a: [[ b ]] } - if (getTokenBeforeValue(parentNode) === ":") { - return fixVariables(parentNode); - } - - // fixes [a, ...[[ b ]]] or [a, ...[{ b }]] - if (parentNode.parent.type === "RestElement") { - // eslint-disable-next-line no-use-before-define -- due to interdependency of functions - return fixRestInPattern(parentNode.parent); - } - - // fix unused variables in destructured array in variable declaration or function parameter - return fixVariables(parentNode); - } - - // remove last unused array element - if ( - getTokenBeforeValue(node) === "," && - getTokenAfterValue(node) === "]" - ) { - return fixer.removeRange([getPreviousTokenStart(node), node.range[1]]); - } - - // remove unused array element - return fixer.removeRange(node.range); - } - - /** - * fix cases like {a: {k}} or {a: [k]} - * @param {ASTNode} node parent node to check - * @returns {Object} fixer object - */ - function fixObjectWithValueSeparator(node) { - const parentNode = node.parent.parent; - - // fix cases like [{a : { b }}] or [{a : [ b ]}] - if ( - parentNode.parent.type === "ArrayPattern" && - parentNode.properties.length === 1 - ) { - return fixNestedArrayVariable(parentNode); - } - - // fix cases like {a: {k}} or {a: [k]} - return fixNestedObjectVariable(node); - } - - /** - * fix ...[[a]] or ...[{a}] like patterns - * @param {ASTNode} node parent node to check - * @returns {Object} fixer object - */ - function fixRestInPattern(node) { - const parentNode = node.parent; - - // fix ...[[a]] or ...[{a}] in function parameters - if (isFunction(parentNode)) { - if (parentNode.params.length === 1) { - return fixer.removeRange(node.range); - } - - return fixer.removeRange([getPreviousTokenStart(node), node.range[1]]); - } - - // fix rest in nested array pattern like [[a, ...[b]]] - if (parentNode.type === "ArrayPattern") { - - // fix [[...[b]]] - if (hasSingleElement(parentNode)) { - if (parentNode.parent.type === "ArrayPattern") { - return fixNestedArrayVariable(parentNode); - } - - // fix 'const [...[b]] = foo; and function foo([...[b]]) {} - return fixVariables(parentNode); - } - - // fix [[a, ...[b]]] - return fixer.removeRange([getPreviousTokenStart(node), node.range[1]]); - } - - return null; - } - - // skip fix when variable has references that would be left behind - if (allWriteReferences.some(ref => ref.identifier.range[0] !== id.range[0])) { - return null; - } - - // remove declared variables such as var a; or var a, b; - if (parentType === "VariableDeclarator") { - if (parent.parent.declarations.length === 1) { - - // prevent fix of variable in forOf and forIn loops. - if (isLoop(parent.parent.parent) && parent.parent.parent.body !== parent.parent) { - return null; - } - - // removes only variable not semicolon in 'if (foo()) var bar;' or in 'loops' or in 'with' statement. - if ( - parent.parent.parent.type === "IfStatement" || - isLoop(parent.parent.parent) || - (parent.parent.parent.type === "WithStatement" && parent.parent.parent.body === parent.parent) - ) { - return fixer.replaceText(parent.parent, ";"); - } - - // if next token is a string it could become a directive if node is removed -> no suggestion. - const nextToken = sourceCode.getTokenAfter(parent.parent); - - // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion. - const prevToken = sourceCode.getTokenBefore(parent.parent); - - if (nextToken && isDeclarationNotSafeToRemove(nextToken, prevToken)) { - return null; - } - - // remove unused declared variable with single declaration like 'var a = b;' - return fixer.removeRange(parent.parent.range); - } - - // remove unused declared variable with multiple declaration except first one like 'var a = b, c = d;' - if (tokenBefore.value === ",") { - return fixer.removeRange([tokenBefore.range[0], parent.range[1]]); - } - - // remove first unused declared variable when there are multiple declarations - return fixer.removeRange([parent.range[0], getNextTokenEnd(parent)]); - } - - // remove variables in object patterns - if (parent.parent.type === "ObjectPattern") { - if (parent.parent.properties.length === 1) { - - // fix [a, ...{b}] - if (parent.parent.parent.type === "RestElement") { - return fixRestInPattern(parent.parent.parent); - } - - // fix [{ a }] - if (parent.parent.parent.type === "ArrayPattern") { - return fixNestedArrayVariable(parent.parent); - } - - /* - * var {a} = foo; - * function a({a}) {} - * fix const { a: { b } } = foo; - */ - return fixVariables(parent.parent); - } - - // fix const { a:b } = foo; - if (tokenBefore.value === ":") { - - // remove first unused variable in const { a:b } = foo; - if (getTokenBeforeValue(parent) === "{" && getTokenAfterValue(parent) === ",") { - return fixer.removeRange([parent.range[0], getNextTokenEnd(parent)]); - } - - // remove unused variables in const { a: b, c: d } = foo; except first one - return fixer.removeRange([getPreviousTokenStart(parent), id.range[1]]); - } - } - - // remove unused variables inside an array - if (parentType === "ArrayPattern") { - if (hasSingleElement(parent)) { - - // fix [a, ...[b]] - if (parent.parent.type === "RestElement") { - return fixRestInPattern(parent.parent); - } - - // fix [ [a] ] - if (parent.parent.type === "ArrayPattern") { - return fixNestedArrayVariable(parent); - } - - /* - * fix var [a] = foo; - * fix function foo([a]) {} - * fix const { a: [b] } = foo; - */ - return fixVariables(parent); - } - - // if "a" is unused in [a, b ,c] fixes to [, b, c] - if (tokenBefore.value === "," && tokenAfter.value === ",") { - return fixer.removeRange(id.range); - } - } - - // remove unused rest elements - if (parentType === "RestElement") { - - // fix [a, ...rest] - if (parent.parent.type === "ArrayPattern") { - if (hasSingleElement(parent.parent)) { - - // fix [[...rest]] when there is only rest element - if ( - parent.parent.parent.type === "ArrayPattern" - ) { - return fixNestedArrayVariable(parent.parent); - } - - // fix 'const [...rest] = foo;' and 'function foo([...rest]) {}' - return fixVariables(parent.parent); - } - - // fix [a, ...rest] - return fixer.removeRange([getPreviousTokenStart(id, 1), id.range[1]]); - } - - // fix { a, ...rest} - if (parent.parent.type === "ObjectPattern") { - - // fix 'const {...rest} = foo;' and 'function foo({...rest}) {}' - if (parent.parent.properties.length === 1) { - return fixVariables(parent.parent); - } - - // fix { a, ...rest} when there are multiple properties - return fixer.removeRange([getPreviousTokenStart(id, 1), id.range[1]]); - } - - // fix function foo(...rest) {} - if (isFunction(parent.parent)) { - - // remove unused rest in function parameter if there is only single parameter - if (parent.parent.params.length === 1) { - return fixer.removeRange(parent.range); - } - - // remove unused rest in function parameter if there multiple parameter - return fixer.removeRange([getPreviousTokenStart(parent), parent.range[1]]); - } - } - - if (parentType === "AssignmentPattern") { - - // fix [a = aDefault] - if (parent.parent.type === "ArrayPattern") { - return fixNestedArrayVariable(parent); - } - - // fix {a = aDefault} - if (parent.parent.parent.type === "ObjectPattern") { - if (parent.parent.parent.properties.length === 1) { - - // fixes [{a = aDefault}] - if (parent.parent.parent.parent.type === "ArrayPattern") { - return fixNestedArrayVariable(parent.parent.parent); - } - - // fix 'const {a = aDefault} = foo;' and 'function foo({a = aDefault}) {}' - return fixVariables(parent.parent.parent); - } - - // fix unused 'a' in {a = aDefault} if it is the first property - if ( - getTokenBeforeValue(parent.parent) === "{" && - getTokenAfterValue(parent.parent) === "," - ) { - return fixer.removeRange([parent.parent.range[0], getNextTokenEnd(parent.parent)]); - } - - // fix unused 'b' in {a, b = aDefault} if it is not the first property - return fixer.removeRange([getPreviousTokenStart(parent.parent), parent.parent.range[1]]); - } - - // fix unused assignment patterns in function parameters - if (isFunction(parent.parent)) { - return fixFunctionParameters(parent); - } - } - - // remove unused functions - if (parentType === "FunctionDeclaration" && parent.id === id) { - return fixer.removeRange(parent.range); - } - - // remove unused default import - if (parentType === "ImportDefaultSpecifier") { - - // remove unused default import when there are not other imports - if ( - !hasImportOfCertainType(parent.parent, "ImportSpecifier") && - !hasImportOfCertainType(parent.parent, "ImportNamespaceSpecifier") - ) { - return fixer.removeRange([parent.range[0], parent.parent.source.range[0]]); - } - - // remove unused default import when there are other imports also - return fixer.removeRange([id.range[0], tokenAfter.range[1]]); - } - - if (parentType === "ImportSpecifier") { - - // remove unused imports when there is a single import - if (parent.parent.specifiers.filter(e => e.type === "ImportSpecifier").length === 1) { - - // remove unused import when there is no default import - if (!hasImportOfCertainType(parent.parent, "ImportDefaultSpecifier")) { - return fixer.removeRange(parent.parent.range); - } - - // fixes "import foo from 'module';" to "import 'module';" - return fixer.removeRange([getPreviousTokenStart(parent, 1), tokenAfter.range[1]]); - } - - if (getTokenBeforeValue(parent) === "{") { - return fixer.removeRange([parent.range[0], getNextTokenEnd(parent)]); - } - - return fixer.removeRange([getPreviousTokenStart(parent), parent.range[1]]); - } - - if (parentType === "ImportNamespaceSpecifier") { - if (hasImportOfCertainType(parent.parent, "ImportDefaultSpecifier")) { - return fixer.removeRange([getPreviousTokenStart(parent), parent.range[1]]); - } - - // fixes "import * as foo from 'module';" to "import 'module';" - return fixer.removeRange([parent.range[0], parent.parent.source.range[0]]); - } - - // skip error in catch(error) variable - if (parentType === "CatchClause") { - return null; - } - - // remove unused declared classes - if (parentType === "ClassDeclaration") { - return fixer.removeRange(parent.range); - } - - // remove unused varible that is in a sequence [a,b] fixes to [a] - if (tokenBefore?.value === ",") { - return fixer.removeRange([tokenBefore.range[0], id.range[1]]); - } - - // remove unused varible that is in a sequence inside function arguments and object pattern - if (tokenAfter.value === ",") { - - // fix function foo(a, b) {} - if (tokenBefore.value === "(") { - return fixer.removeRange([id.range[0], tokenAfter.range[1]]); - } - - // fix const {a, b} = foo; - if (tokenBefore.value === "{") { - return fixer.removeRange([id.range[0], tokenAfter.range[1]]); - } - } - - if (parentType === "ArrowFunctionExpression" && parent.params.length === 1 && tokenAfter?.value !== ")") { - return fixer.replaceText(id, "()"); - } - - return fixer.removeRange(id.range); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - "Program:exit"(programNode) { - const unusedVars = collectUnusedVariables(sourceCode.getScope(programNode), []); - - for (let i = 0, l = unusedVars.length; i < l; ++i) { - const unusedVar = unusedVars[i]; - - // Report the first declaration. - if (unusedVar.defs.length > 0) { - - // report last write reference, https://github.com/eslint/eslint/issues/14324 - const writeReferences = unusedVar.references.filter(ref => ref.isWrite() && ref.from.variableScope === unusedVar.scope.variableScope); - - let referenceToReport; - - if (writeReferences.length > 0) { - referenceToReport = writeReferences.at(-1); - } - - context.report({ - node: referenceToReport ? referenceToReport.identifier : unusedVar.identifiers[0], - messageId: "unusedVar", - data: unusedVar.references.some(ref => ref.isWrite()) - ? getAssignedMessageData(unusedVar) - : getDefinedMessageData(unusedVar), - suggest: [ - { - messageId: "removeVar", - data: { - varName: unusedVar.name - }, - fix(fixer) { - return handleFixes(fixer, unusedVar); - } - } - ] - }); - - // If there are no regular declaration, report the first `/*globals*/` comment directive. - } else if (unusedVar.eslintExplicitGlobalComments) { - const directiveComment = unusedVar.eslintExplicitGlobalComments[0]; - - context.report({ - node: programNode, - loc: astUtils.getNameLocationInGlobalDirectiveComment(sourceCode, directiveComment, unusedVar.name), - messageId: "unusedVar", - data: getDefinedMessageData(unusedVar) - }); - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: "Disallow unused variables", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-unused-vars", + }, + + hasSuggestions: true, + + schema: [ + { + oneOf: [ + { + enum: ["all", "local"], + }, + { + type: "object", + properties: { + vars: { + enum: ["all", "local"], + }, + varsIgnorePattern: { + type: "string", + }, + args: { + enum: ["all", "after-used", "none"], + }, + ignoreRestSiblings: { + type: "boolean", + }, + argsIgnorePattern: { + type: "string", + }, + caughtErrors: { + enum: ["all", "none"], + }, + caughtErrorsIgnorePattern: { + type: "string", + }, + destructuredArrayIgnorePattern: { + type: "string", + }, + ignoreClassWithStaticInitBlock: { + type: "boolean", + }, + reportUsedIgnorePattern: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + unusedVar: + "'{{varName}}' is {{action}} but never used{{additional}}.", + usedIgnoredVar: + "'{{varName}}' is marked as ignored but is used{{additional}}.", + removeVar: "Remove unused variable '{{varName}}'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const REST_PROPERTY_TYPE = + /^(?:RestElement|(?:Experimental)?RestProperty)$/u; + + const config = { + vars: "all", + args: "after-used", + ignoreRestSiblings: false, + caughtErrors: "all", + ignoreClassWithStaticInitBlock: false, + reportUsedIgnorePattern: false, + }; + + const firstOption = context.options[0]; + + if (firstOption) { + if (typeof firstOption === "string") { + config.vars = firstOption; + } else { + config.vars = firstOption.vars || config.vars; + config.args = firstOption.args || config.args; + config.ignoreRestSiblings = + firstOption.ignoreRestSiblings || config.ignoreRestSiblings; + config.caughtErrors = + firstOption.caughtErrors || config.caughtErrors; + config.ignoreClassWithStaticInitBlock = + firstOption.ignoreClassWithStaticInitBlock || + config.ignoreClassWithStaticInitBlock; + config.reportUsedIgnorePattern = + firstOption.reportUsedIgnorePattern || + config.reportUsedIgnorePattern; + + if (firstOption.varsIgnorePattern) { + config.varsIgnorePattern = new RegExp( + firstOption.varsIgnorePattern, + "u", + ); + } + + if (firstOption.argsIgnorePattern) { + config.argsIgnorePattern = new RegExp( + firstOption.argsIgnorePattern, + "u", + ); + } + + if (firstOption.caughtErrorsIgnorePattern) { + config.caughtErrorsIgnorePattern = new RegExp( + firstOption.caughtErrorsIgnorePattern, + "u", + ); + } + + if (firstOption.destructuredArrayIgnorePattern) { + config.destructuredArrayIgnorePattern = new RegExp( + firstOption.destructuredArrayIgnorePattern, + "u", + ); + } + } + } + + /** + * Determines what variable type a def is. + * @param {Object} def the declaration to check + * @returns {VariableType} a simple name for the types of variables that this rule supports + */ + function defToVariableType(def) { + /* + * This `destructuredArrayIgnorePattern` error report works differently from the catch + * clause and parameter error reports. _Both_ the `varsIgnorePattern` and the + * `destructuredArrayIgnorePattern` will be checked for array destructuring. However, + * for the purposes of the report, the currently defined behavior is to only inform the + * user of the `destructuredArrayIgnorePattern` if it's present (regardless of the fact + * that the `varsIgnorePattern` would also apply). If it's not present, the user will be + * informed of the `varsIgnorePattern`, assuming that's present. + */ + if ( + config.destructuredArrayIgnorePattern && + def.name.parent.type === "ArrayPattern" + ) { + return "array-destructure"; + } + + switch (def.type) { + case "CatchClause": + return "catch-clause"; + case "Parameter": + return "parameter"; + + default: + return "variable"; + } + } + + /** + * Gets a given variable's description and configured ignore pattern + * based on the provided variableType + * @param {VariableType} variableType a simple name for the types of variables that this rule supports + * @throws {Error} (Unreachable) + * @returns {[string | undefined, string | undefined]} the given variable's description and + * ignore pattern + */ + function getVariableDescription(variableType) { + let pattern; + let variableDescription; + + switch (variableType) { + case "array-destructure": + pattern = config.destructuredArrayIgnorePattern; + variableDescription = "elements of array destructuring"; + break; + + case "catch-clause": + pattern = config.caughtErrorsIgnorePattern; + variableDescription = "caught errors"; + break; + + case "parameter": + pattern = config.argsIgnorePattern; + variableDescription = "args"; + break; + + case "variable": + pattern = config.varsIgnorePattern; + variableDescription = "vars"; + break; + + default: + throw new Error( + `Unexpected variable type: ${variableType}`, + ); + } + + if (pattern) { + pattern = pattern.toString(); + } + + return [variableDescription, pattern]; + } + + /** + * Generates the message data about the variable being defined and unused, + * including the ignore pattern if configured. + * @param {Variable} unusedVar eslint-scope variable object. + * @returns {UnusedVarMessageData} The message data to be used with this unused variable. + */ + function getDefinedMessageData(unusedVar) { + const def = unusedVar.defs && unusedVar.defs[0]; + let additionalMessageData = ""; + + if (def) { + const [variableDescription, pattern] = getVariableDescription( + defToVariableType(def), + ); + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } + } + + return { + varName: unusedVar.name, + action: "defined", + additional: additionalMessageData, + }; + } + + /** + * Generate the warning message about the variable being + * assigned and unused, including the ignore pattern if configured. + * @param {Variable} unusedVar eslint-scope variable object. + * @returns {UnusedVarMessageData} The message data to be used with this unused variable. + */ + function getAssignedMessageData(unusedVar) { + const def = unusedVar.defs && unusedVar.defs[0]; + let additionalMessageData = ""; + + if (def) { + const [variableDescription, pattern] = getVariableDescription( + defToVariableType(def), + ); + + if (pattern && variableDescription) { + additionalMessageData = `. Allowed unused ${variableDescription} must match ${pattern}`; + } + } + + return { + varName: unusedVar.name, + action: "assigned a value", + additional: additionalMessageData, + }; + } + + /** + * Generate the warning message about a variable being used even though + * it is marked as being ignored. + * @param {Variable} variable eslint-scope variable object + * @param {VariableType} variableType a simple name for the types of variables that this rule supports + * @returns {UsedIgnoredVarMessageData} The message data to be used with + * this used ignored variable. + */ + function getUsedIgnoredMessageData(variable, variableType) { + const [variableDescription, pattern] = + getVariableDescription(variableType); + + let additionalMessageData = ""; + + if (pattern && variableDescription) { + additionalMessageData = `. Used ${variableDescription} must not match ${pattern}`; + } + + return { + varName: variable.name, + additional: additionalMessageData, + }; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const STATEMENT_TYPE = /(?:Statement|Declaration)$/u; + + /** + * Determines if a given variable is being exported from a module. + * @param {Variable} variable eslint-scope variable object. + * @returns {boolean} True if the variable is exported, false if not. + * @private + */ + function isExported(variable) { + const definition = variable.defs[0]; + + if (definition) { + let node = definition.node; + + if (node.type === "VariableDeclarator") { + node = node.parent; + } else if (definition.type === "Parameter") { + return false; + } + + return node.parent.type.indexOf("Export") === 0; + } + return false; + } + + /** + * Checks whether a node is a sibling of the rest property or not. + * @param {ASTNode} node a node to check + * @returns {boolean} True if the node is a sibling of the rest property, otherwise false. + */ + function hasRestSibling(node) { + return ( + node.type === "Property" && + node.parent.type === "ObjectPattern" && + REST_PROPERTY_TYPE.test(node.parent.properties.at(-1).type) + ); + } + + /** + * Determines if a variable has a sibling rest property + * @param {Variable} variable eslint-scope variable object. + * @returns {boolean} True if the variable has a sibling rest property, false if not. + * @private + */ + function hasRestSpreadSibling(variable) { + if (config.ignoreRestSiblings) { + const hasRestSiblingDefinition = variable.defs.some(def => + hasRestSibling(def.name.parent), + ); + const hasRestSiblingReference = variable.references.some(ref => + hasRestSibling(ref.identifier.parent), + ); + + return hasRestSiblingDefinition || hasRestSiblingReference; + } + + return false; + } + + /** + * Determines if a reference is a read operation. + * @param {Reference} ref An eslint-scope Reference + * @returns {boolean} whether the given reference represents a read operation + * @private + */ + function isReadRef(ref) { + return ref.isRead(); + } + + /** + * Determine if an identifier is referencing an enclosing function name. + * @param {Reference} ref The reference to check. + * @param {ASTNode[]} nodes The candidate function nodes. + * @returns {boolean} True if it's a self-reference, false if not. + * @private + */ + function isSelfReference(ref, nodes) { + let scope = ref.from; + + while (scope) { + if (nodes.includes(scope.block)) { + return true; + } + + scope = scope.upper; + } + + return false; + } + + /** + * Gets a list of function definitions for a specified variable. + * @param {Variable} variable eslint-scope variable object. + * @returns {ASTNode[]} Function nodes. + * @private + */ + function getFunctionDefinitions(variable) { + const functionDefinitions = []; + + variable.defs.forEach(def => { + const { type, node } = def; + + // FunctionDeclarations + if (type === "FunctionName") { + functionDefinitions.push(node); + } + + // FunctionExpressions + if ( + type === "Variable" && + node.init && + (node.init.type === "FunctionExpression" || + node.init.type === "ArrowFunctionExpression") + ) { + functionDefinitions.push(node.init); + } + }); + return functionDefinitions; + } + + /** + * Checks the position of given nodes. + * @param {ASTNode} inner A node which is expected as inside. + * @param {ASTNode} outer A node which is expected as outside. + * @returns {boolean} `true` if the `inner` node exists in the `outer` node. + * @private + */ + function isInside(inner, outer) { + return ( + inner.range[0] >= outer.range[0] && + inner.range[1] <= outer.range[1] + ); + } + + /** + * Checks whether a given node is unused expression or not. + * @param {ASTNode} node The node itself + * @returns {boolean} The node is an unused expression. + * @private + */ + function isUnusedExpression(node) { + const parent = node.parent; + + if (parent.type === "ExpressionStatement") { + return true; + } + + if (parent.type === "SequenceExpression") { + const isLastExpression = parent.expressions.at(-1) === node; + + if (!isLastExpression) { + return true; + } + return isUnusedExpression(parent); + } + + return false; + } + + /** + * If a given reference is left-hand side of an assignment, this gets + * the right-hand side node of the assignment. + * + * In the following cases, this returns null. + * + * - The reference is not the LHS of an assignment expression. + * - The reference is inside of a loop. + * - The reference is inside of a function scope which is different from + * the declaration. + * @param {eslint-scope.Reference} ref A reference to check. + * @param {ASTNode} prevRhsNode The previous RHS node. + * @returns {ASTNode|null} The RHS node or null. + * @private + */ + function getRhsNode(ref, prevRhsNode) { + const id = ref.identifier; + const parent = id.parent; + const refScope = ref.from.variableScope; + const varScope = ref.resolved.scope.variableScope; + const canBeUsedLater = + refScope !== varScope || astUtils.isInLoop(id); + + /* + * Inherits the previous node if this reference is in the node. + * This is for `a = a + a`-like code. + */ + if (prevRhsNode && isInside(id, prevRhsNode)) { + return prevRhsNode; + } + + if ( + parent.type === "AssignmentExpression" && + isUnusedExpression(parent) && + id === parent.left && + !canBeUsedLater + ) { + return parent.right; + } + return null; + } + + /** + * Checks whether a given function node is stored to somewhere or not. + * If the function node is stored, the function can be used later. + * @param {ASTNode} funcNode A function node to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} `true` if under the following conditions: + * - the funcNode is assigned to a variable. + * - the funcNode is bound as an argument of a function call. + * - the function is bound to a property and the object satisfies above conditions. + * @private + */ + function isStorableFunction(funcNode, rhsNode) { + let node = funcNode; + let parent = funcNode.parent; + + while (parent && isInside(parent, rhsNode)) { + switch (parent.type) { + case "SequenceExpression": + if (parent.expressions.at(-1) !== node) { + return false; + } + break; + + case "CallExpression": + case "NewExpression": + return parent.callee !== node; + + case "AssignmentExpression": + case "TaggedTemplateExpression": + case "YieldExpression": + return true; + + default: + if (STATEMENT_TYPE.test(parent.type)) { + /* + * If it encountered statements, this is a complex pattern. + * Since analyzing complex patterns is hard, this returns `true` to avoid false positive. + */ + return true; + } + } + + node = parent; + parent = parent.parent; + } + + return false; + } + + /** + * Checks whether a given Identifier node exists inside of a function node which can be used later. + * + * "can be used later" means: + * - the function is assigned to a variable. + * - the function is bound to a property and the object can be used later. + * - the function is bound as an argument of a function call. + * + * If a reference exists in a function which can be used later, the reference is read when the function is called. + * @param {ASTNode} id An Identifier node to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} `true` if the `id` node exists inside of a function node which can be used later. + * @private + */ + function isInsideOfStorableFunction(id, rhsNode) { + const funcNode = astUtils.getUpperFunction(id); + + return ( + funcNode && + isInside(funcNode, rhsNode) && + isStorableFunction(funcNode, rhsNode) + ); + } + + /** + * Checks whether a given reference is a read to update itself or not. + * @param {eslint-scope.Reference} ref A reference to check. + * @param {ASTNode} rhsNode The RHS node of the previous assignment. + * @returns {boolean} The reference is a read to update itself. + * @private + */ + function isReadForItself(ref, rhsNode) { + const id = ref.identifier; + const parent = id.parent; + + return ( + ref.isRead() && + // self update. e.g. `a += 1`, `a++` + ((parent.type === "AssignmentExpression" && + parent.left === id && + isUnusedExpression(parent) && + !astUtils.isLogicalAssignmentOperator(parent.operator)) || + (parent.type === "UpdateExpression" && + isUnusedExpression(parent)) || + // in RHS of an assignment for itself. e.g. `a = a + 1` + (rhsNode && + isInside(id, rhsNode) && + !isInsideOfStorableFunction(id, rhsNode))) + ); + } + + /** + * Determine if an identifier is used either in for-in or for-of loops. + * @param {Reference} ref The reference to check. + * @returns {boolean} whether reference is used in the for-in loops + * @private + */ + function isForInOfRef(ref) { + let target = ref.identifier.parent; + + // "for (var ...) { return; }" + if (target.type === "VariableDeclarator") { + target = target.parent.parent; + } + + if ( + target.type !== "ForInStatement" && + target.type !== "ForOfStatement" + ) { + return false; + } + + // "for (...) { return; }" + if (target.body.type === "BlockStatement") { + target = target.body.body[0]; + + // "for (...) return;" + } else { + target = target.body; + } + + // For empty loop body + if (!target) { + return false; + } + + return target.type === "ReturnStatement"; + } + + /** + * Determines if the variable is used. + * @param {Variable} variable The variable to check. + * @returns {boolean} True if the variable is used + * @private + */ + function isUsedVariable(variable) { + if (variable.eslintUsed) { + return true; + } + + const functionNodes = getFunctionDefinitions(variable); + const isFunctionDefinition = functionNodes.length > 0; + + let rhsNode = null; + + return variable.references.some(ref => { + if (isForInOfRef(ref)) { + return true; + } + + const forItself = isReadForItself(ref, rhsNode); + + rhsNode = getRhsNode(ref, rhsNode); + + return ( + isReadRef(ref) && + !forItself && + !( + isFunctionDefinition && + isSelfReference(ref, functionNodes) + ) + ); + }); + } + + /** + * Checks whether the given variable is after the last used parameter. + * @param {eslint-scope.Variable} variable The variable to check. + * @returns {boolean} `true` if the variable is defined after the last + * used parameter. + */ + function isAfterLastUsedArg(variable) { + const def = variable.defs[0]; + const params = sourceCode.getDeclaredVariables(def.node); + const posteriorParams = params.slice(params.indexOf(variable) + 1); + + // If any used parameters occur after this parameter, do not report. + return !posteriorParams.some( + v => v.references.length > 0 || v.eslintUsed, + ); + } + + /** + * Gets an array of variables without read references. + * @param {Scope} scope an eslint-scope Scope object. + * @param {Variable[]} unusedVars an array that saving result. + * @returns {Variable[]} unused variables of the scope and descendant scopes. + * @private + */ + function collectUnusedVariables(scope, unusedVars) { + const variables = scope.variables; + const childScopes = scope.childScopes; + let i, l; + + if (scope.type !== "global" || config.vars === "all") { + for (i = 0, l = variables.length; i < l; ++i) { + const variable = variables[i]; + + // skip a variable of class itself name in the class scope + if ( + scope.type === "class" && + scope.block.id === variable.identifiers[0] + ) { + continue; + } + + // skip function expression names + if (scope.functionExpressionScope) { + continue; + } + + // skip variables marked with markVariableAsUsed() + if ( + !config.reportUsedIgnorePattern && + variable.eslintUsed + ) { + continue; + } + + // skip implicit "arguments" variable + if ( + scope.type === "function" && + variable.name === "arguments" && + variable.identifiers.length === 0 + ) { + continue; + } + + // explicit global variables don't have definitions. + const def = variable.defs[0]; + + if (def) { + const type = def.type; + const refUsedInArrayPatterns = variable.references.some( + ref => + ref.identifier.parent.type === "ArrayPattern", + ); + + // skip elements of array destructuring patterns + if ( + (def.name.parent.type === "ArrayPattern" || + refUsedInArrayPatterns) && + config.destructuredArrayIgnorePattern && + config.destructuredArrayIgnorePattern.test( + def.name.name, + ) + ) { + if ( + config.reportUsedIgnorePattern && + isUsedVariable(variable) + ) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData( + variable, + "array-destructure", + ), + }); + } + + continue; + } + + if (type === "ClassName") { + const hasStaticBlock = def.node.body.body.some( + node => node.type === "StaticBlock", + ); + + if ( + config.ignoreClassWithStaticInitBlock && + hasStaticBlock + ) { + continue; + } + } + + // skip catch variables + if (type === "CatchClause") { + if (config.caughtErrors === "none") { + continue; + } + + // skip ignored parameters + if ( + config.caughtErrorsIgnorePattern && + config.caughtErrorsIgnorePattern.test( + def.name.name, + ) + ) { + if ( + config.reportUsedIgnorePattern && + isUsedVariable(variable) + ) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData( + variable, + "catch-clause", + ), + }); + } + + continue; + } + } else if (type === "Parameter") { + // skip any setter argument + if ( + (def.node.parent.type === "Property" || + def.node.parent.type === + "MethodDefinition") && + def.node.parent.kind === "set" + ) { + continue; + } + + // if "args" option is "none", skip any parameter + if (config.args === "none") { + continue; + } + + // skip ignored parameters + if ( + config.argsIgnorePattern && + config.argsIgnorePattern.test(def.name.name) + ) { + if ( + config.reportUsedIgnorePattern && + isUsedVariable(variable) + ) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData( + variable, + "parameter", + ), + }); + } + + continue; + } + + // if "args" option is "after-used", skip used variables + if ( + config.args === "after-used" && + astUtils.isFunction(def.name.parent) && + !isAfterLastUsedArg(variable) + ) { + continue; + } + } else { + // skip ignored variables + if ( + config.varsIgnorePattern && + config.varsIgnorePattern.test(def.name.name) + ) { + if ( + config.reportUsedIgnorePattern && + isUsedVariable(variable) + ) { + context.report({ + node: def.name, + messageId: "usedIgnoredVar", + data: getUsedIgnoredMessageData( + variable, + "variable", + ), + }); + } + + continue; + } + } + } + + if ( + !isUsedVariable(variable) && + !isExported(variable) && + !hasRestSpreadSibling(variable) + ) { + unusedVars.push(variable); + } + } + } + + for (i = 0, l = childScopes.length; i < l; ++i) { + collectUnusedVariables(childScopes[i], unusedVars); + } + + return unusedVars; + } + + /** + * fixes unused variables + * @param {Object} fixer fixer object + * @param {Object} unusedVar unused variable to fix + * @returns {Object} fixer object + */ + function handleFixes(fixer, unusedVar) { + const id = unusedVar.identifiers[0]; + const parent = id.parent; + const parentType = parent.type; + const tokenBefore = sourceCode.getTokenBefore(id); + const tokenAfter = sourceCode.getTokenAfter(id); + const isFunction = astUtils.isFunction; + const isLoop = astUtils.isLoop; + const allWriteReferences = unusedVar.references.filter(ref => + ref.isWrite(), + ); + + /** + * get range from token before of a given node + * @param {ASTNode} node node of identifier + * @param {number} skips number of token to skip + * @returns {number} start range of token before the identifier + */ + function getPreviousTokenStart(node, skips) { + return sourceCode.getTokenBefore(node, skips).range[0]; + } + + /** + * get range to token after of a given node + * @param {ASTNode} node node of identifier + * @param {number} skips number of token to skip + * @returns {number} end range of token after the identifier + */ + function getNextTokenEnd(node, skips) { + return sourceCode.getTokenAfter(node, skips).range[1]; + } + + /** + * get the value of token before of a given node + * @param {ASTNode} node node of identifier + * @returns {string} value of token before the identifier + */ + function getTokenBeforeValue(node) { + return sourceCode.getTokenBefore(node).value; + } + + /** + * get the value of token after of a given node + * @param {ASTNode} node node of identifier + * @returns {string} value of token after the identifier + */ + function getTokenAfterValue(node) { + return sourceCode.getTokenAfter(node).value; + } + + /** + * Check if an array has a single element with null as other element. + * @param {ASTNode} node ArrayPattern node + * @returns {boolean} true if array has single element with other null elements + */ + function hasSingleElement(node) { + return node.elements.filter(e => e !== null).length === 1; + } + + /** + * check whether import specifier has an import of particular type + * @param {ASTNode} node ImportDeclaration node + * @param {string} type type of import to check + * @returns {boolean} true if import specifier has import of specified type + */ + function hasImportOfCertainType(node, type) { + return node.specifiers.some(e => e.type === type); + } + + /** + * Check whether declaration is safe to remove or not + * @param {ASTNode} nextToken next token of unused variable + * @param {ASTNode} prevToken previous token of unused variable + * @returns {boolean} true if declaration is not safe to remove + */ + function isDeclarationNotSafeToRemove(nextToken, prevToken) { + return ( + nextToken.type === "String" || + (prevToken && + !astUtils.isSemicolonToken(prevToken) && + !astUtils.isOpeningBraceToken(prevToken)) + ); + } + + /** + * give fixes for unused variables in function parameters + * @param {ASTNode} node node to check + * @returns {Object} fixer object + */ + function fixFunctionParameters(node) { + const parentNode = node.parent; + + if (isFunction(parentNode)) { + // remove unused function parameter if there is only a single parameter + if (parentNode.params.length === 1) { + return fixer.removeRange(node.range); + } + + // remove first unused function parameter when there are multiple parameters + if ( + getTokenBeforeValue(node) === "(" && + getTokenAfterValue(node) === "," + ) { + return fixer.removeRange([ + node.range[0], + getNextTokenEnd(node), + ]); + } + + // remove unused function parameters except first one when there are multiple parameters + return fixer.removeRange([ + getPreviousTokenStart(node), + node.range[1], + ]); + } + + return null; + } + + /** + * fix unused variable declarations and function parameters + * @param {ASTNode} node parent node to identifier + * @returns {Object} fixer object + */ + function fixVariables(node) { + const parentNode = node.parent; + + // remove unused declared variables such as var a = b; or var a = b, c; + if (parentNode.type === "VariableDeclarator") { + // skip variable in for (const [ foo ] of bar); + if (isLoop(parentNode.parent.parent)) { + return null; + } + + /* + * remove unused declared variable with single declaration such as 'var a = b;' + * remove complete declaration when there is an unused variable in 'const { a } = foo;', same for arrays. + */ + if (parentNode.parent.declarations.length === 1) { + // if next token is a string it could become a directive if node is removed -> no suggestion. + const nextToken = sourceCode.getTokenAfter( + parentNode.parent, + ); + + // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion. + const prevToken = sourceCode.getTokenBefore( + parentNode.parent, + ); + + if ( + nextToken && + isDeclarationNotSafeToRemove(nextToken, prevToken) + ) { + return null; + } + + return fixer.removeRange(parentNode.parent.range); + } + + /* + * remove unused declared variable with multiple declaration except first one such as 'var a = b, c = d;' + * fix 'let bar = "hello", { a } = foo;' to 'let bar = "hello";' if 'a' is unused, same for arrays. + */ + if (getTokenBeforeValue(parentNode) === ",") { + return fixer.removeRange([ + getPreviousTokenStart(parentNode), + parentNode.range[1], + ]); + } + + /* + * remove first unused declared variable when there are multiple declarations + * fix 'let { a } = foo, bar = "hello";' to 'let bar = "hello";' if 'a' is unused, same for arrays. + */ + return fixer.removeRange([ + parentNode.range[0], + getNextTokenEnd(parentNode), + ]); + } + + // fixes [{a: {k}}], [{a: [k]}] + if (getTokenBeforeValue(node) === ":") { + if (parentNode.parent.type === "ObjectPattern") { + // eslint-disable-next-line no-use-before-define -- due to interdependency of functions + return fixObjectWithValueSeparator(node); + } + } + + // fix unused function parameters + return fixFunctionParameters(node); + } + + /** + * fix nested object like { a: { b } } + * @param {ASTNode} node parent node to check + * @returns {Object} fixer object + */ + function fixNestedObjectVariable(node) { + const parentNode = node.parent; + + // fix for { a: { b: { c: { d } } } } + if ( + parentNode.parent.parent.parent.type === "ObjectPattern" && + parentNode.parent.properties.length === 1 + ) { + return fixNestedObjectVariable(parentNode.parent); + } + + // fix for { a: { b } } + if (parentNode.parent.type === "ObjectPattern") { + // fix for unused variables in dectructured object with single property in variable decalartion and function parameter + if (parentNode.parent.properties.length === 1) { + return fixVariables(parentNode.parent); + } + + // fix for first unused property when there are multiple properties such as '{ a: { b }, c }' + if (getTokenBeforeValue(parentNode) === "{") { + return fixer.removeRange([ + parentNode.range[0], + getNextTokenEnd(parentNode), + ]); + } + + // fix for unused property except first one when there are multiple properties such as '{ k, a: { b } }' + return fixer.removeRange([ + getPreviousTokenStart(parentNode), + parentNode.range[1], + ]); + } + + return null; + } + + /** + * fix unused variables in array and nested array + * @param {ASTNode} node parent node to check + * @returns {Object} fixer object + */ + function fixNestedArrayVariable(node) { + const parentNode = node.parent; + + // fix for nested arrays [[ a ]] + if ( + parentNode.parent.type === "ArrayPattern" && + hasSingleElement(parentNode) + ) { + return fixNestedArrayVariable(parentNode); + } + + if (hasSingleElement(parentNode)) { + // fixes { a: [{ b }] } or { a: [[ b ]] } + if (getTokenBeforeValue(parentNode) === ":") { + return fixVariables(parentNode); + } + + // fixes [a, ...[[ b ]]] or [a, ...[{ b }]] + if (parentNode.parent.type === "RestElement") { + // eslint-disable-next-line no-use-before-define -- due to interdependency of functions + return fixRestInPattern(parentNode.parent); + } + + // fix unused variables in destructured array in variable declaration or function parameter + return fixVariables(parentNode); + } + + // remove last unused array element + if ( + getTokenBeforeValue(node) === "," && + getTokenAfterValue(node) === "]" + ) { + return fixer.removeRange([ + getPreviousTokenStart(node), + node.range[1], + ]); + } + + // remove unused array element + return fixer.removeRange(node.range); + } + + /** + * fix cases like {a: {k}} or {a: [k]} + * @param {ASTNode} node parent node to check + * @returns {Object} fixer object + */ + function fixObjectWithValueSeparator(node) { + const parentNode = node.parent.parent; + + // fix cases like [{a : { b }}] or [{a : [ b ]}] + if ( + parentNode.parent.type === "ArrayPattern" && + parentNode.properties.length === 1 + ) { + return fixNestedArrayVariable(parentNode); + } + + // fix cases like {a: {k}} or {a: [k]} + return fixNestedObjectVariable(node); + } + + /** + * fix ...[[a]] or ...[{a}] like patterns + * @param {ASTNode} node parent node to check + * @returns {Object} fixer object + */ + function fixRestInPattern(node) { + const parentNode = node.parent; + + // fix ...[[a]] or ...[{a}] in function parameters + if (isFunction(parentNode)) { + if (parentNode.params.length === 1) { + return fixer.removeRange(node.range); + } + + return fixer.removeRange([ + getPreviousTokenStart(node), + node.range[1], + ]); + } + + // fix rest in nested array pattern like [[a, ...[b]]] + if (parentNode.type === "ArrayPattern") { + // fix [[...[b]]] + if (hasSingleElement(parentNode)) { + if (parentNode.parent.type === "ArrayPattern") { + return fixNestedArrayVariable(parentNode); + } + + // fix 'const [...[b]] = foo; and function foo([...[b]]) {} + return fixVariables(parentNode); + } + + // fix [[a, ...[b]]] + return fixer.removeRange([ + getPreviousTokenStart(node), + node.range[1], + ]); + } + + return null; + } + + // skip fix when variable has references that would be left behind + if ( + allWriteReferences.some( + ref => ref.identifier.range[0] !== id.range[0], + ) + ) { + return null; + } + + // remove declared variables such as var a; or var a, b; + if (parentType === "VariableDeclarator") { + if (parent.parent.declarations.length === 1) { + // prevent fix of variable in forOf and forIn loops. + if ( + isLoop(parent.parent.parent) && + parent.parent.parent.body !== parent.parent + ) { + return null; + } + + // removes only variable not semicolon in 'if (foo()) var bar;' or in 'loops' or in 'with' statement. + if ( + parent.parent.parent.type === "IfStatement" || + isLoop(parent.parent.parent) || + (parent.parent.parent.type === "WithStatement" && + parent.parent.parent.body === parent.parent) + ) { + return fixer.replaceText(parent.parent, ";"); + } + + // if next token is a string it could become a directive if node is removed -> no suggestion. + const nextToken = sourceCode.getTokenAfter(parent.parent); + + // if previous token exists and is not ";" or "{" not sure about ASI rules -> no suggestion. + const prevToken = sourceCode.getTokenBefore(parent.parent); + + if ( + nextToken && + isDeclarationNotSafeToRemove(nextToken, prevToken) + ) { + return null; + } + + // remove unused declared variable with single declaration like 'var a = b;' + return fixer.removeRange(parent.parent.range); + } + + // remove unused declared variable with multiple declaration except first one like 'var a = b, c = d;' + if (tokenBefore.value === ",") { + return fixer.removeRange([ + tokenBefore.range[0], + parent.range[1], + ]); + } + + // remove first unused declared variable when there are multiple declarations + return fixer.removeRange([ + parent.range[0], + getNextTokenEnd(parent), + ]); + } + + // remove variables in object patterns + if (parent.parent.type === "ObjectPattern") { + if (parent.parent.properties.length === 1) { + // fix [a, ...{b}] + if (parent.parent.parent.type === "RestElement") { + return fixRestInPattern(parent.parent.parent); + } + + // fix [{ a }] + if (parent.parent.parent.type === "ArrayPattern") { + return fixNestedArrayVariable(parent.parent); + } + + /* + * var {a} = foo; + * function a({a}) {} + * fix const { a: { b } } = foo; + */ + return fixVariables(parent.parent); + } + + // fix const { a:b } = foo; + if (tokenBefore.value === ":") { + // remove first unused variable in const { a:b } = foo; + if ( + getTokenBeforeValue(parent) === "{" && + getTokenAfterValue(parent) === "," + ) { + return fixer.removeRange([ + parent.range[0], + getNextTokenEnd(parent), + ]); + } + + // remove unused variables in const { a: b, c: d } = foo; except first one + return fixer.removeRange([ + getPreviousTokenStart(parent), + id.range[1], + ]); + } + } + + // remove unused variables inside an array + if (parentType === "ArrayPattern") { + if (hasSingleElement(parent)) { + // fix [a, ...[b]] + if (parent.parent.type === "RestElement") { + return fixRestInPattern(parent.parent); + } + + // fix [ [a] ] + if (parent.parent.type === "ArrayPattern") { + return fixNestedArrayVariable(parent); + } + + /* + * fix var [a] = foo; + * fix function foo([a]) {} + * fix const { a: [b] } = foo; + */ + return fixVariables(parent); + } + + // if "a" is unused in [a, b ,c] fixes to [, b, c] + if (tokenBefore.value === "," && tokenAfter.value === ",") { + return fixer.removeRange(id.range); + } + } + + // remove unused rest elements + if (parentType === "RestElement") { + // fix [a, ...rest] + if (parent.parent.type === "ArrayPattern") { + if (hasSingleElement(parent.parent)) { + // fix [[...rest]] when there is only rest element + if (parent.parent.parent.type === "ArrayPattern") { + return fixNestedArrayVariable(parent.parent); + } + + // fix 'const [...rest] = foo;' and 'function foo([...rest]) {}' + return fixVariables(parent.parent); + } + + // fix [a, ...rest] + return fixer.removeRange([ + getPreviousTokenStart(id, 1), + id.range[1], + ]); + } + + // fix { a, ...rest} + if (parent.parent.type === "ObjectPattern") { + // fix 'const {...rest} = foo;' and 'function foo({...rest}) {}' + if (parent.parent.properties.length === 1) { + return fixVariables(parent.parent); + } + + // fix { a, ...rest} when there are multiple properties + return fixer.removeRange([ + getPreviousTokenStart(id, 1), + id.range[1], + ]); + } + + // fix function foo(...rest) {} + if (isFunction(parent.parent)) { + // remove unused rest in function parameter if there is only single parameter + if (parent.parent.params.length === 1) { + return fixer.removeRange(parent.range); + } + + // remove unused rest in function parameter if there multiple parameter + return fixer.removeRange([ + getPreviousTokenStart(parent), + parent.range[1], + ]); + } + } + + if (parentType === "AssignmentPattern") { + // fix [a = aDefault] + if (parent.parent.type === "ArrayPattern") { + return fixNestedArrayVariable(parent); + } + + // fix {a = aDefault} + if (parent.parent.parent.type === "ObjectPattern") { + if (parent.parent.parent.properties.length === 1) { + // fixes [{a = aDefault}] + if ( + parent.parent.parent.parent.type === "ArrayPattern" + ) { + return fixNestedArrayVariable(parent.parent.parent); + } + + // fix 'const {a = aDefault} = foo;' and 'function foo({a = aDefault}) {}' + return fixVariables(parent.parent.parent); + } + + // fix unused 'a' in {a = aDefault} if it is the first property + if ( + getTokenBeforeValue(parent.parent) === "{" && + getTokenAfterValue(parent.parent) === "," + ) { + return fixer.removeRange([ + parent.parent.range[0], + getNextTokenEnd(parent.parent), + ]); + } + + // fix unused 'b' in {a, b = aDefault} if it is not the first property + return fixer.removeRange([ + getPreviousTokenStart(parent.parent), + parent.parent.range[1], + ]); + } + + // fix unused assignment patterns in function parameters + if (isFunction(parent.parent)) { + return fixFunctionParameters(parent); + } + } + + // remove unused functions + if (parentType === "FunctionDeclaration" && parent.id === id) { + return fixer.removeRange(parent.range); + } + + // remove unused default import + if (parentType === "ImportDefaultSpecifier") { + // remove unused default import when there are not other imports + if ( + !hasImportOfCertainType(parent.parent, "ImportSpecifier") && + !hasImportOfCertainType( + parent.parent, + "ImportNamespaceSpecifier", + ) + ) { + return fixer.removeRange([ + parent.range[0], + parent.parent.source.range[0], + ]); + } + + // remove unused default import when there are other imports also + return fixer.removeRange([id.range[0], tokenAfter.range[1]]); + } + + if (parentType === "ImportSpecifier") { + // remove unused imports when there is a single import + if ( + parent.parent.specifiers.filter( + e => e.type === "ImportSpecifier", + ).length === 1 + ) { + // remove unused import when there is no default import + if ( + !hasImportOfCertainType( + parent.parent, + "ImportDefaultSpecifier", + ) + ) { + return fixer.removeRange(parent.parent.range); + } + + // fixes "import foo from 'module';" to "import 'module';" + return fixer.removeRange([ + getPreviousTokenStart(parent, 1), + tokenAfter.range[1], + ]); + } + + if (getTokenBeforeValue(parent) === "{") { + return fixer.removeRange([ + parent.range[0], + getNextTokenEnd(parent), + ]); + } + + return fixer.removeRange([ + getPreviousTokenStart(parent), + parent.range[1], + ]); + } + + if (parentType === "ImportNamespaceSpecifier") { + if ( + hasImportOfCertainType( + parent.parent, + "ImportDefaultSpecifier", + ) + ) { + return fixer.removeRange([ + getPreviousTokenStart(parent), + parent.range[1], + ]); + } + + // fixes "import * as foo from 'module';" to "import 'module';" + return fixer.removeRange([ + parent.range[0], + parent.parent.source.range[0], + ]); + } + + // skip error in catch(error) variable + if (parentType === "CatchClause") { + return null; + } + + // remove unused declared classes + if (parentType === "ClassDeclaration") { + return fixer.removeRange(parent.range); + } + + // remove unused varible that is in a sequence [a,b] fixes to [a] + if (tokenBefore?.value === ",") { + return fixer.removeRange([tokenBefore.range[0], id.range[1]]); + } + + // remove unused varible that is in a sequence inside function arguments and object pattern + if (tokenAfter.value === ",") { + // fix function foo(a, b) {} + if (tokenBefore.value === "(") { + return fixer.removeRange([ + id.range[0], + tokenAfter.range[1], + ]); + } + + // fix const {a, b} = foo; + if (tokenBefore.value === "{") { + return fixer.removeRange([ + id.range[0], + tokenAfter.range[1], + ]); + } + } + + if ( + parentType === "ArrowFunctionExpression" && + parent.params.length === 1 && + tokenAfter?.value !== ")" + ) { + return fixer.replaceText(id, "()"); + } + + return fixer.removeRange(id.range); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + "Program:exit"(programNode) { + const unusedVars = collectUnusedVariables( + sourceCode.getScope(programNode), + [], + ); + + for (let i = 0, l = unusedVars.length; i < l; ++i) { + const unusedVar = unusedVars[i]; + + // Report the first declaration. + if (unusedVar.defs.length > 0) { + // report last write reference, https://github.com/eslint/eslint/issues/14324 + const writeReferences = unusedVar.references.filter( + ref => + ref.isWrite() && + ref.from.variableScope === + unusedVar.scope.variableScope, + ); + + let referenceToReport; + + if (writeReferences.length > 0) { + referenceToReport = writeReferences.at(-1); + } + + context.report({ + node: referenceToReport + ? referenceToReport.identifier + : unusedVar.identifiers[0], + messageId: "unusedVar", + data: unusedVar.references.some(ref => + ref.isWrite(), + ) + ? getAssignedMessageData(unusedVar) + : getDefinedMessageData(unusedVar), + suggest: [ + { + messageId: "removeVar", + data: { + varName: unusedVar.name, + }, + fix(fixer) { + return handleFixes(fixer, unusedVar); + }, + }, + ], + }); + + // If there are no regular declaration, report the first `/*globals*/` comment directive. + } else if (unusedVar.eslintExplicitGlobalComments) { + const directiveComment = + unusedVar.eslintExplicitGlobalComments[0]; + + context.report({ + node: programNode, + loc: astUtils.getNameLocationInGlobalDirectiveComment( + sourceCode, + directiveComment, + unusedVar.name, + ), + messageId: "unusedVar", + data: getDefinedMessageData(unusedVar), + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-use-before-define.js b/lib/rules/no-use-before-define.js index 59510d8ffef8..9c5669e8545f 100644 --- a/lib/rules/no-use-before-define.js +++ b/lib/rules/no-use-before-define.js @@ -9,7 +9,8 @@ // Helpers //------------------------------------------------------------------------------ -const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; +const SENTINEL_TYPE = + /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u; const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; /** @@ -18,16 +19,18 @@ const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u; * @returns {Object} The parsed options. */ function parseOptions(options) { - if (typeof options === "object" && options !== null) { - return options; - } - - const functions = - typeof options === "string" - ? options !== "nofunc" - : true; - - return { functions, classes: true, variables: true, allowNamedExports: false }; + if (typeof options === "object" && options !== null) { + return options; + } + + const functions = typeof options === "string" ? options !== "nofunc" : true; + + return { + functions, + classes: true, + variables: true, + allowNamedExports: false, + }; } /** @@ -37,7 +40,7 @@ function parseOptions(options) { * @returns {boolean} `true` if the location is inside of the range of the node. */ function isInRange(node, location) { - return node && node.range[0] <= location && location <= node.range[1]; + return node && node.range[0] <= location && location <= node.range[1]; } /** @@ -48,18 +51,15 @@ function isInRange(node, location) { * @returns {boolean} `true` if the location is inside of a class static initializer. */ function isInClassStaticInitializerRange(node, location) { - return node.body.some(classMember => ( - ( - classMember.type === "StaticBlock" && - isInRange(classMember, location) - ) || - ( - classMember.type === "PropertyDefinition" && - classMember.static && - classMember.value && - isInRange(classMember.value, location) - ) - )); + return node.body.some( + classMember => + (classMember.type === "StaticBlock" && + isInRange(classMember, location)) || + (classMember.type === "PropertyDefinition" && + classMember.static && + classMember.value && + isInRange(classMember.value, location)), + ); } /** @@ -69,19 +69,18 @@ function isInClassStaticInitializerRange(node, location) { * @returns {boolean} `true` if the scope is a class static initializer scope. */ function isClassStaticInitializerScope(scope) { - if (scope.type === "class-static-block") { - return true; - } - - if (scope.type === "class-field-initializer") { + if (scope.type === "class-static-block") { + return true; + } - // `scope.block` is PropertyDefinition#value node - const propertyDefinition = scope.block.parent; + if (scope.type === "class-field-initializer") { + // `scope.block` is PropertyDefinition#value node + const propertyDefinition = scope.block.parent; - return propertyDefinition.static; - } + return propertyDefinition.static; + } - return false; + return false; } /** @@ -121,19 +120,19 @@ function isClassStaticInitializerScope(scope) { * @returns {boolean} `true` if the reference is from a separate execution context. */ function isFromSeparateExecutionContext(reference) { - const variable = reference.resolved; - let scope = reference.from; - - // Scope#variableScope represents execution context - while (variable.scope.variableScope !== scope.variableScope) { - if (isClassStaticInitializerScope(scope.variableScope)) { - scope = scope.variableScope.upper; - } else { - return true; - } - } - - return false; + const variable = reference.resolved; + let scope = reference.from; + + // Scope#variableScope represents execution context + while (variable.scope.variableScope !== scope.variableScope) { + if (isClassStaticInitializerScope(scope.variableScope)) { + scope = scope.variableScope.upper; + } else { + return true; + } + } + + return false; } /** @@ -156,59 +155,57 @@ function isFromSeparateExecutionContext(reference) { * @returns {boolean} `true` if the reference is evaluated during the initialization. */ function isEvaluatedDuringInitialization(reference) { - if (isFromSeparateExecutionContext(reference)) { - - /* - * Even if the reference appears in the initializer, it isn't evaluated during the initialization. - * For example, `const x = () => x;` is valid. - */ - return false; - } - - const location = reference.identifier.range[1]; - const definition = reference.resolved.defs[0]; - - if (definition.type === "ClassName") { - - // `ClassDeclaration` or `ClassExpression` - const classDefinition = definition.node; - - return ( - isInRange(classDefinition, location) && - - /* - * Class binding is initialized before running static initializers. - * For example, `class C { static foo = C; static { bar = C; } }` is valid. - */ - !isInClassStaticInitializerRange(classDefinition.body, location) - ); - } - - let node = definition.name.parent; - - while (node) { - if (node.type === "VariableDeclarator") { - if (isInRange(node.init, location)) { - return true; - } - if (FOR_IN_OF_TYPE.test(node.parent.parent.type) && - isInRange(node.parent.parent.right, location) - ) { - return true; - } - break; - } else if (node.type === "AssignmentPattern") { - if (isInRange(node.right, location)) { - return true; - } - } else if (SENTINEL_TYPE.test(node.type)) { - break; - } - - node = node.parent; - } - - return false; + if (isFromSeparateExecutionContext(reference)) { + /* + * Even if the reference appears in the initializer, it isn't evaluated during the initialization. + * For example, `const x = () => x;` is valid. + */ + return false; + } + + const location = reference.identifier.range[1]; + const definition = reference.resolved.defs[0]; + + if (definition.type === "ClassName") { + // `ClassDeclaration` or `ClassExpression` + const classDefinition = definition.node; + + return ( + isInRange(classDefinition, location) && + /* + * Class binding is initialized before running static initializers. + * For example, `class C { static foo = C; static { bar = C; } }` is valid. + */ + !isInClassStaticInitializerRange(classDefinition.body, location) + ); + } + + let node = definition.name.parent; + + while (node) { + if (node.type === "VariableDeclarator") { + if (isInRange(node.init, location)) { + return true; + } + if ( + FOR_IN_OF_TYPE.test(node.parent.parent.type) && + isInRange(node.parent.parent.right, location) + ) { + return true; + } + break; + } else if (node.type === "AssignmentPattern") { + if (isInRange(node.right, location)) { + return true; + } + } else if (SENTINEL_TYPE.test(node.type)) { + break; + } + + node = node.parent; + } + + return false; } //------------------------------------------------------------------------------ @@ -217,134 +214,135 @@ function isEvaluatedDuringInitialization(reference) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - oneOf: [ - { - enum: ["nofunc"] - }, - { - type: "object", - properties: { - functions: { type: "boolean" }, - classes: { type: "boolean" }, - variables: { type: "boolean" }, - allowNamedExports: { type: "boolean" } - }, - additionalProperties: false - } - ] - } - ], - - defaultOptions: [{ - classes: true, - functions: true, - variables: true, - allowNamedExports: false - }], - - messages: { - usedBeforeDefined: "'{{name}}' was used before it was defined." - } - }, - - create(context) { - const options = parseOptions(context.options[0]); - const sourceCode = context.sourceCode; - - /** - * Determines whether a given reference should be checked. - * - * Returns `false` if the reference is: - * - initialization's (e.g., `let a = 1`). - * - referring to an undefined variable (i.e., if it's an unresolved reference). - * - referring to a variable that is defined, but not in the given source code - * (e.g., global environment variable or `arguments` in functions). - * - allowed by options. - * @param {eslint-scope.Reference} reference The reference - * @returns {boolean} `true` if the reference should be checked - */ - function shouldCheck(reference) { - if (reference.init) { - return false; - } - - const { identifier } = reference; - - if ( - options.allowNamedExports && - identifier.parent.type === "ExportSpecifier" && - identifier.parent.local === identifier - ) { - return false; - } - - const variable = reference.resolved; - - if (!variable || variable.defs.length === 0) { - return false; - } - - const definitionType = variable.defs[0].type; - - if (!options.functions && definitionType === "FunctionName") { - return false; - } - - if ( - ( - !options.variables && definitionType === "Variable" || - !options.classes && definitionType === "ClassName" - ) && - - // don't skip checking the reference if it's in the same execution context, because of TDZ - isFromSeparateExecutionContext(reference) - ) { - return false; - } - - return true; - } - - /** - * Finds and validates all references in a given scope and its child scopes. - * @param {eslint-scope.Scope} scope The scope object. - * @returns {void} - */ - function checkReferencesInScope(scope) { - scope.references.filter(shouldCheck).forEach(reference => { - const variable = reference.resolved; - const definitionIdentifier = variable.defs[0].name; - - if ( - reference.identifier.range[1] < definitionIdentifier.range[1] || - isEvaluatedDuringInitialization(reference) - ) { - context.report({ - node: reference.identifier, - messageId: "usedBeforeDefined", - data: reference.identifier - }); - } - }); - - scope.childScopes.forEach(checkReferencesInScope); - } - - return { - Program(node) { - checkReferencesInScope(sourceCode.getScope(node)); - } - }; - } + meta: { + 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", + }, + + schema: [ + { + oneOf: [ + { + enum: ["nofunc"], + }, + { + type: "object", + properties: { + functions: { type: "boolean" }, + classes: { type: "boolean" }, + variables: { type: "boolean" }, + allowNamedExports: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, + ], + + defaultOptions: [ + { + classes: true, + functions: true, + variables: true, + allowNamedExports: false, + }, + ], + + messages: { + usedBeforeDefined: "'{{name}}' was used before it was defined.", + }, + }, + + create(context) { + const options = parseOptions(context.options[0]); + const sourceCode = context.sourceCode; + + /** + * Determines whether a given reference should be checked. + * + * Returns `false` if the reference is: + * - initialization's (e.g., `let a = 1`). + * - referring to an undefined variable (i.e., if it's an unresolved reference). + * - referring to a variable that is defined, but not in the given source code + * (e.g., global environment variable or `arguments` in functions). + * - allowed by options. + * @param {eslint-scope.Reference} reference The reference + * @returns {boolean} `true` if the reference should be checked + */ + function shouldCheck(reference) { + if (reference.init) { + return false; + } + + const { identifier } = reference; + + if ( + options.allowNamedExports && + identifier.parent.type === "ExportSpecifier" && + identifier.parent.local === identifier + ) { + return false; + } + + const variable = reference.resolved; + + if (!variable || variable.defs.length === 0) { + return false; + } + + const definitionType = variable.defs[0].type; + + if (!options.functions && definitionType === "FunctionName") { + return false; + } + + if ( + ((!options.variables && definitionType === "Variable") || + (!options.classes && definitionType === "ClassName")) && + // don't skip checking the reference if it's in the same execution context, because of TDZ + isFromSeparateExecutionContext(reference) + ) { + return false; + } + + return true; + } + + /** + * Finds and validates all references in a given scope and its child scopes. + * @param {eslint-scope.Scope} scope The scope object. + * @returns {void} + */ + function checkReferencesInScope(scope) { + scope.references.filter(shouldCheck).forEach(reference => { + const variable = reference.resolved; + const definitionIdentifier = variable.defs[0].name; + + if ( + reference.identifier.range[1] < + definitionIdentifier.range[1] || + isEvaluatedDuringInitialization(reference) + ) { + context.report({ + node: reference.identifier, + messageId: "usedBeforeDefined", + data: reference.identifier, + }); + } + }); + + scope.childScopes.forEach(checkReferencesInScope); + } + + return { + Program(node) { + checkReferencesInScope(sourceCode.getScope(node)); + }, + }; + }, }; diff --git a/lib/rules/no-useless-assignment.js b/lib/rules/no-useless-assignment.js index 90cc1c51d452..fa9cf5eeaaec 100644 --- a/lib/rules/no-useless-assignment.js +++ b/lib/rules/no-useless-assignment.js @@ -32,35 +32,36 @@ const { findVariable } = require("@eslint-community/eslint-utils"); * @param {Pattern} pattern The pattern node to extract identifier * @returns {Iterable} The extracted identifier */ -function *extractIdentifiersFromPattern(pattern) { - switch (pattern.type) { - case "Identifier": - yield pattern; - return; - case "ObjectPattern": - for (const property of pattern.properties) { - yield* extractIdentifiersFromPattern(property.type === "Property" ? property.value : property); - } - return; - case "ArrayPattern": - for (const element of pattern.elements) { - if (!element) { - continue; - } - yield* extractIdentifiersFromPattern(element); - } - return; - case "RestElement": - yield* extractIdentifiersFromPattern(pattern.argument); - return; - case "AssignmentPattern": - yield* extractIdentifiersFromPattern(pattern.left); - - // no default - } +function* extractIdentifiersFromPattern(pattern) { + switch (pattern.type) { + case "Identifier": + yield pattern; + return; + case "ObjectPattern": + for (const property of pattern.properties) { + yield* extractIdentifiersFromPattern( + property.type === "Property" ? property.value : property, + ); + } + return; + case "ArrayPattern": + for (const element of pattern.elements) { + if (!element) { + continue; + } + yield* extractIdentifiersFromPattern(element); + } + return; + case "RestElement": + yield* extractIdentifiersFromPattern(pattern.argument); + return; + case "AssignmentPattern": + yield* extractIdentifiersFromPattern(pattern.left); + + // no default + } } - /** * Checks whether the given identifier node is evaluated after the assignment identifier. * @param {AssignmentInfo} assignment The assignment info. @@ -68,34 +69,33 @@ function *extractIdentifiersFromPattern(pattern) { * @returns {boolean} `true` if the given identifier node is evaluated after the assignment identifier. */ function isIdentifierEvaluatedAfterAssignment(assignment, identifier) { - if (identifier.range[0] < assignment.identifier.range[1]) { - return false; - } - if ( - assignment.expression && - assignment.expression.range[0] <= identifier.range[0] && - identifier.range[1] <= assignment.expression.range[1] - ) { - - /* - * The identifier node is in an expression that is evaluated before the assignment. - * e.g. x = id; - * ^^ identifier to check - * ^ assignment identifier - */ - return false; - } - - /* - * e.g. - * x = 42; id; - * ^^ identifier to check - * ^ assignment identifier - * let { x, y = id } = obj; - * ^^ identifier to check - * ^ assignment identifier - */ - return true; + if (identifier.range[0] < assignment.identifier.range[1]) { + return false; + } + if ( + assignment.expression && + assignment.expression.range[0] <= identifier.range[0] && + identifier.range[1] <= assignment.expression.range[1] + ) { + /* + * The identifier node is in an expression that is evaluated before the assignment. + * e.g. x = id; + * ^^ identifier to check + * ^ assignment identifier + */ + return false; + } + + /* + * e.g. + * x = 42; id; + * ^^ identifier to check + * ^ assignment identifier + * let { x, y = id } = obj; + * ^^ identifier to check + * ^ assignment identifier + */ + return true; } /** @@ -109,13 +109,13 @@ function isIdentifierEvaluatedAfterAssignment(assignment, identifier) { * @returns {boolean} `true` if the given identifier node is used between the assigned identifier and the equal sign. */ function isIdentifierUsedBetweenAssignedAndEqualSign(assignment, identifier) { - if (!assignment.expression) { - return false; - } - return ( - assignment.identifier.range[1] <= identifier.range[0] && - identifier.range[1] <= assignment.expression.range[0] - ); + if (!assignment.expression) { + return false; + } + return ( + assignment.identifier.range[1] <= identifier.range[0] && + identifier.range[1] <= assignment.expression.range[0] + ); } //------------------------------------------------------------------------------ @@ -124,452 +124,531 @@ function isIdentifierUsedBetweenAssignedAndEqualSign(assignment, identifier) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [], - - messages: { - unnecessaryAssignment: "This assigned value is not used in subsequent statements." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * @typedef {Object} ScopeStack - * @property {CodePath} codePath The code path of this scope stack. - * @property {Scope} scope The scope of this scope stack. - * @property {ScopeStack} upper The upper scope stack. - * @property {Record} segments The map of ScopeStackSegmentInfo. - * @property {Set} currentSegments The current CodePathSegments. - * @property {Map} assignments The map of list of AssignmentInfo for each variable. - */ - /** - * @typedef {Object} ScopeStackSegmentInfo - * @property {CodePathSegment} segment The code path segment. - * @property {Identifier|null} first The first identifier that appears within the segment. - * @property {Identifier|null} last The last identifier that appears within the segment. - * `first` and `last` are used to determine whether an identifier exists within the segment position range. - * Since it is used as a range of segments, we should originally hold all nodes, not just identifiers, - * but since the only nodes to be judged are identifiers, it is sufficient to have a range of identifiers. - */ - /** - * @typedef {Object} AssignmentInfo - * @property {Variable} variable The variable that is assigned. - * @property {Identifier} identifier The identifier that is assigned. - * @property {VariableDeclarator|AssignmentExpression|UpdateExpression} node The node where the variable was updated. - * @property {Expression|null} expression The expression that is evaluated before the assignment. - * @property {CodePathSegment[]} segments The code path segments where the assignment was made. - */ - - /** @type {ScopeStack} */ - let scopeStack = null; - - /** @type {Set} */ - const codePathStartScopes = new Set(); - - /** - * Gets the scope of code path start from given scope - * @param {Scope} scope The initial scope - * @returns {Scope} The scope of code path start - * @throws {Error} Unexpected error - */ - function getCodePathStartScope(scope) { - let target = scope; - - while (target) { - if (codePathStartScopes.has(target)) { - return target; - } - target = target.upper; - } - - // Should be unreachable - return null; - } - - /** - * Verify the given scope stack. - * @param {ScopeStack} target The scope stack to verify. - * @returns {void} - */ - function verify(target) { - - /** - * Checks whether the given identifier is used in the segment. - * @param {CodePathSegment} segment The code path segment. - * @param {Identifier} identifier The identifier to check. - * @returns {boolean} `true` if the identifier is used in the segment. - */ - function isIdentifierUsedInSegment(segment, identifier) { - const segmentInfo = target.segments[segment.id]; - - return ( - segmentInfo.first && - segmentInfo.last && - segmentInfo.first.range[0] <= identifier.range[0] && - identifier.range[1] <= segmentInfo.last.range[1] - ); - } - - /** - * Verifies whether the given assignment info is an used assignment. - * Report if it is an unused assignment. - * @param {AssignmentInfo} targetAssignment The assignment info to verify. - * @param {AssignmentInfo[]} allAssignments The list of all assignment info for variables. - * @returns {void} - */ - function verifyAssignmentIsUsed(targetAssignment, allAssignments) { - - /** - * @typedef {Object} SubsequentSegmentData - * @property {CodePathSegment} segment The code path segment - * @property {AssignmentInfo} [assignment] The first occurrence of the assignment within the segment. - * There is no need to check if the variable is used after this assignment, - * as the value it was assigned will be used. - */ - - /** - * Information used in `getSubsequentSegments()`. - * To avoid unnecessary iterations, cache information that has already been iterated over, - * and if additional iterations are needed, start iterating from the retained position. - */ - const subsequentSegmentData = { - - /** - * Cache of subsequent segment information list that have already been iterated. - * @type {SubsequentSegmentData[]} - */ - results: [], - - /** - * Subsequent segments that have already been iterated on. Used to avoid infinite loops. - * @type {Set} - */ - subsequentSegments: new Set(), - - /** - * Unexplored code path segment. - * If additional iterations are needed, consume this information and iterate. - * @type {CodePathSegment[]} - */ - queueSegments: targetAssignment.segments.flatMap(segment => segment.nextSegments) - }; - - - /** - * Gets the subsequent segments from the segment of - * the assignment currently being validated (targetAssignment). - * @returns {Iterable} the subsequent segments - */ - function *getSubsequentSegments() { - yield* subsequentSegmentData.results; - - while (subsequentSegmentData.queueSegments.length > 0) { - const nextSegment = subsequentSegmentData.queueSegments.shift(); - - if (subsequentSegmentData.subsequentSegments.has(nextSegment)) { - continue; - } - subsequentSegmentData.subsequentSegments.add(nextSegment); - - const assignmentInSegment = allAssignments - .find(otherAssignment => ( - otherAssignment.segments.includes(nextSegment) && - !isIdentifierUsedBetweenAssignedAndEqualSign(otherAssignment, targetAssignment.identifier) - )); - - if (!assignmentInSegment) { - - /* - * Stores the next segment to explore. - * If `assignmentInSegment` exists, - * we are guarding it because we don't need to explore the next segment. - */ - subsequentSegmentData.queueSegments.push(...nextSegment.nextSegments); - } - - /** @type {SubsequentSegmentData} */ - const result = { - segment: nextSegment, - assignment: assignmentInSegment - }; - - subsequentSegmentData.results.push(result); - yield result; - } - } - - - if (targetAssignment.variable.references.some(ref => ref.identifier.type !== "Identifier")) { - - /** - * Skip checking for a variable that has at least one non-identifier reference. - * It's generated by plugins and cannot be handled reliably in the core rule. - */ - return; - } - - const readReferences = targetAssignment.variable.references.filter(reference => reference.isRead()); - - if (!readReferences.length) { - - /* - * It is not just an unnecessary assignment, but an unnecessary (unused) variable - * and thus should not be reported by this rule because it is reported by `no-unused-vars`. - */ - return; - } - - /** - * Other assignment on the current segment and after current assignment. - */ - const otherAssignmentAfterTargetAssignment = allAssignments - .find(assignment => { - if ( - assignment === targetAssignment || - assignment.segments.length && assignment.segments.every(segment => !targetAssignment.segments.includes(segment)) - ) { - return false; - } - if (isIdentifierEvaluatedAfterAssignment(targetAssignment, assignment.identifier)) { - return true; - } - if ( - assignment.expression && - assignment.expression.range[0] <= targetAssignment.identifier.range[0] && - targetAssignment.identifier.range[1] <= assignment.expression.range[1] - ) { - - /* - * The target assignment is in an expression that is evaluated before the assignment. - * e.g. x=(x=1); - * ^^^ targetAssignment - * ^^^^^^^ assignment - */ - return true; - } - - return false; - }); - - for (const reference of readReferences) { - - /* - * If the scope of the reference is outside the current code path scope, - * we cannot track whether this assignment is not used. - * For example, it can also be called asynchronously. - */ - if (target.scope !== getCodePathStartScope(reference.from)) { - return; - } - - // Checks if it is used in the same segment as the target assignment. - if ( - isIdentifierEvaluatedAfterAssignment(targetAssignment, reference.identifier) && - ( - isIdentifierUsedBetweenAssignedAndEqualSign(targetAssignment, reference.identifier) || - targetAssignment.segments.some(segment => isIdentifierUsedInSegment(segment, reference.identifier)) - ) - ) { - - if ( - otherAssignmentAfterTargetAssignment && - isIdentifierEvaluatedAfterAssignment(otherAssignmentAfterTargetAssignment, reference.identifier) - ) { - - // There was another assignment before the reference. Therefore, it has not been used yet. - continue; - } - - // Uses in statements after the written identifier. - return; - } - - if (otherAssignmentAfterTargetAssignment) { - - /* - * The assignment was followed by another assignment in the same segment. - * Therefore, there is no need to check the next segment. - */ - continue; - } - - // Check subsequent segments. - for (const subsequentSegment of getSubsequentSegments()) { - if (isIdentifierUsedInSegment(subsequentSegment.segment, reference.identifier)) { - if ( - subsequentSegment.assignment && - isIdentifierEvaluatedAfterAssignment(subsequentSegment.assignment, reference.identifier) - ) { - - // There was another assignment before the reference. Therefore, it has not been used yet. - continue; - } - - // It is used - return; - } - } - } - context.report({ - node: targetAssignment.identifier, - messageId: "unnecessaryAssignment" - }); - } - - // Verify that each assignment in the code path is used. - for (const assignments of target.assignments.values()) { - assignments.sort((a, b) => a.identifier.range[0] - b.identifier.range[0]); - for (const assignment of assignments) { - verifyAssignmentIsUsed(assignment, assignments); - } - } - } - - return { - onCodePathStart(codePath, node) { - const scope = sourceCode.getScope(node); - - scopeStack = { - upper: scopeStack, - codePath, - scope, - segments: Object.create(null), - currentSegments: new Set(), - assignments: new Map() - }; - codePathStartScopes.add(scopeStack.scope); - }, - onCodePathEnd() { - verify(scopeStack); - - scopeStack = scopeStack.upper; - }, - onCodePathSegmentStart(segment) { - const segmentInfo = { segment, first: null, last: null }; - - scopeStack.segments[segment.id] = segmentInfo; - scopeStack.currentSegments.add(segment); - }, - onCodePathSegmentEnd(segment) { - scopeStack.currentSegments.delete(segment); - }, - Identifier(node) { - for (const segment of scopeStack.currentSegments) { - const segmentInfo = scopeStack.segments[segment.id]; - - if (!segmentInfo.first) { - segmentInfo.first = node; - } - segmentInfo.last = node; - } - }, - ":matches(VariableDeclarator[init!=null], AssignmentExpression, UpdateExpression):exit"(node) { - if (scopeStack.currentSegments.size === 0) { - - // Ignore unreachable segments - return; - } - - const assignments = scopeStack.assignments; - - let pattern; - let expression = null; - - if (node.type === "VariableDeclarator") { - pattern = node.id; - expression = node.init; - } else if (node.type === "AssignmentExpression") { - pattern = node.left; - expression = node.right; - } else { // UpdateExpression - pattern = node.argument; - } - - for (const identifier of extractIdentifiersFromPattern(pattern)) { - const scope = sourceCode.getScope(identifier); - - /** @type {Variable} */ - const variable = findVariable(scope, identifier); - - if (!variable) { - continue; - } - - // We don't know where global variables are used. - if (variable.scope.type === "global" && variable.defs.length === 0) { - continue; - } - - /* - * If the scope of the variable is outside the current code path scope, - * we cannot track whether this assignment is not used. - */ - if (scopeStack.scope !== getCodePathStartScope(variable.scope)) { - continue; - } - - // Variables marked by `markVariableAsUsed()` or - // exported by "exported" block comment. - if (variable.eslintUsed) { - continue; - } - - // Variables exported by ESM export syntax - if (variable.scope.type === "module") { - if ( - variable.defs - .some(def => ( - (def.type === "Variable" && def.parent.parent.type === "ExportNamedDeclaration") || - ( - def.type === "FunctionName" && - ( - def.node.parent.type === "ExportNamedDeclaration" || - def.node.parent.type === "ExportDefaultDeclaration" - ) - ) || - ( - def.type === "ClassName" && - ( - def.node.parent.type === "ExportNamedDeclaration" || - def.node.parent.type === "ExportDefaultDeclaration" - ) - ) - )) - ) { - continue; - } - if (variable.references.some(reference => reference.identifier.parent.type === "ExportSpecifier")) { - - // It have `export { ... }` reference. - continue; - } - } - - let list = assignments.get(variable); - - if (!list) { - list = []; - assignments.set(variable, list); - } - list.push({ - variable, - identifier, - node, - expression, - segments: [...scopeStack.currentSegments] - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [], + + messages: { + unnecessaryAssignment: + "This assigned value is not used in subsequent statements.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * @typedef {Object} ScopeStack + * @property {CodePath} codePath The code path of this scope stack. + * @property {Scope} scope The scope of this scope stack. + * @property {ScopeStack} upper The upper scope stack. + * @property {Record} segments The map of ScopeStackSegmentInfo. + * @property {Set} currentSegments The current CodePathSegments. + * @property {Map} assignments The map of list of AssignmentInfo for each variable. + * @property {Array} tryStatementBlocks The array of TryStatement block nodes in this scope stack. + */ + /** + * @typedef {Object} ScopeStackSegmentInfo + * @property {CodePathSegment} segment The code path segment. + * @property {Identifier|null} first The first identifier that appears within the segment. + * @property {Identifier|null} last The last identifier that appears within the segment. + * `first` and `last` are used to determine whether an identifier exists within the segment position range. + * Since it is used as a range of segments, we should originally hold all nodes, not just identifiers, + * but since the only nodes to be judged are identifiers, it is sufficient to have a range of identifiers. + */ + /** + * @typedef {Object} AssignmentInfo + * @property {Variable} variable The variable that is assigned. + * @property {Identifier} identifier The identifier that is assigned. + * @property {VariableDeclarator|AssignmentExpression|UpdateExpression} node The node where the variable was updated. + * @property {Expression|null} expression The expression that is evaluated before the assignment. + * @property {CodePathSegment[]} segments The code path segments where the assignment was made. + */ + + /** @type {ScopeStack} */ + let scopeStack = null; + + /** @type {Set} */ + const codePathStartScopes = new Set(); + + /** + * Gets the scope of code path start from given scope + * @param {Scope} scope The initial scope + * @returns {Scope} The scope of code path start + * @throws {Error} Unexpected error + */ + function getCodePathStartScope(scope) { + let target = scope; + + while (target) { + if (codePathStartScopes.has(target)) { + return target; + } + target = target.upper; + } + + // Should be unreachable + return null; + } + + /** + * Verify the given scope stack. + * @param {ScopeStack} target The scope stack to verify. + * @returns {void} + */ + function verify(target) { + /** + * Checks whether the given identifier is used in the segment. + * @param {CodePathSegment} segment The code path segment. + * @param {Identifier} identifier The identifier to check. + * @returns {boolean} `true` if the identifier is used in the segment. + */ + function isIdentifierUsedInSegment(segment, identifier) { + const segmentInfo = target.segments[segment.id]; + + return ( + segmentInfo.first && + segmentInfo.last && + segmentInfo.first.range[0] <= identifier.range[0] && + identifier.range[1] <= segmentInfo.last.range[1] + ); + } + + /** + * Verifies whether the given assignment info is an used assignment. + * Report if it is an unused assignment. + * @param {AssignmentInfo} targetAssignment The assignment info to verify. + * @param {AssignmentInfo[]} allAssignments The list of all assignment info for variables. + * @returns {void} + */ + function verifyAssignmentIsUsed(targetAssignment, allAssignments) { + // Skip assignment if it is in a try block. + const isAssignmentInTryBlock = target.tryStatementBlocks.some( + tryBlock => + tryBlock.range[0] <= + targetAssignment.identifier.range[0] && + targetAssignment.identifier.range[1] <= + tryBlock.range[1], + ); + + if (isAssignmentInTryBlock) { + return; + } + + /** + * @typedef {Object} SubsequentSegmentData + * @property {CodePathSegment} segment The code path segment + * @property {AssignmentInfo} [assignment] The first occurrence of the assignment within the segment. + * There is no need to check if the variable is used after this assignment, + * as the value it was assigned will be used. + */ + + /** + * Information used in `getSubsequentSegments()`. + * To avoid unnecessary iterations, cache information that has already been iterated over, + * and if additional iterations are needed, start iterating from the retained position. + */ + const subsequentSegmentData = { + /** + * Cache of subsequent segment information list that have already been iterated. + * @type {SubsequentSegmentData[]} + */ + results: [], + + /** + * Subsequent segments that have already been iterated on. Used to avoid infinite loops. + * @type {Set} + */ + subsequentSegments: new Set(), + + /** + * Unexplored code path segment. + * If additional iterations are needed, consume this information and iterate. + * @type {CodePathSegment[]} + */ + queueSegments: targetAssignment.segments.flatMap( + segment => segment.nextSegments, + ), + }; + + /** + * Gets the subsequent segments from the segment of + * the assignment currently being validated (targetAssignment). + * @returns {Iterable} the subsequent segments + */ + function* getSubsequentSegments() { + yield* subsequentSegmentData.results; + + while (subsequentSegmentData.queueSegments.length > 0) { + const nextSegment = + subsequentSegmentData.queueSegments.shift(); + + if ( + subsequentSegmentData.subsequentSegments.has( + nextSegment, + ) + ) { + continue; + } + subsequentSegmentData.subsequentSegments.add( + nextSegment, + ); + + const assignmentInSegment = allAssignments.find( + otherAssignment => + otherAssignment.segments.includes( + nextSegment, + ) && + !isIdentifierUsedBetweenAssignedAndEqualSign( + otherAssignment, + targetAssignment.identifier, + ), + ); + + if (!assignmentInSegment) { + /* + * Stores the next segment to explore. + * If `assignmentInSegment` exists, + * we are guarding it because we don't need to explore the next segment. + */ + subsequentSegmentData.queueSegments.push( + ...nextSegment.nextSegments, + ); + } + + /** @type {SubsequentSegmentData} */ + const result = { + segment: nextSegment, + assignment: assignmentInSegment, + }; + + subsequentSegmentData.results.push(result); + yield result; + } + } + + if ( + targetAssignment.variable.references.some( + ref => ref.identifier.type !== "Identifier", + ) + ) { + /** + * Skip checking for a variable that has at least one non-identifier reference. + * It's generated by plugins and cannot be handled reliably in the core rule. + */ + return; + } + + const readReferences = + targetAssignment.variable.references.filter(reference => + reference.isRead(), + ); + + if (!readReferences.length) { + /* + * It is not just an unnecessary assignment, but an unnecessary (unused) variable + * and thus should not be reported by this rule because it is reported by `no-unused-vars`. + */ + return; + } + + /** + * Other assignment on the current segment and after current assignment. + */ + const otherAssignmentAfterTargetAssignment = + allAssignments.find(assignment => { + if ( + assignment === targetAssignment || + (assignment.segments.length && + assignment.segments.every( + segment => + !targetAssignment.segments.includes( + segment, + ), + )) + ) { + return false; + } + if ( + isIdentifierEvaluatedAfterAssignment( + targetAssignment, + assignment.identifier, + ) + ) { + return true; + } + if ( + assignment.expression && + assignment.expression.range[0] <= + targetAssignment.identifier.range[0] && + targetAssignment.identifier.range[1] <= + assignment.expression.range[1] + ) { + /* + * The target assignment is in an expression that is evaluated before the assignment. + * e.g. x=(x=1); + * ^^^ targetAssignment + * ^^^^^^^ assignment + */ + return true; + } + + return false; + }); + + for (const reference of readReferences) { + /* + * If the scope of the reference is outside the current code path scope, + * we cannot track whether this assignment is not used. + * For example, it can also be called asynchronously. + */ + if ( + target.scope !== getCodePathStartScope(reference.from) + ) { + return; + } + + // Checks if it is used in the same segment as the target assignment. + if ( + isIdentifierEvaluatedAfterAssignment( + targetAssignment, + reference.identifier, + ) && + (isIdentifierUsedBetweenAssignedAndEqualSign( + targetAssignment, + reference.identifier, + ) || + targetAssignment.segments.some(segment => + isIdentifierUsedInSegment( + segment, + reference.identifier, + ), + )) + ) { + if ( + otherAssignmentAfterTargetAssignment && + isIdentifierEvaluatedAfterAssignment( + otherAssignmentAfterTargetAssignment, + reference.identifier, + ) + ) { + // There was another assignment before the reference. Therefore, it has not been used yet. + continue; + } + + // Uses in statements after the written identifier. + return; + } + + if (otherAssignmentAfterTargetAssignment) { + /* + * The assignment was followed by another assignment in the same segment. + * Therefore, there is no need to check the next segment. + */ + continue; + } + + // Check subsequent segments. + for (const subsequentSegment of getSubsequentSegments()) { + if ( + isIdentifierUsedInSegment( + subsequentSegment.segment, + reference.identifier, + ) + ) { + if ( + subsequentSegment.assignment && + isIdentifierEvaluatedAfterAssignment( + subsequentSegment.assignment, + reference.identifier, + ) + ) { + // There was another assignment before the reference. Therefore, it has not been used yet. + continue; + } + + // It is used + return; + } + } + } + context.report({ + node: targetAssignment.identifier, + messageId: "unnecessaryAssignment", + }); + } + + // Verify that each assignment in the code path is used. + for (const assignments of target.assignments.values()) { + assignments.sort( + (a, b) => a.identifier.range[0] - b.identifier.range[0], + ); + for (const assignment of assignments) { + verifyAssignmentIsUsed(assignment, assignments); + } + } + } + + return { + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); + + scopeStack = { + upper: scopeStack, + codePath, + scope, + segments: Object.create(null), + currentSegments: new Set(), + assignments: new Map(), + tryStatementBlocks: [], + }; + codePathStartScopes.add(scopeStack.scope); + }, + onCodePathEnd() { + verify(scopeStack); + + scopeStack = scopeStack.upper; + }, + onCodePathSegmentStart(segment) { + const segmentInfo = { segment, first: null, last: null }; + + scopeStack.segments[segment.id] = segmentInfo; + scopeStack.currentSegments.add(segment); + }, + onCodePathSegmentEnd(segment) { + scopeStack.currentSegments.delete(segment); + }, + TryStatement(node) { + scopeStack.tryStatementBlocks.push(node.block); + }, + Identifier(node) { + for (const segment of scopeStack.currentSegments) { + const segmentInfo = scopeStack.segments[segment.id]; + + if (!segmentInfo.first) { + segmentInfo.first = node; + } + segmentInfo.last = node; + } + }, + ":matches(VariableDeclarator[init!=null], AssignmentExpression, UpdateExpression):exit"( + node, + ) { + if (scopeStack.currentSegments.size === 0) { + // Ignore unreachable segments + return; + } + + const assignments = scopeStack.assignments; + + let pattern; + let expression = null; + + if (node.type === "VariableDeclarator") { + pattern = node.id; + expression = node.init; + } else if (node.type === "AssignmentExpression") { + pattern = node.left; + expression = node.right; + } else { + // UpdateExpression + pattern = node.argument; + } + + for (const identifier of extractIdentifiersFromPattern( + pattern, + )) { + const scope = sourceCode.getScope(identifier); + + /** @type {Variable} */ + const variable = findVariable(scope, identifier); + + if (!variable) { + continue; + } + + // We don't know where global variables are used. + if ( + variable.scope.type === "global" && + variable.defs.length === 0 + ) { + continue; + } + + /* + * If the scope of the variable is outside the current code path scope, + * we cannot track whether this assignment is not used. + */ + if ( + scopeStack.scope !== + getCodePathStartScope(variable.scope) + ) { + continue; + } + + // Variables marked by `markVariableAsUsed()` or + // exported by "exported" block comment. + if (variable.eslintUsed) { + continue; + } + + // Variables exported by ESM export syntax + if (variable.scope.type === "module") { + if ( + variable.defs.some( + def => + (def.type === "Variable" && + def.parent.parent.type === + "ExportNamedDeclaration") || + (def.type === "FunctionName" && + (def.node.parent.type === + "ExportNamedDeclaration" || + def.node.parent.type === + "ExportDefaultDeclaration")) || + (def.type === "ClassName" && + (def.node.parent.type === + "ExportNamedDeclaration" || + def.node.parent.type === + "ExportDefaultDeclaration")), + ) + ) { + continue; + } + if ( + variable.references.some( + reference => + reference.identifier.parent.type === + "ExportSpecifier", + ) + ) { + // It have `export { ... }` reference. + continue; + } + } + + let list = assignments.get(variable); + + if (!list) { + list = []; + assignments.set(variable, list); + } + list.push({ + variable, + identifier, + node, + expression, + segments: [...scopeStack.currentSegments], + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-useless-backreference.js b/lib/rules/no-useless-backreference.js index d41a89883a5c..739d8aa42bf8 100644 --- a/lib/rules/no-useless-backreference.js +++ b/lib/rules/no-useless-backreference.js @@ -9,7 +9,12 @@ // Requirements //------------------------------------------------------------------------------ -const { CALL, CONSTRUCT, ReferenceTracker, getStringIfConstant } = require("@eslint-community/eslint-utils"); +const { + CALL, + CONSTRUCT, + ReferenceTracker, + getStringIfConstant, +} = require("@eslint-community/eslint-utils"); const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); //------------------------------------------------------------------------------ @@ -24,15 +29,15 @@ const parser = new RegExpParser(); * @returns {regexpp.Node[]} Array that starts with the given node and ends with the root node. */ function getPathToRoot(node) { - const path = []; - let current = node; + const path = []; + let current = node; - do { - path.push(current); - current = current.parent; - } while (current); + do { + path.push(current); + current = current.parent; + } while (current); - return path; + return path; } /** @@ -41,8 +46,10 @@ function getPathToRoot(node) { * @returns {boolean} `true` if it is a lookaround node. */ function isLookaround(node) { - return node.type === "Assertion" && - (node.kind === "lookahead" || node.kind === "lookbehind"); + return ( + node.type === "Assertion" && + (node.kind === "lookahead" || node.kind === "lookbehind") + ); } /** @@ -51,7 +58,7 @@ function isLookaround(node) { * @returns {boolean} `true` if it is a negative lookaround node. */ function isNegativeLookaround(node) { - return isLookaround(node) && node.negate; + return isLookaround(node) && node.negate; } //------------------------------------------------------------------------------ @@ -60,185 +67,197 @@ function isNegativeLookaround(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - docs: { - description: "Disallow useless backreferences in regular expressions", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-useless-backreference" - }, - - schema: [], - - messages: { - nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} from within that group.", - forward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears later in the pattern.", - backward: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears before in the same lookbehind.", - disjunctive: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in another alternative.", - intoNegativeLookaround: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in a negative lookaround." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - /** - * Checks and reports useless backreferences in the given regular expression. - * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. - * @param {string} pattern Regular expression pattern. - * @param {string} flags Regular expression flags. - * @returns {void} - */ - function checkRegex(node, pattern, flags) { - let regExpAST; - - try { - regExpAST = parser.parsePattern(pattern, 0, pattern.length, { unicode: flags.includes("u"), unicodeSets: flags.includes("v") }); - } catch { - - // Ignore regular expressions with syntax errors - return; - } - - visitRegExpAST(regExpAST, { - onBackreferenceEnter(bref) { - const groups = [bref.resolved].flat(), - brefPath = getPathToRoot(bref); - - const problems = groups.map(group => { - const groupPath = getPathToRoot(group); - - if (brefPath.includes(group)) { - - // group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match. - return { - messageId: "nested", - group - }; - } - - - // Start from the root to find the lowest common ancestor. - let i = brefPath.length - 1, - j = groupPath.length - 1; - - do { - i--; - j--; - } while (brefPath[i] === groupPath[j]); - - const indexOfLowestCommonAncestor = j + 1, - groupCut = groupPath.slice(0, indexOfLowestCommonAncestor), - commonPath = groupPath.slice(indexOfLowestCommonAncestor), - lowestCommonLookaround = commonPath.find(isLookaround), - isMatchingBackward = lowestCommonLookaround && lowestCommonLookaround.kind === "lookbehind"; - - if (groupCut.at(-1).type === "Alternative") { - - // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive. - return { - messageId: "disjunctive", - group - }; - } - if (!isMatchingBackward && bref.end <= group.start) { - - // bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match. - return { - messageId: "forward", - group - }; - } - if (isMatchingBackward && group.end <= bref.start) { - - // the opposite of the previous when the regex is matching backward in a lookbehind context. - return { - messageId: "backward", - group - }; - } - if (groupCut.some(isNegativeLookaround)) { - - // group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match. - return { - messageId: "intoNegativeLookaround", - group - }; - } - - return null; - }); - - if (problems.length === 0 || problems.some(problem => !problem)) { - - // If there are no problems or no problems with any group then do not report it. - return; - } - - let problemsToReport; - - // Gets problems that appear in the same disjunction. - const problemsInSameDisjunction = problems.filter(problem => problem.messageId !== "disjunctive"); - - if (problemsInSameDisjunction.length) { - - // Only report problems that appear in the same disjunction. - problemsToReport = problemsInSameDisjunction; - } else { - - // If all groups appear in different disjunctions, report it. - problemsToReport = problems; - } - - const [{ messageId, group }, ...other] = problemsToReport; - let otherGroups = ""; - - if (other.length === 1) { - otherGroups = " and another group"; - } else if (other.length > 1) { - otherGroups = ` and other ${other.length} groups`; - } - context.report({ - node, - messageId, - data: { - bref: bref.raw, - group: group.raw, - otherGroups - } - }); - } - }); - } - - return { - "Literal[regex]"(node) { - const { pattern, flags } = node.regex; - - checkRegex(node, pattern, flags); - }, - Program(node) { - const scope = sourceCode.getScope(node), - tracker = new ReferenceTracker(scope), - traceMap = { - RegExp: { - [CALL]: true, - [CONSTRUCT]: true - } - }; - - for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { - const [patternNode, flagsNode] = refNode.arguments, - pattern = getStringIfConstant(patternNode, scope), - flags = getStringIfConstant(flagsNode, scope); - - if (typeof pattern === "string") { - checkRegex(refNode, pattern, flags || ""); - } - } - } - }; - } + meta: { + type: "problem", + + docs: { + description: + "Disallow useless backreferences in regular expressions", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-useless-backreference", + }, + + schema: [], + + messages: { + nested: "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} from within that group.", + forward: + "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears later in the pattern.", + backward: + "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which appears before in the same lookbehind.", + disjunctive: + "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in another alternative.", + intoNegativeLookaround: + "Backreference '{{ bref }}' will be ignored. It references group '{{ group }}'{{ otherGroups }} which is in a negative lookaround.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Checks and reports useless backreferences in the given regular expression. + * @param {ASTNode} node Node that represents regular expression. A regex literal or RegExp constructor call. + * @param {string} pattern Regular expression pattern. + * @param {string} flags Regular expression flags. + * @returns {void} + */ + function checkRegex(node, pattern, flags) { + let regExpAST; + + try { + regExpAST = parser.parsePattern(pattern, 0, pattern.length, { + unicode: flags.includes("u"), + unicodeSets: flags.includes("v"), + }); + } catch { + // Ignore regular expressions with syntax errors + return; + } + + visitRegExpAST(regExpAST, { + onBackreferenceEnter(bref) { + const groups = [bref.resolved].flat(), + brefPath = getPathToRoot(bref); + + const problems = groups.map(group => { + const groupPath = getPathToRoot(group); + + if (brefPath.includes(group)) { + // group is bref's ancestor => bref is nested ('nested reference') => group hasn't matched yet when bref starts to match. + return { + messageId: "nested", + group, + }; + } + + // Start from the root to find the lowest common ancestor. + let i = brefPath.length - 1, + j = groupPath.length - 1; + + do { + i--; + j--; + } while (brefPath[i] === groupPath[j]); + + const indexOfLowestCommonAncestor = j + 1, + groupCut = groupPath.slice( + 0, + indexOfLowestCommonAncestor, + ), + commonPath = groupPath.slice( + indexOfLowestCommonAncestor, + ), + lowestCommonLookaround = + commonPath.find(isLookaround), + isMatchingBackward = + lowestCommonLookaround && + lowestCommonLookaround.kind === "lookbehind"; + + if (groupCut.at(-1).type === "Alternative") { + // group's and bref's ancestor nodes below the lowest common ancestor are sibling alternatives => they're disjunctive. + return { + messageId: "disjunctive", + group, + }; + } + if (!isMatchingBackward && bref.end <= group.start) { + // bref is left, group is right ('forward reference') => group hasn't matched yet when bref starts to match. + return { + messageId: "forward", + group, + }; + } + if (isMatchingBackward && group.end <= bref.start) { + // the opposite of the previous when the regex is matching backward in a lookbehind context. + return { + messageId: "backward", + group, + }; + } + if (groupCut.some(isNegativeLookaround)) { + // group is in a negative lookaround which isn't bref's ancestor => group has already failed when bref starts to match. + return { + messageId: "intoNegativeLookaround", + group, + }; + } + + return null; + }); + + if ( + problems.length === 0 || + problems.some(problem => !problem) + ) { + // If there are no problems or no problems with any group then do not report it. + return; + } + + let problemsToReport; + + // Gets problems that appear in the same disjunction. + const problemsInSameDisjunction = problems.filter( + problem => problem.messageId !== "disjunctive", + ); + + if (problemsInSameDisjunction.length) { + // Only report problems that appear in the same disjunction. + problemsToReport = problemsInSameDisjunction; + } else { + // If all groups appear in different disjunctions, report it. + problemsToReport = problems; + } + + const [{ messageId, group }, ...other] = problemsToReport; + let otherGroups = ""; + + if (other.length === 1) { + otherGroups = " and another group"; + } else if (other.length > 1) { + otherGroups = ` and other ${other.length} groups`; + } + context.report({ + node, + messageId, + data: { + bref: bref.raw, + group: group.raw, + otherGroups, + }, + }); + }, + }); + } + + return { + "Literal[regex]"(node) { + const { pattern, flags } = node.regex; + + checkRegex(node, pattern, flags); + }, + Program(node) { + const scope = sourceCode.getScope(node), + tracker = new ReferenceTracker(scope), + traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true, + }, + }; + + for (const { node: refNode } of tracker.iterateGlobalReferences( + traceMap, + )) { + const [patternNode, flagsNode] = refNode.arguments, + pattern = getStringIfConstant(patternNode, scope), + flags = getStringIfConstant(flagsNode, scope); + + if (typeof pattern === "string") { + checkRegex(refNode, pattern, flags || ""); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-useless-call.js b/lib/rules/no-useless-call.js index dea2b47a4a42..9ed936f3fa23 100644 --- a/lib/rules/no-useless-call.js +++ b/lib/rules/no-useless-call.js @@ -17,20 +17,19 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} Whether or not the node is a `.call()`/`.apply()`. */ function isCallOrNonVariadicApply(node) { - const callee = astUtils.skipChainExpression(node.callee); - - return ( - callee.type === "MemberExpression" && - callee.property.type === "Identifier" && - callee.computed === false && - ( - (callee.property.name === "call" && node.arguments.length >= 1) || - (callee.property.name === "apply" && node.arguments.length === 2 && node.arguments[1].type === "ArrayExpression") - ) - ); + const callee = astUtils.skipChainExpression(node.callee); + + return ( + callee.type === "MemberExpression" && + callee.property.type === "Identifier" && + callee.computed === false && + ((callee.property.name === "call" && node.arguments.length >= 1) || + (callee.property.name === "apply" && + node.arguments.length === 2 && + node.arguments[1].type === "ArrayExpression")) + ); } - /** * Checks whether or not `thisArg` is not changed by `.call()`/`.apply()`. * @param {ASTNode|null} expectedThis The node that is the owner of the applied function. @@ -39,10 +38,10 @@ function isCallOrNonVariadicApply(node) { * @returns {boolean} Whether or not `thisArg` is not changed by `.call()`/`.apply()`. */ function isValidThisArg(expectedThis, thisArg, sourceCode) { - if (!expectedThis) { - return astUtils.isNullOrUndefined(thisArg); - } - return astUtils.equalTokens(expectedThis, thisArg, sourceCode); + if (!expectedThis) { + return astUtils.isNullOrUndefined(thisArg); + } + return astUtils.equalTokens(expectedThis, thisArg, sourceCode); } //------------------------------------------------------------------------------ @@ -51,40 +50,46 @@ function isValidThisArg(expectedThis, thisArg, sourceCode) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary calls to `.call()` and `.apply()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-useless-call" - }, - - schema: [], - - messages: { - unnecessaryCall: "Unnecessary '.{{name}}()'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - if (!isCallOrNonVariadicApply(node)) { - return; - } - - const callee = astUtils.skipChainExpression(node.callee); - const applied = astUtils.skipChainExpression(callee.object); - const expectedThis = (applied.type === "MemberExpression") ? applied.object : null; - const thisArg = node.arguments[0]; - - if (isValidThisArg(expectedThis, thisArg, sourceCode)) { - context.report({ node, messageId: "unnecessaryCall", data: { name: callee.property.name } }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow unnecessary calls to `.call()` and `.apply()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-useless-call", + }, + + schema: [], + + messages: { + unnecessaryCall: "Unnecessary '.{{name}}()'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + CallExpression(node) { + if (!isCallOrNonVariadicApply(node)) { + return; + } + + const callee = astUtils.skipChainExpression(node.callee); + const applied = astUtils.skipChainExpression(callee.object); + const expectedThis = + applied.type === "MemberExpression" ? applied.object : null; + const thisArg = node.arguments[0]; + + if (isValidThisArg(expectedThis, thisArg, sourceCode)) { + context.report({ + node, + messageId: "unnecessaryCall", + data: { name: callee.property.name }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-useless-catch.js b/lib/rules/no-useless-catch.js index e02013db627b..4444b4dde41c 100644 --- a/lib/rules/no-useless-catch.js +++ b/lib/rules/no-useless-catch.js @@ -11,47 +11,47 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Disallow unnecessary `catch` clauses", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-useless-catch" - }, + docs: { + description: "Disallow unnecessary `catch` clauses", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-useless-catch", + }, - schema: [], + schema: [], - messages: { - unnecessaryCatchClause: "Unnecessary catch clause.", - unnecessaryCatch: "Unnecessary try/catch wrapper." - } - }, + messages: { + unnecessaryCatchClause: "Unnecessary catch clause.", + unnecessaryCatch: "Unnecessary try/catch wrapper.", + }, + }, - create(context) { - return { - CatchClause(node) { - if ( - node.param && - node.param.type === "Identifier" && - node.body.body.length && - node.body.body[0].type === "ThrowStatement" && - node.body.body[0].argument.type === "Identifier" && - node.body.body[0].argument.name === node.param.name - ) { - if (node.parent.finalizer) { - context.report({ - node, - messageId: "unnecessaryCatchClause" - }); - } else { - context.report({ - node: node.parent, - messageId: "unnecessaryCatch" - }); - } - } - } - }; - } + create(context) { + return { + CatchClause(node) { + if ( + node.param && + node.param.type === "Identifier" && + node.body.body.length && + node.body.body[0].type === "ThrowStatement" && + node.body.body[0].argument.type === "Identifier" && + node.body.body[0].argument.name === node.param.name + ) { + if (node.parent.finalizer) { + context.report({ + node, + messageId: "unnecessaryCatchClause", + }); + } else { + context.report({ + node: node.parent, + messageId: "unnecessaryCatch", + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/no-useless-computed-key.js b/lib/rules/no-useless-computed-key.js index 3f537caca8c2..fdb183d2a9b5 100644 --- a/lib/rules/no-useless-computed-key.js +++ b/lib/rules/no-useless-computed-key.js @@ -40,48 +40,47 @@ const astUtils = require("./utils/ast-utils"); * @returns {void} `true` if the node has useless computed key. */ function hasUselessComputedKey(node) { - if (!node.computed) { - return false; - } + if (!node.computed) { + return false; + } - const { key } = node; + const { key } = node; - if (key.type !== "Literal") { - return false; - } + if (key.type !== "Literal") { + return false; + } - const { value } = key; + const { value } = key; - if (typeof value !== "number" && typeof value !== "string") { - return false; - } + if (typeof value !== "number" && typeof value !== "string") { + return false; + } - switch (node.type) { - case "Property": - if (node.parent.type === "ObjectExpression") { - return value !== "__proto__"; - } - return true; + switch (node.type) { + case "Property": + if (node.parent.type === "ObjectExpression") { + return value !== "__proto__"; + } + return true; - case "PropertyDefinition": - if (node.static) { - return value !== "constructor" && value !== "prototype"; - } + case "PropertyDefinition": + if (node.static) { + return value !== "constructor" && value !== "prototype"; + } - return value !== "constructor"; + return value !== "constructor"; - case "MethodDefinition": - if (node.static) { - return value !== "prototype"; - } + case "MethodDefinition": + if (node.static) { + return value !== "prototype"; + } - return value !== "constructor"; - - /* c8 ignore next */ - default: - throw new Error(`Unexpected node type: ${node.type}`); - } + return value !== "constructor"; + /* c8 ignore next */ + default: + throw new Error(`Unexpected node type: ${node.type}`); + } } //------------------------------------------------------------------------------ @@ -90,86 +89,116 @@ function hasUselessComputedKey(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [{ - type: "object", - properties: { - enforceForClassMembers: { - type: "boolean" - } - }, - additionalProperties: false - }], - fixable: "code", - - messages: { - unnecessarilyComputedProperty: "Unnecessarily computed property [{{property}}] found." - } - }, - create(context) { - const sourceCode = context.sourceCode; - const [{ enforceForClassMembers }] = context.options; - - /** - * Reports a given node if it violated this rule. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function check(node) { - if (hasUselessComputedKey(node)) { - const { key } = node; - - context.report({ - node, - messageId: "unnecessarilyComputedProperty", - data: { property: sourceCode.getText(key) }, - fix(fixer) { - const leftSquareBracket = sourceCode.getTokenBefore(key, astUtils.isOpeningBracketToken); - const rightSquareBracket = sourceCode.getTokenAfter(key, astUtils.isClosingBracketToken); - - // If there are comments between the brackets and the property name, don't do a fix. - if (sourceCode.commentsExistBetween(leftSquareBracket, rightSquareBracket)) { - return null; - } - - const tokenBeforeLeftBracket = sourceCode.getTokenBefore(leftSquareBracket); - - // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} }) - const needsSpaceBeforeKey = tokenBeforeLeftBracket.range[1] === leftSquareBracket.range[0] && - !astUtils.canTokensBeAdjacent(tokenBeforeLeftBracket, sourceCode.getFirstToken(key)); - - const replacementKey = (needsSpaceBeforeKey ? " " : "") + key.raw; - - return fixer.replaceTextRange([leftSquareBracket.range[0], rightSquareBracket.range[1]], replacementKey); - } - }); - } - } - - /** - * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`. - * @returns {void} - * @private - */ - function noop() {} - - return { - Property: check, - MethodDefinition: enforceForClassMembers ? check : noop, - PropertyDefinition: enforceForClassMembers ? check : noop - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + enforceForClassMembers: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + fixable: "code", + + messages: { + unnecessarilyComputedProperty: + "Unnecessarily computed property [{{property}}] found.", + }, + }, + create(context) { + const sourceCode = context.sourceCode; + const [{ enforceForClassMembers }] = context.options; + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function check(node) { + if (hasUselessComputedKey(node)) { + const { key } = node; + + context.report({ + node, + messageId: "unnecessarilyComputedProperty", + data: { property: sourceCode.getText(key) }, + fix(fixer) { + const leftSquareBracket = sourceCode.getTokenBefore( + key, + astUtils.isOpeningBracketToken, + ); + const rightSquareBracket = sourceCode.getTokenAfter( + key, + astUtils.isClosingBracketToken, + ); + + // If there are comments between the brackets and the property name, don't do a fix. + if ( + sourceCode.commentsExistBetween( + leftSquareBracket, + rightSquareBracket, + ) + ) { + return null; + } + + const tokenBeforeLeftBracket = + sourceCode.getTokenBefore(leftSquareBracket); + + // Insert a space before the key to avoid changing identifiers, e.g. ({ get[2]() {} }) to ({ get2() {} }) + const needsSpaceBeforeKey = + tokenBeforeLeftBracket.range[1] === + leftSquareBracket.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBeforeLeftBracket, + sourceCode.getFirstToken(key), + ); + + const replacementKey = + (needsSpaceBeforeKey ? " " : "") + key.raw; + + return fixer.replaceTextRange( + [ + leftSquareBracket.range[0], + rightSquareBracket.range[1], + ], + replacementKey, + ); + }, + }); + } + } + + /** + * A no-op function to act as placeholder for checking a node when the `enforceForClassMembers` option is `false`. + * @returns {void} + * @private + */ + function noop() {} + + return { + Property: check, + MethodDefinition: enforceForClassMembers ? check : noop, + PropertyDefinition: enforceForClassMembers ? check : noop, + }; + }, }; diff --git a/lib/rules/no-useless-concat.js b/lib/rules/no-useless-concat.js index b25ed25fb24f..41b07096a2a1 100644 --- a/lib/rules/no-useless-concat.js +++ b/lib/rules/no-useless-concat.js @@ -20,7 +20,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is a concatenation. */ function isConcatenation(node) { - return node.type === "BinaryExpression" && node.operator === "+"; + return node.type === "BinaryExpression" && node.operator === "+"; } /** @@ -29,7 +29,7 @@ function isConcatenation(node) { * @returns {boolean} `true` if the token is a `+` token. */ function isConcatOperatorToken(token) { - return token.value === "+" && token.type === "Punctuator"; + return token.value === "+" && token.type === "Punctuator"; } /** @@ -38,12 +38,12 @@ function isConcatOperatorToken(token) { * @returns {ASTNode} node */ function getLeft(node) { - let left = node.left; + let left = node.left; - while (isConcatenation(left)) { - left = left.right; - } - return left; + while (isConcatenation(left)) { + left = left.right; + } + return left; } /** @@ -52,12 +52,12 @@ function getLeft(node) { * @returns {ASTNode} node */ function getRight(node) { - let right = node.right; + let right = node.right; - while (isConcatenation(right)) { - right = right.left; - } - return right; + while (isConcatenation(right)) { + right = right.left; + } + return right; } //------------------------------------------------------------------------------ @@ -66,51 +66,56 @@ function getRight(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary concatenation of literals or template literals", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-useless-concat" - }, - - schema: [], - - messages: { - unexpectedConcat: "Unexpected string concatenation of literals." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - BinaryExpression(node) { - - // check if not concatenation - if (node.operator !== "+") { - return; - } - - // account for the `foo + "a" + "b"` case - const left = getLeft(node); - const right = getRight(node); - - if (astUtils.isStringLiteral(left) && - astUtils.isStringLiteral(right) && - astUtils.isTokenOnSameLine(left, right) - ) { - const operatorToken = sourceCode.getFirstTokenBetween(left, right, isConcatOperatorToken); - - context.report({ - node, - loc: operatorToken.loc, - messageId: "unexpectedConcat" - }); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow unnecessary concatenation of literals or template literals", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-useless-concat", + }, + + schema: [], + + messages: { + unexpectedConcat: "Unexpected string concatenation of literals.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + BinaryExpression(node) { + // check if not concatenation + if (node.operator !== "+") { + return; + } + + // account for the `foo + "a" + "b"` case + const left = getLeft(node); + const right = getRight(node); + + if ( + astUtils.isStringLiteral(left) && + astUtils.isStringLiteral(right) && + astUtils.isTokenOnSameLine(left, right) + ) { + const operatorToken = sourceCode.getFirstTokenBetween( + left, + right, + isConcatOperatorToken, + ); + + context.report({ + node, + loc: operatorToken.loc, + messageId: "unexpectedConcat", + }); + } + }, + }; + }, }; diff --git a/lib/rules/no-useless-constructor.js b/lib/rules/no-useless-constructor.js index 2c063caa26b3..1a556ec6a2e5 100644 --- a/lib/rules/no-useless-constructor.js +++ b/lib/rules/no-useless-constructor.js @@ -10,18 +10,47 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ +/** + * Checks whether any of a method's parameters have a decorator or are a parameter property. + * @param {ASTNode} node A method definition node. + * @returns {boolean} `true` if any parameter had a decorator or is a parameter property. + */ +function hasDecoratorsOrParameterProperty(node) { + return node.value.params.some( + param => + param.decorators?.length || param.type === "TSParameterProperty", + ); +} + +/** + * Checks whether a node's accessibility makes it not useless. + * @param {ASTNode} node A method definition node. + * @returns {boolean} `true` if the node has a useful accessibility. + */ +function hasUsefulAccessibility(node) { + switch (node.accessibility) { + case "protected": + case "private": + return true; + case "public": + return !!node.parent.parent.superClass; + default: + return false; + } +} + /** * Checks whether a given array of statements is a single call of `super`. * @param {ASTNode[]} body An array of statements to check. * @returns {boolean} `true` if the body is a single call of `super`. */ function isSingleSuperCall(body) { - return ( - body.length === 1 && - body[0].type === "ExpressionStatement" && - body[0].expression.type === "CallExpression" && - body[0].expression.callee.type === "Super" - ); + return ( + body.length === 1 && + body[0].type === "ExpressionStatement" && + body[0].expression.type === "CallExpression" && + body[0].expression.callee.type === "Super" + ); } /** @@ -31,7 +60,7 @@ function isSingleSuperCall(body) { * @returns {boolean} `true` if the node doesn't have any side effects. */ function isSimple(node) { - return node.type === "Identifier" || node.type === "RestElement"; + return node.type === "Identifier" || node.type === "RestElement"; } /** @@ -41,12 +70,12 @@ function isSimple(node) { * @returns {boolean} `true` if the superArgs is `...arguments`. */ function isSpreadArguments(superArgs) { - return ( - superArgs.length === 1 && - superArgs[0].type === "SpreadElement" && - superArgs[0].argument.type === "Identifier" && - superArgs[0].argument.name === "arguments" - ); + return ( + superArgs.length === 1 && + superArgs[0].type === "SpreadElement" && + superArgs[0].argument.type === "Identifier" && + superArgs[0].argument.name === "arguments" + ); } /** @@ -57,11 +86,11 @@ function isSpreadArguments(superArgs) { * name. */ function isValidIdentifierPair(ctorParam, superArg) { - return ( - ctorParam.type === "Identifier" && - superArg.type === "Identifier" && - ctorParam.name === superArg.name - ); + return ( + ctorParam.type === "Identifier" && + superArg.type === "Identifier" && + ctorParam.name === superArg.name + ); } /** @@ -72,11 +101,11 @@ function isValidIdentifierPair(ctorParam, superArg) { * same values. */ function isValidRestSpreadPair(ctorParam, superArg) { - return ( - ctorParam.type === "RestElement" && - superArg.type === "SpreadElement" && - isValidIdentifierPair(ctorParam.argument, superArg.argument) - ); + return ( + ctorParam.type === "RestElement" && + superArg.type === "SpreadElement" && + isValidIdentifierPair(ctorParam.argument, superArg.argument) + ); } /** @@ -86,10 +115,10 @@ function isValidRestSpreadPair(ctorParam, superArg) { * @returns {boolean} `true` if the nodes have the same value or not. */ function isValidPair(ctorParam, superArg) { - return ( - isValidIdentifierPair(ctorParam, superArg) || - isValidRestSpreadPair(ctorParam, superArg) - ); + return ( + isValidIdentifierPair(ctorParam, superArg) || + isValidRestSpreadPair(ctorParam, superArg) + ); } /** @@ -100,17 +129,17 @@ function isValidPair(ctorParam, superArg) { * @returns {boolean} `true` if those have the same values. */ function isPassingThrough(ctorParams, superArgs) { - if (ctorParams.length !== superArgs.length) { - return false; - } + if (ctorParams.length !== superArgs.length) { + return false; + } - for (let i = 0; i < ctorParams.length; ++i) { - if (!isValidPair(ctorParams[i], superArgs[i])) { - return false; - } - } + for (let i = 0; i < ctorParams.length; ++i) { + if (!isValidPair(ctorParams[i], superArgs[i])) { + return false; + } + } - return true; + return true; } /** @@ -120,14 +149,12 @@ function isPassingThrough(ctorParams, superArgs) { * @returns {boolean} true if the constructor body is redundant */ function isRedundantSuperCall(body, ctorParams) { - return ( - isSingleSuperCall(body) && - ctorParams.every(isSimple) && - ( - isSpreadArguments(body[0].expression.arguments) || - isPassingThrough(ctorParams, body[0].expression.arguments) - ) - ); + return ( + isSingleSuperCall(body) && + ctorParams.every(isSimple) && + (isSpreadArguments(body[0].expression.arguments) || + isPassingThrough(ctorParams, body[0].expression.arguments)) + ); } //------------------------------------------------------------------------------ @@ -136,70 +163,90 @@ function isRedundantSuperCall(body, ctorParams) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary constructors", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-useless-constructor" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - noUselessConstructor: "Useless constructor.", - removeConstructor: "Remove the constructor." - } - }, - - create(context) { - - /** - * Checks whether a node is a redundant constructor - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkForConstructor(node) { - if (node.kind !== "constructor") { - return; - } - - /* - * Prevent crashing on parsers which do not require class constructor - * to have a body, e.g. typescript and flow - */ - if (!node.value.body) { - return; - } - - const body = node.value.body.body; - const ctorParams = node.value.params; - const superClass = node.parent.parent.superClass; - - if (superClass ? isRedundantSuperCall(body, ctorParams) : (body.length === 0)) { - context.report({ - node, - messageId: "noUselessConstructor", - suggest: [ - { - messageId: "removeConstructor", - *fix(fixer) { - const nextToken = context.sourceCode.getTokenAfter(node); - const addSemiColon = nextToken.type === "Punctuator" && nextToken.value === "[" && astUtils.needsPrecedingSemicolon(context.sourceCode, node); - - yield fixer.replaceText(node, addSemiColon ? ";" : ""); - } - } - ] - }); - } - } - - return { - MethodDefinition: checkForConstructor - }; - } + meta: { + 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, + + schema: [], + + messages: { + noUselessConstructor: "Useless constructor.", + removeConstructor: "Remove the constructor.", + }, + }, + + create(context) { + /** + * Checks whether a node is a redundant constructor + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkForConstructor(node) { + if ( + node.kind !== "constructor" || + node.value.type !== "FunctionExpression" || + hasDecoratorsOrParameterProperty(node) || + hasUsefulAccessibility(node) + ) { + return; + } + + /* + * Prevent crashing on parsers which do not require class constructor + * to have a body, e.g. typescript and flow + */ + if (!node.value.body) { + return; + } + + const body = node.value.body.body; + const ctorParams = node.value.params; + const superClass = node.parent.parent.superClass; + + if ( + superClass + ? isRedundantSuperCall(body, ctorParams) + : body.length === 0 + ) { + context.report({ + node, + messageId: "noUselessConstructor", + suggest: [ + { + messageId: "removeConstructor", + *fix(fixer) { + const nextToken = + context.sourceCode.getTokenAfter(node); + const addSemiColon = + nextToken.type === "Punctuator" && + nextToken.value === "[" && + astUtils.needsPrecedingSemicolon( + context.sourceCode, + node, + ); + + yield fixer.replaceText( + node, + addSemiColon ? ";" : "", + ); + }, + }, + ], + }); + } + } + + return { + MethodDefinition: checkForConstructor, + }; + }, }; diff --git a/lib/rules/no-useless-escape.js b/lib/rules/no-useless-escape.js index 0e0f6f09f2c3..545419912d4f 100644 --- a/lib/rules/no-useless-escape.js +++ b/lib/rules/no-useless-escape.js @@ -23,311 +23,362 @@ const { RegExpParser, visitRegExpAST } = require("@eslint-community/regexpp"); * @returns {Set} The union of the two sets */ function union(setA, setB) { - return new Set(function *() { - yield* setA; - yield* setB; - }()); + return new Set( + (function* () { + yield* setA; + yield* setB; + })(), + ); } const VALID_STRING_ESCAPES = union(new Set("\\nrvtbfux"), astUtils.LINEBREAKS); const REGEX_GENERAL_ESCAPES = new Set("\\bcdDfnpPrsStvwWxu0123456789]"); -const REGEX_NON_CHARCLASS_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("^/.$*+?[{}|()Bk")); +const REGEX_NON_CHARCLASS_ESCAPES = union( + REGEX_GENERAL_ESCAPES, + new Set("^/.$*+?[{}|()Bk"), +); /* * Set of characters that require escaping in character classes in `unicodeSets` mode. * ( ) [ ] { } / - \ | are ClassSetSyntaxCharacter */ -const REGEX_CLASSSET_CHARACTER_ESCAPES = union(REGEX_GENERAL_ESCAPES, new Set("q/[{}|()-")); +const REGEX_CLASSSET_CHARACTER_ESCAPES = union( + REGEX_GENERAL_ESCAPES, + new Set("q/[{}|()-"), +); /* * A single character set of ClassSetReservedDoublePunctuator. * && !! ## $$ %% ** ++ ,, .. :: ;; << == >> ?? @@ ^^ `` ~~ are ClassSetReservedDoublePunctuator */ -const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set("!#$%&*+,.:;<=>?@^`~"); +const REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR = new Set( + "!#$%&*+,.:;<=>?@^`~", +); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow unnecessary escape characters", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-useless-escape" - }, - - hasSuggestions: true, - - messages: { - unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", - removeEscape: "Remove the `\\`. This maintains the current functionality.", - removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.", - escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character." - }, - - schema: [] - }, - - create(context) { - const sourceCode = context.sourceCode; - const parser = new RegExpParser(); - - /** - * Reports a node - * @param {ASTNode} node The node to report - * @param {number} startOffset The backslash's offset from the start of the node - * @param {string} character The uselessly escaped character (not including the backslash) - * @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off. - * @returns {void} - */ - function report(node, startOffset, character, disableEscapeBackslashSuggest) { - const rangeStart = node.range[0] + startOffset; - const range = [rangeStart, rangeStart + 1]; - const start = sourceCode.getLocFromIndex(rangeStart); - - context.report({ - node, - loc: { - start, - end: { line: start.line, column: start.column + 1 } - }, - messageId: "unnecessaryEscape", - data: { character }, - suggest: [ - { - - // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality. - messageId: astUtils.isDirective(node.parent) - ? "removeEscapeDoNotKeepSemantics" : "removeEscape", - fix(fixer) { - return fixer.removeRange(range); - } - }, - ...disableEscapeBackslashSuggest - ? [] - : [ - { - messageId: "escapeBackslash", - fix(fixer) { - return fixer.insertTextBeforeRange(range, "\\"); - } - } - ] - ] - }); - } - - /** - * Checks if the escape character in given string slice is unnecessary. - * @private - * @param {ASTNode} node node to validate. - * @param {string} match string slice to validate. - * @returns {void} - */ - function validateString(node, match) { - const isTemplateElement = node.type === "TemplateElement"; - const escapedChar = match[0][1]; - let isUnnecessaryEscape = !VALID_STRING_ESCAPES.has(escapedChar); - let isQuoteEscape; - - if (isTemplateElement) { - isQuoteEscape = escapedChar === "`"; - - if (escapedChar === "$") { - - // Warn if `\$` is not followed by `{` - isUnnecessaryEscape = match.input[match.index + 2] !== "{"; - } else if (escapedChar === "{") { - - /* - * Warn if `\{` is not preceded by `$`. If preceded by `$`, escaping - * is necessary and the rule should not warn. If preceded by `/$`, the rule - * will warn for the `/$` instead, as it is the first unnecessarily escaped character. - */ - isUnnecessaryEscape = match.input[match.index - 1] !== "$"; - } - } else { - isQuoteEscape = escapedChar === node.raw[0]; - } - - if (isUnnecessaryEscape && !isQuoteEscape) { - report(node, match.index, match[0].slice(1)); - } - } - - /** - * Checks if the escape character in given regexp is unnecessary. - * @private - * @param {ASTNode} node node to validate. - * @returns {void} - */ - function validateRegExp(node) { - const { pattern, flags } = node.regex; - let patternNode; - const unicode = flags.includes("u"); - const unicodeSets = flags.includes("v"); - - try { - patternNode = parser.parsePattern(pattern, 0, pattern.length, { unicode, unicodeSets }); - } catch { - - // Ignore regular expressions with syntax errors - return; - } - - /** @type {(CharacterClass | ExpressionCharacterClass)[]} */ - const characterClassStack = []; - - visitRegExpAST(patternNode, { - onCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), - onCharacterClassLeave: () => characterClassStack.shift(), - onExpressionCharacterClassEnter: characterClassNode => characterClassStack.unshift(characterClassNode), - onExpressionCharacterClassLeave: () => characterClassStack.shift(), - onCharacterEnter(characterNode) { - if (!characterNode.raw.startsWith("\\")) { - - // It's not an escaped character. - return; - } - - const escapedChar = characterNode.raw.slice(1); - - if (escapedChar !== String.fromCodePoint(characterNode.value)) { - - // It's a valid escape. - return; - } - let allowedEscapes; - - if (characterClassStack.length) { - allowedEscapes = unicodeSets ? REGEX_CLASSSET_CHARACTER_ESCAPES : REGEX_GENERAL_ESCAPES; - } else { - allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; - } - if (allowedEscapes.has(escapedChar)) { - return; - } - - const reportedIndex = characterNode.start + 1; - let disableEscapeBackslashSuggest = false; - - if (characterClassStack.length) { - const characterClassNode = characterClassStack[0]; - - if (escapedChar === "^") { - - /* - * The '^' character is also a special case; it must always be escaped outside of character classes, but - * it only needs to be escaped in character classes if it's at the beginning of the character class. To - * account for this, consider it to be a valid escape character outside of character classes, and filter - * out '^' characters that appear at the start of a character class. - */ - if (characterClassNode.start + 1 === characterNode.start) { - - return; - } - } - if (!unicodeSets) { - if (escapedChar === "-") { - - /* - * The '-' character is a special case, because it's only valid to escape it if it's in a character - * class, and is not at either edge of the character class. To account for this, don't consider '-' - * characters to be valid in general, and filter out '-' characters that appear in the middle of a - * character class. - */ - if (characterClassNode.start + 1 !== characterNode.start && characterNode.end !== characterClassNode.end - 1) { - - return; - } - } - } else { // unicodeSets mode - if (REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has(escapedChar)) { - - // Escaping is valid if it is a ClassSetReservedDoublePunctuator. - if (pattern[characterNode.end] === escapedChar) { - return; - } - if (pattern[characterNode.start - 1] === escapedChar) { - if (escapedChar !== "^") { - return; - } - - // If the previous character is a `negate` caret(`^`), escape to caret is unnecessary. - - if (!characterClassNode.negate) { - return; - } - const negateCaretIndex = characterClassNode.start + 1; - - if (negateCaretIndex < characterNode.start - 1) { - return; - } - } - } - - if (characterNode.parent.type === "ClassIntersection" || characterNode.parent.type === "ClassSubtraction") { - disableEscapeBackslashSuggest = true; - } - } - } - - report( - node, - reportedIndex, - escapedChar, - disableEscapeBackslashSuggest - ); - } - }); - } - - /** - * Checks if a node has an escape. - * @param {ASTNode} node node to check. - * @returns {void} - */ - function check(node) { - const isTemplateElement = node.type === "TemplateElement"; - - if ( - isTemplateElement && - node.parent && - node.parent.parent && - node.parent.parent.type === "TaggedTemplateExpression" && - node.parent === node.parent.parent.quasi - ) { - - // Don't report tagged template literals, because the backslash character is accessible to the tag function. - return; - } - - if (typeof node.value === "string" || isTemplateElement) { - - /* - * JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/. - * In addition, backticks are not supported by JSX yet: https://github.com/facebook/jsx/issues/25. - */ - if (node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment") { - return; - } - - const value = isTemplateElement ? sourceCode.getText(node) : node.raw; - const pattern = /\\[^\d]/gu; - let match; - - while ((match = pattern.exec(value))) { - validateString(node, match); - } - } else if (node.regex) { - validateRegExp(node); - } - - } - - return { - Literal: check, - TemplateElement: check - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow unnecessary escape characters", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-useless-escape", + }, + + hasSuggestions: true, + + messages: { + unnecessaryEscape: "Unnecessary escape character: \\{{character}}.", + removeEscape: + "Remove the `\\`. This maintains the current functionality.", + removeEscapeDoNotKeepSemantics: + "Remove the `\\` if it was inserted by mistake.", + escapeBackslash: + "Replace the `\\` with `\\\\` to include the actual backslash character.", + }, + + schema: [], + }, + + create(context) { + const sourceCode = context.sourceCode; + const parser = new RegExpParser(); + + /** + * Reports a node + * @param {ASTNode} node The node to report + * @param {number} startOffset The backslash's offset from the start of the node + * @param {string} character The uselessly escaped character (not including the backslash) + * @param {boolean} [disableEscapeBackslashSuggest] `true` if escapeBackslash suggestion should be turned off. + * @returns {void} + */ + function report( + node, + startOffset, + character, + disableEscapeBackslashSuggest, + ) { + const rangeStart = node.range[0] + startOffset; + const range = [rangeStart, rangeStart + 1]; + const start = sourceCode.getLocFromIndex(rangeStart); + + context.report({ + node, + loc: { + start, + end: { line: start.line, column: start.column + 1 }, + }, + messageId: "unnecessaryEscape", + data: { character }, + suggest: [ + { + // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality. + messageId: astUtils.isDirective(node.parent) + ? "removeEscapeDoNotKeepSemantics" + : "removeEscape", + fix(fixer) { + return fixer.removeRange(range); + }, + }, + ...(disableEscapeBackslashSuggest + ? [] + : [ + { + messageId: "escapeBackslash", + fix(fixer) { + return fixer.insertTextBeforeRange( + range, + "\\", + ); + }, + }, + ]), + ], + }); + } + + /** + * Checks if the escape character in given string slice is unnecessary. + * @private + * @param {ASTNode} node node to validate. + * @param {string} match string slice to validate. + * @returns {void} + */ + function validateString(node, match) { + const isTemplateElement = node.type === "TemplateElement"; + const escapedChar = match[0][1]; + let isUnnecessaryEscape = !VALID_STRING_ESCAPES.has(escapedChar); + let isQuoteEscape; + + if (isTemplateElement) { + isQuoteEscape = escapedChar === "`"; + + if (escapedChar === "$") { + // Warn if `\$` is not followed by `{` + isUnnecessaryEscape = match.input[match.index + 2] !== "{"; + } else if (escapedChar === "{") { + /* + * Warn if `\{` is not preceded by `$`. If preceded by `$`, escaping + * is necessary and the rule should not warn. If preceded by `/$`, the rule + * will warn for the `/$` instead, as it is the first unnecessarily escaped character. + */ + isUnnecessaryEscape = match.input[match.index - 1] !== "$"; + } + } else { + isQuoteEscape = escapedChar === node.raw[0]; + } + + if (isUnnecessaryEscape && !isQuoteEscape) { + report(node, match.index, match[0].slice(1)); + } + } + + /** + * Checks if the escape character in given regexp is unnecessary. + * @private + * @param {ASTNode} node node to validate. + * @returns {void} + */ + function validateRegExp(node) { + const { pattern, flags } = node.regex; + let patternNode; + const unicode = flags.includes("u"); + const unicodeSets = flags.includes("v"); + + try { + patternNode = parser.parsePattern(pattern, 0, pattern.length, { + unicode, + unicodeSets, + }); + } catch { + // Ignore regular expressions with syntax errors + return; + } + + /** @type {(CharacterClass | ExpressionCharacterClass)[]} */ + const characterClassStack = []; + + visitRegExpAST(patternNode, { + onCharacterClassEnter: characterClassNode => + characterClassStack.unshift(characterClassNode), + onCharacterClassLeave: () => characterClassStack.shift(), + onExpressionCharacterClassEnter: characterClassNode => + characterClassStack.unshift(characterClassNode), + onExpressionCharacterClassLeave: () => + characterClassStack.shift(), + onCharacterEnter(characterNode) { + if (!characterNode.raw.startsWith("\\")) { + // It's not an escaped character. + return; + } + + const escapedChar = characterNode.raw.slice(1); + + if ( + escapedChar !== + String.fromCodePoint(characterNode.value) + ) { + // It's a valid escape. + return; + } + let allowedEscapes; + + if (characterClassStack.length) { + allowedEscapes = unicodeSets + ? REGEX_CLASSSET_CHARACTER_ESCAPES + : REGEX_GENERAL_ESCAPES; + } else { + allowedEscapes = REGEX_NON_CHARCLASS_ESCAPES; + } + if (allowedEscapes.has(escapedChar)) { + return; + } + + const reportedIndex = characterNode.start + 1; + let disableEscapeBackslashSuggest = false; + + if (characterClassStack.length) { + const characterClassNode = characterClassStack[0]; + + if (escapedChar === "^") { + /* + * The '^' character is also a special case; it must always be escaped outside of character classes, but + * it only needs to be escaped in character classes if it's at the beginning of the character class. To + * account for this, consider it to be a valid escape character outside of character classes, and filter + * out '^' characters that appear at the start of a character class. + */ + if ( + characterClassNode.start + 1 === + characterNode.start + ) { + return; + } + } + if (!unicodeSets) { + if (escapedChar === "-") { + /* + * The '-' character is a special case, because it's only valid to escape it if it's in a character + * class, and is not at either edge of the character class. To account for this, don't consider '-' + * characters to be valid in general, and filter out '-' characters that appear in the middle of a + * character class. + */ + if ( + characterClassNode.start + 1 !== + characterNode.start && + characterNode.end !== + characterClassNode.end - 1 + ) { + return; + } + } + } else { + // unicodeSets mode + if ( + REGEX_CLASS_SET_RESERVED_DOUBLE_PUNCTUATOR.has( + escapedChar, + ) + ) { + // Escaping is valid if it is a ClassSetReservedDoublePunctuator. + if ( + pattern[characterNode.end] === escapedChar + ) { + return; + } + if ( + pattern[characterNode.start - 1] === + escapedChar + ) { + if (escapedChar !== "^") { + return; + } + + // If the previous character is a `negate` caret(`^`), escape to caret is unnecessary. + + if (!characterClassNode.negate) { + return; + } + const negateCaretIndex = + characterClassNode.start + 1; + + if ( + negateCaretIndex < + characterNode.start - 1 + ) { + return; + } + } + } + + if ( + characterNode.parent.type === + "ClassIntersection" || + characterNode.parent.type === "ClassSubtraction" + ) { + disableEscapeBackslashSuggest = true; + } + } + } + + report( + node, + reportedIndex, + escapedChar, + disableEscapeBackslashSuggest, + ); + }, + }); + } + + /** + * Checks if a node has an escape. + * @param {ASTNode} node node to check. + * @returns {void} + */ + function check(node) { + const isTemplateElement = node.type === "TemplateElement"; + + if ( + isTemplateElement && + node.parent && + node.parent.parent && + node.parent.parent.type === "TaggedTemplateExpression" && + node.parent === node.parent.parent.quasi + ) { + // Don't report tagged template literals, because the backslash character is accessible to the tag function. + return; + } + + if (typeof node.value === "string" || isTemplateElement) { + /* + * JSXAttribute doesn't have any escape sequence: https://facebook.github.io/jsx/. + * In addition, backticks are not supported by JSX yet: https://github.com/facebook/jsx/issues/25. + */ + if ( + node.parent.type === "JSXAttribute" || + node.parent.type === "JSXElement" || + node.parent.type === "JSXFragment" + ) { + return; + } + + const value = isTemplateElement + ? sourceCode.getText(node) + : node.raw; + const pattern = /\\[^\d]/gu; + let match; + + while ((match = pattern.exec(value))) { + validateString(node, match); + } + } else if (node.regex) { + validateRegExp(node); + } + } + + return { + Literal: check, + TemplateElement: check, + }; + }, }; diff --git a/lib/rules/no-useless-rename.js b/lib/rules/no-useless-rename.js index 2bde8234f4eb..28815636d878 100644 --- a/lib/rules/no-useless-rename.js +++ b/lib/rules/no-useless-rename.js @@ -17,159 +17,186 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - ignoreDestructuring: false, - ignoreImport: false, - ignoreExport: false - }], - - docs: { - description: "Disallow renaming import, export, and destructured assignments to the same name", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-useless-rename" - }, - - fixable: "code", - - schema: [ - { - type: "object", - properties: { - ignoreDestructuring: { type: "boolean" }, - ignoreImport: { type: "boolean" }, - ignoreExport: { type: "boolean" } - }, - additionalProperties: false - } - ], - - messages: { - unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ ignoreDestructuring, ignoreImport, ignoreExport }] = context.options; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports error for unnecessarily renamed assignments - * @param {ASTNode} node node to report - * @param {ASTNode} initial node with initial name value - * @param {string} type the type of the offending node - * @returns {void} - */ - function reportError(node, initial, type) { - const name = initial.type === "Identifier" ? initial.name : initial.value; - - return context.report({ - node, - messageId: "unnecessarilyRenamed", - data: { - name, - type - }, - fix(fixer) { - const replacementNode = node.type === "Property" ? node.value : node.local; - - if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(replacementNode).length) { - return null; - } - - // Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties. - if ( - replacementNode.type === "AssignmentPattern" && - astUtils.isParenthesised(sourceCode, replacementNode.left) - ) { - return null; - } - - return fixer.replaceText(node, sourceCode.getText(replacementNode)); - } - }); - } - - /** - * Checks whether a destructured assignment is unnecessarily renamed - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkDestructured(node) { - if (ignoreDestructuring) { - return; - } - - for (const property of node.properties) { - - /** - * Properties using shorthand syntax and rest elements can not be renamed. - * If the property is computed, we have no idea if a rename is useless or not. - */ - if (property.type !== "Property" || property.shorthand || property.computed) { - continue; - } - - const key = (property.key.type === "Identifier" && property.key.name) || (property.key.type === "Literal" && property.key.value); - const renamedKey = property.value.type === "AssignmentPattern" ? property.value.left.name : property.value.name; - - if (key === renamedKey) { - reportError(property, property.key, "Destructuring assignment"); - } - } - } - - /** - * Checks whether an import is unnecessarily renamed - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkImport(node) { - if (ignoreImport) { - return; - } - - if ( - node.imported.range[0] !== node.local.range[0] && - astUtils.getModuleExportName(node.imported) === node.local.name - ) { - reportError(node, node.imported, "Import"); - } - } - - /** - * Checks whether an export is unnecessarily renamed - * @param {ASTNode} node node to check - * @returns {void} - */ - function checkExport(node) { - if (ignoreExport) { - return; - } - - if ( - node.local.range[0] !== node.exported.range[0] && - astUtils.getModuleExportName(node.local) === astUtils.getModuleExportName(node.exported) - ) { - reportError(node, node.local, "Export"); - } - - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - ObjectPattern: checkDestructured, - ImportSpecifier: checkImport, - ExportSpecifier: checkExport - }; - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + ignoreDestructuring: false, + ignoreImport: false, + ignoreExport: false, + }, + ], + + docs: { + description: + "Disallow renaming import, export, and destructured assignments to the same name", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-useless-rename", + }, + + fixable: "code", + + schema: [ + { + type: "object", + properties: { + ignoreDestructuring: { type: "boolean" }, + ignoreImport: { type: "boolean" }, + ignoreExport: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + + messages: { + unnecessarilyRenamed: "{{type}} {{name}} unnecessarily renamed.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ ignoreDestructuring, ignoreImport, ignoreExport }] = + context.options; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports error for unnecessarily renamed assignments + * @param {ASTNode} node node to report + * @param {ASTNode} initial node with initial name value + * @param {string} type the type of the offending node + * @returns {void} + */ + function reportError(node, initial, type) { + const name = + initial.type === "Identifier" ? initial.name : initial.value; + + return context.report({ + node, + messageId: "unnecessarilyRenamed", + data: { + name, + type, + }, + fix(fixer) { + const replacementNode = + node.type === "Property" ? node.value : node.local; + + if ( + sourceCode.getCommentsInside(node).length > + sourceCode.getCommentsInside(replacementNode).length + ) { + return null; + } + + // Don't autofix code such as `({foo: (foo) = a} = obj);`, parens are not allowed in shorthand properties. + if ( + replacementNode.type === "AssignmentPattern" && + astUtils.isParenthesised( + sourceCode, + replacementNode.left, + ) + ) { + return null; + } + + return fixer.replaceText( + node, + sourceCode.getText(replacementNode), + ); + }, + }); + } + + /** + * Checks whether a destructured assignment is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkDestructured(node) { + if (ignoreDestructuring) { + return; + } + + for (const property of node.properties) { + /** + * Properties using shorthand syntax and rest elements can not be renamed. + * If the property is computed, we have no idea if a rename is useless or not. + */ + if ( + property.type !== "Property" || + property.shorthand || + property.computed + ) { + continue; + } + + const key = + (property.key.type === "Identifier" && property.key.name) || + (property.key.type === "Literal" && property.key.value); + const renamedKey = + property.value.type === "AssignmentPattern" + ? property.value.left.name + : property.value.name; + + if (key === renamedKey) { + reportError( + property, + property.key, + "Destructuring assignment", + ); + } + } + } + + /** + * Checks whether an import is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkImport(node) { + if (ignoreImport) { + return; + } + + if ( + node.imported.range[0] !== node.local.range[0] && + astUtils.getModuleExportName(node.imported) === node.local.name + ) { + reportError(node, node.imported, "Import"); + } + } + + /** + * Checks whether an export is unnecessarily renamed + * @param {ASTNode} node node to check + * @returns {void} + */ + function checkExport(node) { + if (ignoreExport) { + return; + } + + if ( + node.local.range[0] !== node.exported.range[0] && + astUtils.getModuleExportName(node.local) === + astUtils.getModuleExportName(node.exported) + ) { + reportError(node, node.local, "Export"); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + ObjectPattern: checkDestructured, + ImportSpecifier: checkImport, + ExportSpecifier: checkExport, + }; + }, }; diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js index 1f85cdb9aafc..588e4ac49010 100644 --- a/lib/rules/no-useless-return.js +++ b/lib/rules/no-useless-return.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"), - FixTracker = require("./utils/fix-tracker"); + FixTracker = require("./utils/fix-tracker"); //------------------------------------------------------------------------------ // Helpers @@ -22,11 +22,11 @@ const astUtils = require("./utils/ast-utils"), * @returns {void} */ function remove(array, element) { - const index = array.indexOf(element); + const index = array.indexOf(element); - if (index !== -1) { - array.splice(index, 1); - } + if (index !== -1) { + array.splice(index, 1); + } } /** @@ -35,7 +35,7 @@ function remove(array, element) { * @returns {boolean} `true` if the node is removable. */ function isRemovable(node) { - return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type); + return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type); } /** @@ -44,17 +44,20 @@ function isRemovable(node) { * @returns {boolean} `true` if the node is in a `finally` block. */ function isInFinally(node) { - for ( - let currentNode = node; - currentNode && currentNode.parent && !astUtils.isFunction(currentNode); - currentNode = currentNode.parent - ) { - if (currentNode.parent.type === "TryStatement" && currentNode.parent.finalizer === currentNode) { - return true; - } - } - - return false; + for ( + let currentNode = node; + currentNode && currentNode.parent && !astUtils.isFunction(currentNode); + currentNode = currentNode.parent + ) { + if ( + currentNode.parent.type === "TryStatement" && + currentNode.parent.finalizer === currentNode + ) { + return true; + } + } + + return false; } /** @@ -63,14 +66,13 @@ function isInFinally(node) { * @returns {boolean} True if any segment is reachable; false otherwise. */ function isAnySegmentReachable(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; } //------------------------------------------------------------------------------ @@ -79,291 +81,321 @@ function isAnySegmentReachable(segments) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow redundant return statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-useless-return" - }, - - fixable: "code", - schema: [], - - messages: { - unnecessaryReturn: "Unnecessary return statement." - } - }, - - create(context) { - const segmentInfoMap = new WeakMap(); - const sourceCode = context.sourceCode; - let scopeInfo = null; - - /** - * Checks whether the given segment is terminated by a return statement or not. - * @param {CodePathSegment} segment The segment to check. - * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable. - */ - function isReturned(segment) { - const info = segmentInfoMap.get(segment); - - return !info || info.returned; - } - - /** - * Collects useless return statements from the given previous segments. - * - * A previous segment may be an unreachable segment. - * In that case, the information object of the unreachable segment is not - * initialized because `onCodePathSegmentStart` event is not notified for - * unreachable segments. - * This goes to the previous segments of the unreachable segment recursively - * if the unreachable segment was generated by a return statement. Otherwise, - * this ignores the unreachable segment. - * - * This behavior would simulate code paths for the case that the return - * statement does not exist. - * @param {ASTNode[]} uselessReturns The collected return statements. - * @param {CodePathSegment[]} prevSegments The previous segments to traverse. - * @param {WeakSet} [providedTraversedSegments] A set of segments that have already been traversed in this call - * @returns {ASTNode[]} `uselessReturns`. - */ - function getUselessReturns(uselessReturns, prevSegments, providedTraversedSegments) { - const traversedSegments = providedTraversedSegments || new WeakSet(); - - for (const segment of prevSegments) { - if (!segment.reachable) { - if (!traversedSegments.has(segment)) { - traversedSegments.add(segment); - getUselessReturns( - uselessReturns, - segment.allPrevSegments.filter(isReturned), - traversedSegments - ); - } - continue; - } - - if (segmentInfoMap.has(segment)) { - uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns); - } - } - - return uselessReturns; - } - - /** - * Removes the return statements on the given segment from the useless return - * statement list. - * - * This segment may be an unreachable segment. - * In that case, the information object of the unreachable segment is not - * initialized because `onCodePathSegmentStart` event is not notified for - * unreachable segments. - * This goes to the previous segments of the unreachable segment recursively - * if the unreachable segment was generated by a return statement. Otherwise, - * this ignores the unreachable segment. - * - * This behavior would simulate code paths for the case that the return - * statement does not exist. - * @param {CodePathSegment} segment The segment to get return statements. - * @param {Set} usedUnreachableSegments A set of segments that have already been traversed in this call. - * @returns {void} - */ - function markReturnStatementsOnSegmentAsUsed(segment, usedUnreachableSegments) { - if (!segment.reachable) { - usedUnreachableSegments.add(segment); - segment.allPrevSegments - .filter(isReturned) - .filter(prevSegment => !usedUnreachableSegments.has(prevSegment)) - .forEach(prevSegment => markReturnStatementsOnSegmentAsUsed(prevSegment, usedUnreachableSegments)); - return; - } - - const info = segmentInfoMap.get(segment); - - if (!info) { - return; - } - - info.uselessReturns = info.uselessReturns.filter(node => { - if (scopeInfo.traversedTryBlockStatements && scopeInfo.traversedTryBlockStatements.length > 0) { - const returnInitialRange = node.range[0]; - const returnFinalRange = node.range[1]; - - const areBlocksInRange = scopeInfo.traversedTryBlockStatements.some(tryBlockStatement => { - const blockInitialRange = tryBlockStatement.range[0]; - const blockFinalRange = tryBlockStatement.range[1]; - - return ( - returnInitialRange >= blockInitialRange && - returnFinalRange <= blockFinalRange - ); - }); - - if (areBlocksInRange) { - return true; - } - } - - remove(scopeInfo.uselessReturns, node); - return false; - }); - } - - /** - * Removes the return statements on the current segments from the useless - * return statement list. - * - * This function will be called at every statement except FunctionDeclaration, - * BlockStatement, and BreakStatement. - * - * - FunctionDeclarations are always executed whether it's returned or not. - * - BlockStatements do nothing. - * - BreakStatements go the next merely. - * @returns {void} - */ - function markReturnStatementsOnCurrentSegmentsAsUsed() { - scopeInfo - .currentSegments - .forEach(segment => markReturnStatementsOnSegmentAsUsed(segment, new Set())); - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - - // Makes and pushes a new scope information. - onCodePathStart(codePath) { - scopeInfo = { - upper: scopeInfo, - uselessReturns: [], - traversedTryBlockStatements: [], - codePath, - currentSegments: new Set() - }; - }, - - // Reports useless return statements if exist. - onCodePathEnd() { - for (const node of scopeInfo.uselessReturns) { - context.report({ - node, - loc: node.loc, - messageId: "unnecessaryReturn", - fix(fixer) { - if (isRemovable(node) && !sourceCode.getCommentsInside(node).length) { - - /* - * Extend the replacement range to include the - * entire function to avoid conflicting with - * no-else-return. - * https://github.com/eslint/eslint/issues/8026 - */ - return new FixTracker(fixer, sourceCode) - .retainEnclosingFunction(node) - .remove(node); - } - return null; - } - }); - } - - scopeInfo = scopeInfo.upper; - }, - - /* - * Initializes segments. - * NOTE: This event is notified for only reachable segments. - */ - onCodePathSegmentStart(segment) { - scopeInfo.currentSegments.add(segment); - - const info = { - uselessReturns: getUselessReturns([], segment.allPrevSegments), - returned: false - }; - - // Stores the info. - segmentInfoMap.set(segment, info); - }, - - onUnreachableCodePathSegmentStart(segment) { - scopeInfo.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - scopeInfo.currentSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - scopeInfo.currentSegments.delete(segment); - }, - - // Adds ReturnStatement node to check whether it's useless or not. - ReturnStatement(node) { - if (node.argument) { - markReturnStatementsOnCurrentSegmentsAsUsed(); - } - if ( - node.argument || - astUtils.isInLoop(node) || - isInFinally(node) || - - // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647). - !isAnySegmentReachable(scopeInfo.currentSegments) - ) { - return; - } - - for (const segment of scopeInfo.currentSegments) { - const info = segmentInfoMap.get(segment); - - if (info) { - info.uselessReturns.push(node); - info.returned = true; - } - } - scopeInfo.uselessReturns.push(node); - }, - - "TryStatement > BlockStatement.block:exit"(node) { - scopeInfo.traversedTryBlockStatements.push(node); - }, - - "TryStatement:exit"() { - scopeInfo.traversedTryBlockStatements.pop(); - }, - - /* - * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement. - * Removes return statements of the current segments from the useless return statement list. - */ - ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, - ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, - LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, - WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed, - ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, - ExportDefaultDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, - ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow redundant return statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-useless-return", + }, + + fixable: "code", + schema: [], + + messages: { + unnecessaryReturn: "Unnecessary return statement.", + }, + }, + + create(context) { + const segmentInfoMap = new WeakMap(); + const sourceCode = context.sourceCode; + let scopeInfo = null; + + /** + * Checks whether the given segment is terminated by a return statement or not. + * @param {CodePathSegment} segment The segment to check. + * @returns {boolean} `true` if the segment is terminated by a return statement, or if it's still a part of unreachable. + */ + function isReturned(segment) { + const info = segmentInfoMap.get(segment); + + return !info || info.returned; + } + + /** + * Collects useless return statements from the given previous segments. + * + * A previous segment may be an unreachable segment. + * In that case, the information object of the unreachable segment is not + * initialized because `onCodePathSegmentStart` event is not notified for + * unreachable segments. + * This goes to the previous segments of the unreachable segment recursively + * if the unreachable segment was generated by a return statement. Otherwise, + * this ignores the unreachable segment. + * + * This behavior would simulate code paths for the case that the return + * statement does not exist. + * @param {ASTNode[]} uselessReturns The collected return statements. + * @param {CodePathSegment[]} prevSegments The previous segments to traverse. + * @param {WeakSet} [providedTraversedSegments] A set of segments that have already been traversed in this call + * @returns {ASTNode[]} `uselessReturns`. + */ + function getUselessReturns( + uselessReturns, + prevSegments, + providedTraversedSegments, + ) { + const traversedSegments = + providedTraversedSegments || new WeakSet(); + + for (const segment of prevSegments) { + if (!segment.reachable) { + if (!traversedSegments.has(segment)) { + traversedSegments.add(segment); + getUselessReturns( + uselessReturns, + segment.allPrevSegments.filter(isReturned), + traversedSegments, + ); + } + continue; + } + + if (segmentInfoMap.has(segment)) { + uselessReturns.push( + ...segmentInfoMap.get(segment).uselessReturns, + ); + } + } + + return uselessReturns; + } + + /** + * Removes the return statements on the given segment from the useless return + * statement list. + * + * This segment may be an unreachable segment. + * In that case, the information object of the unreachable segment is not + * initialized because `onCodePathSegmentStart` event is not notified for + * unreachable segments. + * This goes to the previous segments of the unreachable segment recursively + * if the unreachable segment was generated by a return statement. Otherwise, + * this ignores the unreachable segment. + * + * This behavior would simulate code paths for the case that the return + * statement does not exist. + * @param {CodePathSegment} segment The segment to get return statements. + * @param {Set} usedUnreachableSegments A set of segments that have already been traversed in this call. + * @returns {void} + */ + function markReturnStatementsOnSegmentAsUsed( + segment, + usedUnreachableSegments, + ) { + if (!segment.reachable) { + usedUnreachableSegments.add(segment); + segment.allPrevSegments + .filter(isReturned) + .filter( + prevSegment => + !usedUnreachableSegments.has(prevSegment), + ) + .forEach(prevSegment => + markReturnStatementsOnSegmentAsUsed( + prevSegment, + usedUnreachableSegments, + ), + ); + return; + } + + const info = segmentInfoMap.get(segment); + + if (!info) { + return; + } + + info.uselessReturns = info.uselessReturns.filter(node => { + if ( + scopeInfo.traversedTryBlockStatements && + scopeInfo.traversedTryBlockStatements.length > 0 + ) { + const returnInitialRange = node.range[0]; + const returnFinalRange = node.range[1]; + + const areBlocksInRange = + scopeInfo.traversedTryBlockStatements.some( + tryBlockStatement => { + const blockInitialRange = + tryBlockStatement.range[0]; + const blockFinalRange = + tryBlockStatement.range[1]; + + return ( + returnInitialRange >= blockInitialRange && + returnFinalRange <= blockFinalRange + ); + }, + ); + + if (areBlocksInRange) { + return true; + } + } + + remove(scopeInfo.uselessReturns, node); + return false; + }); + } + + /** + * Removes the return statements on the current segments from the useless + * return statement list. + * + * This function will be called at every statement except FunctionDeclaration, + * BlockStatement, and BreakStatement. + * + * - FunctionDeclarations are always executed whether it's returned or not. + * - BlockStatements do nothing. + * - BreakStatements go the next merely. + * @returns {void} + */ + function markReturnStatementsOnCurrentSegmentsAsUsed() { + scopeInfo.currentSegments.forEach(segment => + markReturnStatementsOnSegmentAsUsed(segment, new Set()), + ); + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + // Makes and pushes a new scope information. + onCodePathStart(codePath) { + scopeInfo = { + upper: scopeInfo, + uselessReturns: [], + traversedTryBlockStatements: [], + codePath, + currentSegments: new Set(), + }; + }, + + // Reports useless return statements if exist. + onCodePathEnd() { + for (const node of scopeInfo.uselessReturns) { + context.report({ + node, + loc: node.loc, + messageId: "unnecessaryReturn", + fix(fixer) { + if ( + isRemovable(node) && + !sourceCode.getCommentsInside(node).length + ) { + /* + * Extend the replacement range to include the + * entire function to avoid conflicting with + * no-else-return. + * https://github.com/eslint/eslint/issues/8026 + */ + return new FixTracker(fixer, sourceCode) + .retainEnclosingFunction(node) + .remove(node); + } + return null; + }, + }); + } + + scopeInfo = scopeInfo.upper; + }, + + /* + * Initializes segments. + * NOTE: This event is notified for only reachable segments. + */ + onCodePathSegmentStart(segment) { + scopeInfo.currentSegments.add(segment); + + const info = { + uselessReturns: getUselessReturns( + [], + segment.allPrevSegments, + ), + returned: false, + }; + + // Stores the info. + segmentInfoMap.set(segment, info); + }, + + onUnreachableCodePathSegmentStart(segment) { + scopeInfo.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + scopeInfo.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + scopeInfo.currentSegments.delete(segment); + }, + + // Adds ReturnStatement node to check whether it's useless or not. + ReturnStatement(node) { + if (node.argument) { + markReturnStatementsOnCurrentSegmentsAsUsed(); + } + if ( + node.argument || + astUtils.isInLoop(node) || + isInFinally(node) || + // Ignore `return` statements in unreachable places (https://github.com/eslint/eslint/issues/11647). + !isAnySegmentReachable(scopeInfo.currentSegments) + ) { + return; + } + + for (const segment of scopeInfo.currentSegments) { + const info = segmentInfoMap.get(segment); + + if (info) { + info.uselessReturns.push(node); + info.returned = true; + } + } + scopeInfo.uselessReturns.push(node); + }, + + "TryStatement > BlockStatement.block:exit"(node) { + scopeInfo.traversedTryBlockStatements.push(node); + }, + + "TryStatement:exit"() { + scopeInfo.traversedTryBlockStatements.pop(); + }, + + /* + * Registers for all statement nodes except FunctionDeclaration, BlockStatement, BreakStatement. + * Removes return statements of the current segments from the useless return statement list. + */ + ClassDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + ContinueStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + DebuggerStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + DoWhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + EmptyStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ExpressionStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForInStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForOfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ForStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + IfStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ImportDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + LabeledStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + SwitchStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ThrowStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + TryStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + VariableDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + WhileStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + WithStatement: markReturnStatementsOnCurrentSegmentsAsUsed, + ExportNamedDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + ExportDefaultDeclaration: + markReturnStatementsOnCurrentSegmentsAsUsed, + ExportAllDeclaration: markReturnStatementsOnCurrentSegmentsAsUsed, + }; + }, }; diff --git a/lib/rules/no-var.js b/lib/rules/no-var.js index d45a91a1de81..d34e4d02a4cc 100644 --- a/lib/rules/no-var.js +++ b/lib/rules/no-var.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the variable is a global variable. */ function isGlobal(variable) { - return Boolean(variable.scope) && variable.scope.type === "global"; + return Boolean(variable.scope) && variable.scope.type === "global"; } /** @@ -32,12 +32,12 @@ function isGlobal(variable) { * scope. */ function getEnclosingFunctionScope(scope) { - let currentScope = scope; + let currentScope = scope; - while (currentScope.type !== "function" && currentScope.type !== "global") { - currentScope = currentScope.upper; - } - return currentScope; + while (currentScope.type !== "function" && currentScope.type !== "global") { + currentScope = currentScope.upper; + } + return currentScope; } /** @@ -47,10 +47,13 @@ function getEnclosingFunctionScope(scope) { * @returns {boolean} `true` if the variable is used from a closure. */ function isReferencedInClosure(variable) { - const enclosingFunctionScope = getEnclosingFunctionScope(variable.scope); + const enclosingFunctionScope = getEnclosingFunctionScope(variable.scope); - return variable.references.some(reference => - getEnclosingFunctionScope(reference.from) !== enclosingFunctionScope); + return variable.references.some( + reference => + getEnclosingFunctionScope(reference.from) !== + enclosingFunctionScope, + ); } /** @@ -60,8 +63,11 @@ function isReferencedInClosure(variable) { * iteration. */ function isLoopAssignee(node) { - return (node.parent.type === "ForOfStatement" || node.parent.type === "ForInStatement") && - node === node.parent.left; + return ( + (node.parent.type === "ForOfStatement" || + node.parent.type === "ForInStatement") && + node === node.parent.left + ); } /** @@ -70,10 +76,11 @@ function isLoopAssignee(node) { * @returns {boolean} `true` if the declaration has an initializer. */ function isDeclarationInitialized(node) { - return node.declarations.every(declarator => declarator.init !== null); + return node.declarations.every(declarator => declarator.init !== null); } -const SCOPE_NODE_TYPE = /^(?:Program|BlockStatement|SwitchStatement|ForStatement|ForInStatement|ForOfStatement)$/u; +const SCOPE_NODE_TYPE = + /^(?:Program|BlockStatement|SwitchStatement|ForStatement|ForInStatement|ForOfStatement)$/u; /** * Gets the scope node which directly contains a given node. @@ -84,14 +91,18 @@ const SCOPE_NODE_TYPE = /^(?:Program|BlockStatement|SwitchStatement|ForStatement * `ForOfStatement`. */ function getScopeNode(node) { - for (let currentNode = node; currentNode; currentNode = currentNode.parent) { - if (SCOPE_NODE_TYPE.test(currentNode.type)) { - return currentNode; - } - } - - /* c8 ignore next */ - return null; + for ( + let currentNode = node; + currentNode; + currentNode = currentNode.parent + ) { + if (SCOPE_NODE_TYPE.test(currentNode.type)) { + return currentNode; + } + } + + /* c8 ignore next */ + return null; } /** @@ -100,7 +111,7 @@ function getScopeNode(node) { * @returns {boolean} `true` if the variable is redeclared. */ function isRedeclared(variable) { - return variable.defs.length >= 2; + return variable.defs.length >= 2; } /** @@ -110,23 +121,22 @@ function isRedeclared(variable) { * variable is used from outside of the specified scope. */ function isUsedFromOutsideOf(scopeNode) { - - /** - * Checks whether a given reference is inside of the specified scope or not. - * @param {eslint-scope.Reference} reference A reference to check. - * @returns {boolean} `true` if the reference is inside of the specified - * scope. - */ - function isOutsideOfScope(reference) { - const scope = scopeNode.range; - const id = reference.identifier.range; - - return id[0] < scope[0] || id[1] > scope[1]; - } - - return function(variable) { - return variable.references.some(isOutsideOfScope); - }; + /** + * Checks whether a given reference is inside of the specified scope or not. + * @param {eslint-scope.Reference} reference A reference to check. + * @returns {boolean} `true` if the reference is inside of the specified + * scope. + */ + function isOutsideOfScope(reference) { + const scope = scopeNode.range; + const id = reference.identifier.range; + + return id[0] < scope[0] || id[1] > scope[1]; + } + + return function (variable) { + return variable.references.some(isOutsideOfScope); + }; } /** @@ -142,27 +152,33 @@ function isUsedFromOutsideOf(scopeNode) { * @private */ function hasReferenceInTDZ(node) { - const initStart = node.range[0]; - const initEnd = node.range[1]; - - return variable => { - const id = variable.defs[0].name; - const idStart = id.range[0]; - const defaultValue = (id.parent.type === "AssignmentPattern" ? id.parent.right : null); - const defaultStart = defaultValue && defaultValue.range[0]; - const defaultEnd = defaultValue && defaultValue.range[1]; - - return variable.references.some(reference => { - const start = reference.identifier.range[0]; - const end = reference.identifier.range[1]; - - return !reference.init && ( - start < idStart || - (defaultValue !== null && start >= defaultStart && end <= defaultEnd) || - (!astUtils.isFunction(node) && start >= initStart && end <= initEnd) - ); - }); - }; + const initStart = node.range[0]; + const initEnd = node.range[1]; + + return variable => { + const id = variable.defs[0].name; + const idStart = id.range[0]; + const defaultValue = + id.parent.type === "AssignmentPattern" ? id.parent.right : null; + const defaultStart = defaultValue && defaultValue.range[0]; + const defaultEnd = defaultValue && defaultValue.range[1]; + + return variable.references.some(reference => { + const start = reference.identifier.range[0]; + const end = reference.identifier.range[1]; + + return ( + !reference.init && + (start < idStart || + (defaultValue !== null && + start >= defaultStart && + end <= defaultEnd) || + (!astUtils.isFunction(node) && + start >= initStart && + end <= initEnd)) + ); + }); + }; } /** @@ -172,7 +188,7 @@ function hasReferenceInTDZ(node) { * @returns {boolean} `true` if the variable has a disallowed name. */ function hasNameDisallowedForLetDeclarations(variable) { - return variable.name === "let"; + return variable.name === "let"; } //------------------------------------------------------------------------------ @@ -181,154 +197,159 @@ function hasNameDisallowedForLetDeclarations(variable) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require `let` or `const` instead of `var`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-var" - }, - - schema: [], - fixable: "code", - - messages: { - unexpectedVar: "Unexpected var, use let or const instead." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Checks whether the variables which are defined by the given declarator node have their references in TDZ. - * @param {ASTNode} declarator The VariableDeclarator node to check. - * @returns {boolean} `true` if one of the variables which are defined by the given declarator node have their references in TDZ. - */ - function hasSelfReferenceInTDZ(declarator) { - if (!declarator.init) { - return false; - } - const variables = sourceCode.getDeclaredVariables(declarator); - - return variables.some(hasReferenceInTDZ(declarator.init)); - } - - /** - * Checks whether it can fix a given variable declaration or not. - * It cannot fix if the following cases: - * - * - A variable is a global variable. - * - A variable is declared on a SwitchCase node. - * - A variable is redeclared. - * - A variable is used from outside the scope. - * - A variable is used from a closure within a loop. - * - A variable might be used before it is assigned within a loop. - * - A variable might be used in TDZ. - * - A variable is declared in statement position (e.g. a single-line `IfStatement`) - * - A variable has name that is disallowed for `let` declarations. - * - * ## A variable is declared on a SwitchCase node. - * - * If this rule modifies 'var' declarations on a SwitchCase node, it - * would generate the warnings of 'no-case-declarations' rule. And the - * 'eslint:recommended' preset includes 'no-case-declarations' rule, so - * this rule doesn't modify those declarations. - * - * ## A variable is redeclared. - * - * The language spec disallows redeclarations of `let` declarations. - * Those variables would cause syntax errors. - * - * ## A variable is used from outside the scope. - * - * The language spec disallows accesses from outside of the scope for - * `let` declarations. Those variables would cause reference errors. - * - * ## A variable is used from a closure within a loop. - * - * A `var` declaration within a loop shares the same variable instance - * across all loop iterations, while a `let` declaration creates a new - * instance for each iteration. This means if a variable in a loop is - * referenced by any closure, changing it from `var` to `let` would - * change the behavior in a way that is generally unsafe. - * - * ## A variable might be used before it is assigned within a loop. - * - * Within a loop, a `let` declaration without an initializer will be - * initialized to null, while a `var` declaration will retain its value - * from the previous iteration, so it is only safe to change `var` to - * `let` if we can statically determine that the variable is always - * assigned a value before its first access in the loop body. To keep - * the implementation simple, we only convert `var` to `let` within - * loops when the variable is a loop assignee or the declaration has an - * initializer. - * @param {ASTNode} node A variable declaration node to check. - * @returns {boolean} `true` if it can fix the node. - */ - function canFix(node) { - const variables = sourceCode.getDeclaredVariables(node); - const scopeNode = getScopeNode(node); - - if (node.parent.type === "SwitchCase" || - node.declarations.some(hasSelfReferenceInTDZ) || - variables.some(isGlobal) || - variables.some(isRedeclared) || - variables.some(isUsedFromOutsideOf(scopeNode)) || - variables.some(hasNameDisallowedForLetDeclarations) - ) { - return false; - } - - if (astUtils.isInLoop(node)) { - if (variables.some(isReferencedInClosure)) { - return false; - } - if (!isLoopAssignee(node) && !isDeclarationInitialized(node)) { - return false; - } - } - - if ( - !isLoopAssignee(node) && - !(node.parent.type === "ForStatement" && node.parent.init === node) && - !astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type) - ) { - - // If the declaration is not in a block, e.g. `if (foo) var bar = 1;`, then it can't be fixed. - return false; - } - - return true; - } - - /** - * Reports a given variable declaration node. - * @param {ASTNode} node A variable declaration node to report. - * @returns {void} - */ - function report(node) { - context.report({ - node, - messageId: "unexpectedVar", - - fix(fixer) { - const varToken = sourceCode.getFirstToken(node, { filter: t => t.value === "var" }); - - return canFix(node) - ? fixer.replaceText(varToken, "let") - : null; - } - }); - } - - return { - "VariableDeclaration:exit"(node) { - if (node.kind === "var") { - report(node); - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Require `let` or `const` instead of `var`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-var", + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedVar: "Unexpected var, use let or const instead.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Checks whether the variables which are defined by the given declarator node have their references in TDZ. + * @param {ASTNode} declarator The VariableDeclarator node to check. + * @returns {boolean} `true` if one of the variables which are defined by the given declarator node have their references in TDZ. + */ + function hasSelfReferenceInTDZ(declarator) { + if (!declarator.init) { + return false; + } + const variables = sourceCode.getDeclaredVariables(declarator); + + return variables.some(hasReferenceInTDZ(declarator.init)); + } + + /** + * Checks whether it can fix a given variable declaration or not. + * It cannot fix if the following cases: + * + * - A variable is a global variable. + * - A variable is declared on a SwitchCase node. + * - A variable is redeclared. + * - A variable is used from outside the scope. + * - A variable is used from a closure within a loop. + * - A variable might be used before it is assigned within a loop. + * - A variable might be used in TDZ. + * - A variable is declared in statement position (e.g. a single-line `IfStatement`) + * - A variable has name that is disallowed for `let` declarations. + * + * ## A variable is declared on a SwitchCase node. + * + * If this rule modifies 'var' declarations on a SwitchCase node, it + * would generate the warnings of 'no-case-declarations' rule. And the + * 'eslint:recommended' preset includes 'no-case-declarations' rule, so + * this rule doesn't modify those declarations. + * + * ## A variable is redeclared. + * + * The language spec disallows redeclarations of `let` declarations. + * Those variables would cause syntax errors. + * + * ## A variable is used from outside the scope. + * + * The language spec disallows accesses from outside of the scope for + * `let` declarations. Those variables would cause reference errors. + * + * ## A variable is used from a closure within a loop. + * + * A `var` declaration within a loop shares the same variable instance + * across all loop iterations, while a `let` declaration creates a new + * instance for each iteration. This means if a variable in a loop is + * referenced by any closure, changing it from `var` to `let` would + * change the behavior in a way that is generally unsafe. + * + * ## A variable might be used before it is assigned within a loop. + * + * Within a loop, a `let` declaration without an initializer will be + * initialized to null, while a `var` declaration will retain its value + * from the previous iteration, so it is only safe to change `var` to + * `let` if we can statically determine that the variable is always + * assigned a value before its first access in the loop body. To keep + * the implementation simple, we only convert `var` to `let` within + * loops when the variable is a loop assignee or the declaration has an + * initializer. + * @param {ASTNode} node A variable declaration node to check. + * @returns {boolean} `true` if it can fix the node. + */ + function canFix(node) { + const variables = sourceCode.getDeclaredVariables(node); + const scopeNode = getScopeNode(node); + + if ( + node.parent.type === "SwitchCase" || + node.declarations.some(hasSelfReferenceInTDZ) || + variables.some(isGlobal) || + variables.some(isRedeclared) || + variables.some(isUsedFromOutsideOf(scopeNode)) || + variables.some(hasNameDisallowedForLetDeclarations) + ) { + return false; + } + + if (astUtils.isInLoop(node)) { + if (variables.some(isReferencedInClosure)) { + return false; + } + if (!isLoopAssignee(node) && !isDeclarationInitialized(node)) { + return false; + } + } + + if ( + !isLoopAssignee(node) && + !( + node.parent.type === "ForStatement" && + node.parent.init === node + ) && + !astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type) + ) { + // If the declaration is not in a block, e.g. `if (foo) var bar = 1;`, then it can't be fixed. + return false; + } + + return true; + } + + /** + * Reports a given variable declaration node. + * @param {ASTNode} node A variable declaration node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "unexpectedVar", + + fix(fixer) { + const varToken = sourceCode.getFirstToken(node, { + filter: t => t.value === "var", + }); + + return canFix(node) + ? fixer.replaceText(varToken, "let") + : null; + }, + }); + } + + return { + "VariableDeclaration:exit"(node) { + if (node.kind === "var") { + report(node); + } + }, + }; + }, }; diff --git a/lib/rules/no-void.js b/lib/rules/no-void.js index 0ac8288347ad..8e2cf446d74a 100644 --- a/lib/rules/no-void.js +++ b/lib/rules/no-void.js @@ -10,58 +10,60 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - defaultOptions: [{ - allowAsStatement: false - }], + defaultOptions: [ + { + allowAsStatement: false, + }, + ], - docs: { - description: "Disallow `void` operators", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/no-void" - }, + docs: { + description: "Disallow `void` operators", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/no-void", + }, - messages: { - noVoid: "Expected 'undefined' and instead saw 'void'." - }, + messages: { + noVoid: "Expected 'undefined' and instead saw 'void'.", + }, - schema: [ - { - type: "object", - properties: { - allowAsStatement: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - }, + schema: [ + { + type: "object", + properties: { + allowAsStatement: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, - create(context) { - const [{ allowAsStatement }] = context.options; + create(context) { + const [{ allowAsStatement }] = context.options; - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - 'UnaryExpression[operator="void"]'(node) { - if ( - allowAsStatement && - node.parent && - node.parent.type === "ExpressionStatement" - ) { - return; - } - context.report({ - node, - messageId: "noVoid" - }); - } - }; - } + return { + 'UnaryExpression[operator="void"]'(node) { + if ( + allowAsStatement && + node.parent && + node.parent.type === "ExpressionStatement" + ) { + return; + } + context.report({ + node, + messageId: "noVoid", + }); + }, + }; + }, }; diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js index 0e6a2f2848ec..9b3bb1a4a520 100644 --- a/lib/rules/no-warning-comments.js +++ b/lib/rules/no-warning-comments.js @@ -16,189 +16,194 @@ const CHAR_LIMIT = 40; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - terms: { - type: "array", - items: { - type: "string" - } - }, - location: { - enum: ["start", "anywhere"] - }, - decoration: { - type: "array", - items: { - type: "string", - pattern: "^\\S$" - }, - minItems: 1, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedComment: "Unexpected '{{matchedTerm}}' comment: '{{comment}}'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const [{ decoration, location, terms: warningTerms }] = context.options; - const escapedDecoration = escapeRegExp(decoration ? decoration.join("") : ""); - const selfConfigRegEx = /\bno-warning-comments\b/u; - - /** - * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified - * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not - * require word boundaries on that side. - * @param {string} term A term to convert to a RegExp - * @returns {RegExp} The term converted to a RegExp - */ - function convertToRegExp(term) { - const escaped = escapeRegExp(term); - - /* - * When matching at the start, ignore leading whitespace, and - * there's no need to worry about word boundaries. - * - * These expressions for the prefix and suffix are designed as follows: - * ^ handles any terms at the beginning of a comment. - * e.g. terms ["TODO"] matches `//TODO something` - * $ handles any terms at the end of a comment - * e.g. terms ["TODO"] matches `// something TODO` - * \b handles terms preceded/followed by word boundary - * e.g. terms: ["!FIX", "FIX!"] matches `// FIX!something` or `// something!FIX` - * terms: ["FIX"] matches `// FIX!` or `// !FIX`, but not `// fixed or affix` - * - * For location start: - * [\s]* handles optional leading spaces - * e.g. terms ["TODO"] matches `// TODO something` - * [\s\*]* (where "\*" is the escaped string of decoration) - * handles optional leading spaces or decoration characters (for "start" location only) - * e.g. terms ["TODO"] matches `/**** TODO something ... ` - */ - const wordBoundary = "\\b"; - - let prefix = ""; - - if (location === "start") { - prefix = `^[\\s${escapedDecoration}]*`; - } else if (/^\w/u.test(term)) { - prefix = wordBoundary; - } - - const suffix = /\w$/u.test(term) ? wordBoundary : ""; - const flags = "iu"; // Case-insensitive with Unicode case folding. - - /* - * For location "start", the typical regex is: - * /^[\s]*ESCAPED_TERM\b/iu. - * Or if decoration characters are specified (e.g. "*"), then any of - * those characters may appear in any order at the start: - * /^[\s\*]*ESCAPED_TERM\b/iu. - * - * For location "anywhere" the typical regex is - * /\bESCAPED_TERM\b/iu - * - * If it starts or ends with non-word character, the prefix and suffix are empty, respectively. - */ - return new RegExp(`${prefix}${escaped}${suffix}`, flags); - } - - const warningRegExps = warningTerms.map(convertToRegExp); - - /** - * Checks the specified comment for matches of the configured warning terms and returns the matches. - * @param {string} comment The comment which is checked. - * @returns {Array} All matched warning terms for this comment. - */ - function commentContainsWarningTerm(comment) { - const matches = []; - - warningRegExps.forEach((regex, index) => { - if (regex.test(comment)) { - matches.push(warningTerms[index]); - } - }); - - return matches; - } - - /** - * Checks the specified node for matching warning comments and reports them. - * @param {ASTNode} node The AST node being checked. - * @returns {void} undefined. - */ - function checkComment(node) { - const comment = node.value; - - if ( - astUtils.isDirectiveComment(node) && - selfConfigRegEx.test(comment) - ) { - return; - } - - const matches = commentContainsWarningTerm(comment); - - matches.forEach(matchedTerm => { - let commentToDisplay = ""; - let truncated = false; - - for (const c of comment.trim().split(/\s+/u)) { - const tmp = commentToDisplay ? `${commentToDisplay} ${c}` : c; - - if (tmp.length <= CHAR_LIMIT) { - commentToDisplay = tmp; - } else { - truncated = true; - break; - } - } - - context.report({ - node, - messageId: "unexpectedComment", - data: { - matchedTerm, - comment: `${commentToDisplay}${ - truncated ? "..." : "" - }` - } - }); - }); - } - - return { - Program() { - const comments = sourceCode.getAllComments(); - - comments - .filter(token => token.type !== "Shebang") - .forEach(checkComment); - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + terms: { + type: "array", + items: { + type: "string", + }, + }, + location: { + enum: ["start", "anywhere"], + }, + decoration: { + type: "array", + items: { + type: "string", + pattern: "^\\S$", + }, + minItems: 1, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedComment: + "Unexpected '{{matchedTerm}}' comment: '{{comment}}'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const [{ decoration, location, terms: warningTerms }] = context.options; + const escapedDecoration = escapeRegExp( + decoration ? decoration.join("") : "", + ); + const selfConfigRegEx = /\bno-warning-comments\b/u; + + /** + * Convert a warning term into a RegExp which will match a comment containing that whole word in the specified + * location ("start" or "anywhere"). If the term starts or ends with non word characters, then the match will not + * require word boundaries on that side. + * @param {string} term A term to convert to a RegExp + * @returns {RegExp} The term converted to a RegExp + */ + function convertToRegExp(term) { + const escaped = escapeRegExp(term); + + /* + * When matching at the start, ignore leading whitespace, and + * there's no need to worry about word boundaries. + * + * These expressions for the prefix and suffix are designed as follows: + * ^ handles any terms at the beginning of a comment. + * e.g. terms ["TODO"] matches `//TODO something` + * $ handles any terms at the end of a comment + * e.g. terms ["TODO"] matches `// something TODO` + * \b handles terms preceded/followed by word boundary + * e.g. terms: ["!FIX", "FIX!"] matches `// FIX!something` or `// something!FIX` + * terms: ["FIX"] matches `// FIX!` or `// !FIX`, but not `// fixed or affix` + * + * For location start: + * [\s]* handles optional leading spaces + * e.g. terms ["TODO"] matches `// TODO something` + * [\s\*]* (where "\*" is the escaped string of decoration) + * handles optional leading spaces or decoration characters (for "start" location only) + * e.g. terms ["TODO"] matches `/**** TODO something ... ` + */ + const wordBoundary = "\\b"; + + let prefix = ""; + + if (location === "start") { + prefix = `^[\\s${escapedDecoration}]*`; + } else if (/^\w/u.test(term)) { + prefix = wordBoundary; + } + + const suffix = /\w$/u.test(term) ? wordBoundary : ""; + const flags = "iu"; // Case-insensitive with Unicode case folding. + + /* + * For location "start", the typical regex is: + * /^[\s]*ESCAPED_TERM\b/iu. + * Or if decoration characters are specified (e.g. "*"), then any of + * those characters may appear in any order at the start: + * /^[\s\*]*ESCAPED_TERM\b/iu. + * + * For location "anywhere" the typical regex is + * /\bESCAPED_TERM\b/iu + * + * If it starts or ends with non-word character, the prefix and suffix are empty, respectively. + */ + return new RegExp(`${prefix}${escaped}${suffix}`, flags); + } + + const warningRegExps = warningTerms.map(convertToRegExp); + + /** + * Checks the specified comment for matches of the configured warning terms and returns the matches. + * @param {string} comment The comment which is checked. + * @returns {Array} All matched warning terms for this comment. + */ + function commentContainsWarningTerm(comment) { + const matches = []; + + warningRegExps.forEach((regex, index) => { + if (regex.test(comment)) { + matches.push(warningTerms[index]); + } + }); + + return matches; + } + + /** + * Checks the specified node for matching warning comments and reports them. + * @param {ASTNode} node The AST node being checked. + * @returns {void} undefined. + */ + function checkComment(node) { + const comment = node.value; + + if ( + astUtils.isDirectiveComment(node) && + selfConfigRegEx.test(comment) + ) { + return; + } + + const matches = commentContainsWarningTerm(comment); + + matches.forEach(matchedTerm => { + let commentToDisplay = ""; + let truncated = false; + + for (const c of comment.trim().split(/\s+/u)) { + const tmp = commentToDisplay + ? `${commentToDisplay} ${c}` + : c; + + if (tmp.length <= CHAR_LIMIT) { + commentToDisplay = tmp; + } else { + truncated = true; + break; + } + } + + context.report({ + node, + messageId: "unexpectedComment", + data: { + matchedTerm, + comment: `${commentToDisplay}${truncated ? "..." : ""}`, + }, + }); + }); + } + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments + .filter(token => token.type !== "Shebang") + .forEach(checkComment); + }, + }; + }, }; diff --git a/lib/rules/no-whitespace-before-property.js b/lib/rules/no-whitespace-before-property.js index 4cc77b5dcb56..a0e63352fe88 100644 --- a/lib/rules/no-whitespace-before-property.js +++ b/lib/rules/no-whitespace-before-property.js @@ -17,118 +17,134 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-whitespace-before-property", - url: "https://eslint.style/rules/js/no-whitespace-before-property" - } - } - ] - }, - type: "layout", - - docs: { - description: "Disallow whitespace before properties", - recommended: false, - url: "https://eslint.org/docs/latest/rules/no-whitespace-before-property" - }, - - fixable: "whitespace", - schema: [], - - messages: { - unexpectedWhitespace: "Unexpected whitespace before property {{propName}}." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports whitespace before property token - * @param {ASTNode} node the node to report in the event of an error - * @param {Token} leftToken the left token - * @param {Token} rightToken the right token - * @returns {void} - * @private - */ - function reportError(node, leftToken, rightToken) { - context.report({ - node, - messageId: "unexpectedWhitespace", - data: { - propName: sourceCode.getText(node.property) - }, - fix(fixer) { - let replacementText = ""; - - if (!node.computed && !node.optional && astUtils.isDecimalInteger(node.object)) { - - /* - * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError. - * Don't fix this case. - */ - return null; - } - - // Don't fix if comments exist. - if (sourceCode.commentsExistBetween(leftToken, rightToken)) { - return null; - } - - if (node.optional) { - replacementText = "?."; - } else if (!node.computed) { - replacementText = "."; - } - - return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], replacementText); - } - }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - MemberExpression(node) { - let rightToken; - let leftToken; - - if (!astUtils.isTokenOnSameLine(node.object, node.property)) { - return; - } - - if (node.computed) { - rightToken = sourceCode.getTokenBefore(node.property, astUtils.isOpeningBracketToken); - leftToken = sourceCode.getTokenBefore(rightToken, node.optional ? 1 : 0); - } else { - rightToken = sourceCode.getFirstToken(node.property); - leftToken = sourceCode.getTokenBefore(rightToken, 1); - } - - if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { - reportError(node, leftToken, rightToken); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-whitespace-before-property", + url: "https://eslint.style/rules/js/no-whitespace-before-property", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Disallow whitespace before properties", + recommended: false, + url: "https://eslint.org/docs/latest/rules/no-whitespace-before-property", + }, + + fixable: "whitespace", + schema: [], + + messages: { + unexpectedWhitespace: + "Unexpected whitespace before property {{propName}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports whitespace before property token + * @param {ASTNode} node the node to report in the event of an error + * @param {Token} leftToken the left token + * @param {Token} rightToken the right token + * @returns {void} + * @private + */ + function reportError(node, leftToken, rightToken) { + context.report({ + node, + messageId: "unexpectedWhitespace", + data: { + propName: sourceCode.getText(node.property), + }, + fix(fixer) { + let replacementText = ""; + + if ( + !node.computed && + !node.optional && + astUtils.isDecimalInteger(node.object) + ) { + /* + * If the object is a number literal, fixing it to something like 5.toString() would cause a SyntaxError. + * Don't fix this case. + */ + return null; + } + + // Don't fix if comments exist. + if ( + sourceCode.commentsExistBetween(leftToken, rightToken) + ) { + return null; + } + + if (node.optional) { + replacementText = "?."; + } else if (!node.computed) { + replacementText = "."; + } + + return fixer.replaceTextRange( + [leftToken.range[1], rightToken.range[0]], + replacementText, + ); + }, + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + MemberExpression(node) { + let rightToken; + let leftToken; + + if (!astUtils.isTokenOnSameLine(node.object, node.property)) { + return; + } + + if (node.computed) { + rightToken = sourceCode.getTokenBefore( + node.property, + astUtils.isOpeningBracketToken, + ); + leftToken = sourceCode.getTokenBefore( + rightToken, + node.optional ? 1 : 0, + ); + } else { + rightToken = sourceCode.getFirstToken(node.property); + leftToken = sourceCode.getTokenBefore(rightToken, 1); + } + + if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken)) { + reportError(node, leftToken, rightToken); + } + }, + }; + }, }; diff --git a/lib/rules/no-with.js b/lib/rules/no-with.js index 0fb2c4519b45..ec2eda10b0a6 100644 --- a/lib/rules/no-with.js +++ b/lib/rules/no-with.js @@ -11,29 +11,27 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow `with` statements", - recommended: true, - url: "https://eslint.org/docs/latest/rules/no-with" - }, - - schema: [], - - messages: { - unexpectedWith: "Unexpected use of 'with' statement." - } - }, - - create(context) { - - return { - WithStatement(node) { - context.report({ node, messageId: "unexpectedWith" }); - } - }; - - } + meta: { + type: "suggestion", + + docs: { + description: "Disallow `with` statements", + recommended: true, + url: "https://eslint.org/docs/latest/rules/no-with", + }, + + schema: [], + + messages: { + unexpectedWith: "Unexpected use of 'with' statement.", + }, + }, + + create(context) { + return { + WithStatement(node) { + context.report({ node, messageId: "unexpectedWith" }); + }, + }; + }, }; diff --git a/lib/rules/nonblock-statement-body-position.js b/lib/rules/nonblock-statement-body-position.js index add4c7dd9450..623103dc3a86 100644 --- a/lib/rules/nonblock-statement-body-position.js +++ b/lib/rules/nonblock-statement-body-position.js @@ -13,133 +13,152 @@ const POSITION_SCHEMA = { enum: ["beside", "below", "any"] }; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "nonblock-statement-body-position", - url: "https://eslint.style/rules/js/nonblock-statement-body-position" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce the location of single-line statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/nonblock-statement-body-position" - }, - - fixable: "whitespace", - - schema: [ - POSITION_SCHEMA, - { - properties: { - overrides: { - properties: { - if: POSITION_SCHEMA, - else: POSITION_SCHEMA, - while: POSITION_SCHEMA, - do: POSITION_SCHEMA, - for: POSITION_SCHEMA - }, - additionalProperties: false - } - }, - additionalProperties: false - } - ], - - messages: { - expectNoLinebreak: "Expected no linebreak before this statement.", - expectLinebreak: "Expected a linebreak before this statement." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Gets the applicable preference for a particular keyword - * @param {string} keywordName The name of a keyword, e.g. 'if' - * @returns {string} The applicable option for the keyword, e.g. 'beside' - */ - function getOption(keywordName) { - return context.options[1] && context.options[1].overrides && context.options[1].overrides[keywordName] || - context.options[0] || - "beside"; - } - - /** - * Validates the location of a single-line statement - * @param {ASTNode} node The single-line statement - * @param {string} keywordName The applicable keyword name for the single-line statement - * @returns {void} - */ - function validateStatement(node, keywordName) { - const option = getOption(keywordName); - - if (node.type === "BlockStatement" || option === "any") { - return; - } - - const tokenBefore = sourceCode.getTokenBefore(node); - - if (tokenBefore.loc.end.line === node.loc.start.line && option === "below") { - context.report({ - node, - messageId: "expectLinebreak", - fix: fixer => fixer.insertTextBefore(node, "\n") - }); - } else if (tokenBefore.loc.end.line !== node.loc.start.line && option === "beside") { - context.report({ - node, - messageId: "expectNoLinebreak", - fix(fixer) { - if (sourceCode.getText().slice(tokenBefore.range[1], node.range[0]).trim()) { - return null; - } - return fixer.replaceTextRange([tokenBefore.range[1], node.range[0]], " "); - } - }); - } - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - IfStatement(node) { - validateStatement(node.consequent, "if"); - - // Check the `else` node, but don't check 'else if' statements. - if (node.alternate && node.alternate.type !== "IfStatement") { - validateStatement(node.alternate, "else"); - } - }, - WhileStatement: node => validateStatement(node.body, "while"), - DoWhileStatement: node => validateStatement(node.body, "do"), - ForStatement: node => validateStatement(node.body, "for"), - ForInStatement: node => validateStatement(node.body, "for"), - ForOfStatement: node => validateStatement(node.body, "for") - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "nonblock-statement-body-position", + url: "https://eslint.style/rules/js/nonblock-statement-body-position", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce the location of single-line statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/nonblock-statement-body-position", + }, + + fixable: "whitespace", + + schema: [ + POSITION_SCHEMA, + { + properties: { + overrides: { + properties: { + if: POSITION_SCHEMA, + else: POSITION_SCHEMA, + while: POSITION_SCHEMA, + do: POSITION_SCHEMA, + for: POSITION_SCHEMA, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + expectNoLinebreak: "Expected no linebreak before this statement.", + expectLinebreak: "Expected a linebreak before this statement.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Gets the applicable preference for a particular keyword + * @param {string} keywordName The name of a keyword, e.g. 'if' + * @returns {string} The applicable option for the keyword, e.g. 'beside' + */ + function getOption(keywordName) { + return ( + (context.options[1] && + context.options[1].overrides && + context.options[1].overrides[keywordName]) || + context.options[0] || + "beside" + ); + } + + /** + * Validates the location of a single-line statement + * @param {ASTNode} node The single-line statement + * @param {string} keywordName The applicable keyword name for the single-line statement + * @returns {void} + */ + function validateStatement(node, keywordName) { + const option = getOption(keywordName); + + if (node.type === "BlockStatement" || option === "any") { + return; + } + + const tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore.loc.end.line === node.loc.start.line && + option === "below" + ) { + context.report({ + node, + messageId: "expectLinebreak", + fix: fixer => fixer.insertTextBefore(node, "\n"), + }); + } else if ( + tokenBefore.loc.end.line !== node.loc.start.line && + option === "beside" + ) { + context.report({ + node, + messageId: "expectNoLinebreak", + fix(fixer) { + if ( + sourceCode + .getText() + .slice(tokenBefore.range[1], node.range[0]) + .trim() + ) { + return null; + } + return fixer.replaceTextRange( + [tokenBefore.range[1], node.range[0]], + " ", + ); + }, + }); + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + IfStatement(node) { + validateStatement(node.consequent, "if"); + + // Check the `else` node, but don't check 'else if' statements. + if (node.alternate && node.alternate.type !== "IfStatement") { + validateStatement(node.alternate, "else"); + } + }, + WhileStatement: node => validateStatement(node.body, "while"), + DoWhileStatement: node => validateStatement(node.body, "do"), + ForStatement: node => validateStatement(node.body, "for"), + ForInStatement: node => validateStatement(node.body, "for"), + ForOfStatement: node => validateStatement(node.body, "for"), + }; + }, }; diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js index 69717e5baeea..2e7d9daab988 100644 --- a/lib/rules/object-curly-newline.js +++ b/lib/rules/object-curly-newline.js @@ -18,28 +18,28 @@ const astUtils = require("./utils/ast-utils"); // Schema objects. const OPTION_VALUE = { - oneOf: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - multiline: { - type: "boolean" - }, - minProperties: { - type: "integer", - minimum: 0 - }, - consistent: { - type: "boolean" - } - }, - additionalProperties: false, - minProperties: 1 - } - ] + oneOf: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + multiline: { + type: "boolean", + }, + minProperties: { + type: "integer", + minimum: 0, + }, + consistent: { + type: "boolean", + }, + }, + additionalProperties: false, + minProperties: 1, + }, + ], }; /** @@ -48,25 +48,25 @@ const OPTION_VALUE = { * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object. */ function normalizeOptionValue(value) { - let multiline = false; - let minProperties = Number.POSITIVE_INFINITY; - let consistent = false; - - if (value) { - if (value === "always") { - minProperties = 0; - } else if (value === "never") { - minProperties = Number.POSITIVE_INFINITY; - } else { - multiline = Boolean(value.multiline); - minProperties = value.minProperties || Number.POSITIVE_INFINITY; - consistent = Boolean(value.consistent); - } - } else { - consistent = true; - } - - return { multiline, minProperties, consistent }; + let multiline = false; + let minProperties = Number.POSITIVE_INFINITY; + let consistent = false; + + if (value) { + if (value === "always") { + minProperties = 0; + } else if (value === "never") { + minProperties = Number.POSITIVE_INFINITY; + } else { + multiline = Boolean(value.multiline); + minProperties = value.minProperties || Number.POSITIVE_INFINITY; + consistent = Boolean(value.consistent); + } + } else { + consistent = true; + } + + return { multiline, minProperties, consistent }; } /** @@ -75,7 +75,7 @@ function normalizeOptionValue(value) { * @returns {boolean} `true` if the value is an object, otherwise `false` */ function isObject(value) { - return typeof value === "object" && value !== null; + return typeof value === "object" && value !== null; } /** @@ -84,7 +84,7 @@ function isObject(value) { * @returns {boolean} `true` if the option is node-specific, otherwise `false` */ function isNodeSpecificOption(option) { - return isObject(option) || typeof option === "string"; + return isObject(option) || typeof option === "string"; } /** @@ -98,18 +98,28 @@ function isNodeSpecificOption(option) { * }} Normalized option object. */ function normalizeOptions(options) { - if (isObject(options) && Object.values(options).some(isNodeSpecificOption)) { - return { - ObjectExpression: normalizeOptionValue(options.ObjectExpression), - ObjectPattern: normalizeOptionValue(options.ObjectPattern), - ImportDeclaration: normalizeOptionValue(options.ImportDeclaration), - ExportNamedDeclaration: normalizeOptionValue(options.ExportDeclaration) - }; - } - - const value = normalizeOptionValue(options); - - return { ObjectExpression: value, ObjectPattern: value, ImportDeclaration: value, ExportNamedDeclaration: value }; + if ( + isObject(options) && + Object.values(options).some(isNodeSpecificOption) + ) { + return { + ObjectExpression: normalizeOptionValue(options.ObjectExpression), + ObjectPattern: normalizeOptionValue(options.ObjectPattern), + ImportDeclaration: normalizeOptionValue(options.ImportDeclaration), + ExportNamedDeclaration: normalizeOptionValue( + options.ExportDeclaration, + ), + }; + } + + const value = normalizeOptionValue(options); + + return { + ObjectExpression: value, + ObjectPattern: value, + ImportDeclaration: value, + ExportNamedDeclaration: value, + }; } /** @@ -122,23 +132,23 @@ function normalizeOptions(options) { * @returns {boolean} `true` if node needs to be checked for missing line breaks */ function areLineBreaksRequired(node, options, first, last) { - let objectProperties; - - if (node.type === "ObjectExpression" || node.type === "ObjectPattern") { - objectProperties = node.properties; - } else { - - // is ImportDeclaration or ExportNamedDeclaration - objectProperties = node.specifiers - .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier"); - } - - return objectProperties.length >= options.minProperties || - ( - options.multiline && - objectProperties.length > 0 && - first.loc.start.line !== last.loc.end.line - ); + let objectProperties; + + if (node.type === "ObjectExpression" || node.type === "ObjectPattern") { + objectProperties = node.properties; + } else { + // is ImportDeclaration or ExportNamedDeclaration + objectProperties = node.specifiers.filter( + s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier", + ); + } + + return ( + objectProperties.length >= options.minProperties || + (options.multiline && + objectProperties.length > 0 && + first.loc.start.line !== last.loc.end.line) + ); } //------------------------------------------------------------------------------ @@ -147,196 +157,227 @@ function areLineBreaksRequired(node, options, first, last) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "object-curly-newline", - url: "https://eslint.style/rules/js/object-curly-newline" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent line breaks after opening and before closing braces", - recommended: false, - url: "https://eslint.org/docs/latest/rules/object-curly-newline" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - OPTION_VALUE, - { - type: "object", - properties: { - ObjectExpression: OPTION_VALUE, - ObjectPattern: OPTION_VALUE, - ImportDeclaration: OPTION_VALUE, - ExportDeclaration: OPTION_VALUE - }, - additionalProperties: false, - minProperties: 1 - } - ] - } - ], - - messages: { - unexpectedLinebreakBeforeClosingBrace: "Unexpected line break before this closing brace.", - unexpectedLinebreakAfterOpeningBrace: "Unexpected line break after this opening brace.", - expectedLinebreakBeforeClosingBrace: "Expected a line break before this closing brace.", - expectedLinebreakAfterOpeningBrace: "Expected a line break after this opening brace." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const normalizedOptions = normalizeOptions(context.options[0]); - - /** - * Reports a given node if it violated this rule. - * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node. - * @returns {void} - */ - function check(node) { - const options = normalizedOptions[node.type]; - - if ( - (node.type === "ImportDeclaration" && - !node.specifiers.some(specifier => specifier.type === "ImportSpecifier")) || - (node.type === "ExportNamedDeclaration" && - !node.specifiers.some(specifier => specifier.type === "ExportSpecifier")) - ) { - return; - } - - const openBrace = sourceCode.getFirstToken(node, token => token.value === "{"); - - let closeBrace; - - if (node.typeAnnotation) { - closeBrace = sourceCode.getTokenBefore(node.typeAnnotation); - } else { - closeBrace = sourceCode.getLastToken(node, token => token.value === "}"); - } - - let first = sourceCode.getTokenAfter(openBrace, { includeComments: true }); - let last = sourceCode.getTokenBefore(closeBrace, { includeComments: true }); - - const needsLineBreaks = areLineBreaksRequired(node, options, first, last); - - const hasCommentsFirstToken = astUtils.isCommentToken(first); - const hasCommentsLastToken = astUtils.isCommentToken(last); - - /* - * Use tokens or comments to check multiline or not. - * But use only tokens to check whether line breaks are needed. - * This allows: - * var obj = { // eslint-disable-line foo - * a: 1 - * } - */ - first = sourceCode.getTokenAfter(openBrace); - last = sourceCode.getTokenBefore(closeBrace); - - if (needsLineBreaks) { - if (astUtils.isTokenOnSameLine(openBrace, first)) { - context.report({ - messageId: "expectedLinebreakAfterOpeningBrace", - node, - loc: openBrace.loc, - fix(fixer) { - if (hasCommentsFirstToken) { - return null; - } - - return fixer.insertTextAfter(openBrace, "\n"); - } - }); - } - if (astUtils.isTokenOnSameLine(last, closeBrace)) { - context.report({ - messageId: "expectedLinebreakBeforeClosingBrace", - node, - loc: closeBrace.loc, - fix(fixer) { - if (hasCommentsLastToken) { - return null; - } - - return fixer.insertTextBefore(closeBrace, "\n"); - } - }); - } - } else { - const consistent = options.consistent; - const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first); - const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace); - - if ( - (!consistent && hasLineBreakBetweenOpenBraceAndFirst) || - (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast) - ) { - context.report({ - messageId: "unexpectedLinebreakAfterOpeningBrace", - node, - loc: openBrace.loc, - fix(fixer) { - if (hasCommentsFirstToken) { - return null; - } - - return fixer.removeRange([ - openBrace.range[1], - first.range[0] - ]); - } - }); - } - if ( - (!consistent && hasLineBreakBetweenCloseBraceAndLast) || - (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast) - ) { - context.report({ - messageId: "unexpectedLinebreakBeforeClosingBrace", - node, - loc: closeBrace.loc, - fix(fixer) { - if (hasCommentsLastToken) { - return null; - } - - return fixer.removeRange([ - last.range[1], - closeBrace.range[0] - ]); - } - }); - } - } - } - - return { - ObjectExpression: check, - ObjectPattern: check, - ImportDeclaration: check, - ExportNamedDeclaration: check - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "object-curly-newline", + url: "https://eslint.style/rules/js/object-curly-newline", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent line breaks after opening and before closing braces", + recommended: false, + url: "https://eslint.org/docs/latest/rules/object-curly-newline", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + OPTION_VALUE, + { + type: "object", + properties: { + ObjectExpression: OPTION_VALUE, + ObjectPattern: OPTION_VALUE, + ImportDeclaration: OPTION_VALUE, + ExportDeclaration: OPTION_VALUE, + }, + additionalProperties: false, + minProperties: 1, + }, + ], + }, + ], + + messages: { + unexpectedLinebreakBeforeClosingBrace: + "Unexpected line break before this closing brace.", + unexpectedLinebreakAfterOpeningBrace: + "Unexpected line break after this opening brace.", + expectedLinebreakBeforeClosingBrace: + "Expected a line break before this closing brace.", + expectedLinebreakAfterOpeningBrace: + "Expected a line break after this opening brace.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const normalizedOptions = normalizeOptions(context.options[0]); + + /** + * Reports a given node if it violated this rule. + * @param {ASTNode} node A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node. + * @returns {void} + */ + function check(node) { + const options = normalizedOptions[node.type]; + + if ( + (node.type === "ImportDeclaration" && + !node.specifiers.some( + specifier => specifier.type === "ImportSpecifier", + )) || + (node.type === "ExportNamedDeclaration" && + !node.specifiers.some( + specifier => specifier.type === "ExportSpecifier", + )) + ) { + return; + } + + const openBrace = sourceCode.getFirstToken( + node, + token => token.value === "{", + ); + + let closeBrace; + + if (node.typeAnnotation) { + closeBrace = sourceCode.getTokenBefore(node.typeAnnotation); + } else { + closeBrace = sourceCode.getLastToken( + node, + token => token.value === "}", + ); + } + + let first = sourceCode.getTokenAfter(openBrace, { + includeComments: true, + }); + let last = sourceCode.getTokenBefore(closeBrace, { + includeComments: true, + }); + + const needsLineBreaks = areLineBreaksRequired( + node, + options, + first, + last, + ); + + const hasCommentsFirstToken = astUtils.isCommentToken(first); + const hasCommentsLastToken = astUtils.isCommentToken(last); + + /* + * Use tokens or comments to check multiline or not. + * But use only tokens to check whether line breaks are needed. + * This allows: + * var obj = { // eslint-disable-line foo + * a: 1 + * } + */ + first = sourceCode.getTokenAfter(openBrace); + last = sourceCode.getTokenBefore(closeBrace); + + if (needsLineBreaks) { + if (astUtils.isTokenOnSameLine(openBrace, first)) { + context.report({ + messageId: "expectedLinebreakAfterOpeningBrace", + node, + loc: openBrace.loc, + fix(fixer) { + if (hasCommentsFirstToken) { + return null; + } + + return fixer.insertTextAfter(openBrace, "\n"); + }, + }); + } + if (astUtils.isTokenOnSameLine(last, closeBrace)) { + context.report({ + messageId: "expectedLinebreakBeforeClosingBrace", + node, + loc: closeBrace.loc, + fix(fixer) { + if (hasCommentsLastToken) { + return null; + } + + return fixer.insertTextBefore(closeBrace, "\n"); + }, + }); + } + } else { + const consistent = options.consistent; + const hasLineBreakBetweenOpenBraceAndFirst = + !astUtils.isTokenOnSameLine(openBrace, first); + const hasLineBreakBetweenCloseBraceAndLast = + !astUtils.isTokenOnSameLine(last, closeBrace); + + if ( + (!consistent && hasLineBreakBetweenOpenBraceAndFirst) || + (consistent && + hasLineBreakBetweenOpenBraceAndFirst && + !hasLineBreakBetweenCloseBraceAndLast) + ) { + context.report({ + messageId: "unexpectedLinebreakAfterOpeningBrace", + node, + loc: openBrace.loc, + fix(fixer) { + if (hasCommentsFirstToken) { + return null; + } + + return fixer.removeRange([ + openBrace.range[1], + first.range[0], + ]); + }, + }); + } + if ( + (!consistent && hasLineBreakBetweenCloseBraceAndLast) || + (consistent && + !hasLineBreakBetweenOpenBraceAndFirst && + hasLineBreakBetweenCloseBraceAndLast) + ) { + context.report({ + messageId: "unexpectedLinebreakBeforeClosingBrace", + node, + loc: closeBrace.loc, + fix(fixer) { + if (hasCommentsLastToken) { + return null; + } + + return fixer.removeRange([ + last.range[1], + closeBrace.range[0], + ]); + }, + }); + } + } + } + + return { + ObjectExpression: check, + ObjectPattern: check, + ImportDeclaration: check, + ExportNamedDeclaration: check, + }; + }, }; diff --git a/lib/rules/object-curly-spacing.js b/lib/rules/object-curly-spacing.js index 42d65552c05b..d95728846ae5 100644 --- a/lib/rules/object-curly-spacing.js +++ b/lib/rules/object-curly-spacing.js @@ -13,317 +13,363 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "object-curly-spacing", - url: "https://eslint.style/rules/js/object-curly-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing inside braces", - recommended: false, - url: "https://eslint.org/docs/latest/rules/object-curly-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - arraysInObjects: { - type: "boolean" - }, - objectsInObjects: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - requireSpaceBefore: "A space is required before '{{token}}'.", - requireSpaceAfter: "A space is required after '{{token}}'.", - unexpectedSpaceBefore: "There should be no space before '{{token}}'.", - unexpectedSpaceAfter: "There should be no space after '{{token}}'." - } - }, - - create(context) { - const spaced = context.options[0] === "always", - sourceCode = context.sourceCode; - - /** - * Determines whether an option is set, relative to the spacing option. - * If spaced is "always", then check whether option is set to false. - * If spaced is "never", then check whether option is set to true. - * @param {Object} option The option to exclude. - * @returns {boolean} Whether or not the property is excluded. - */ - function isOptionSet(option) { - return context.options[1] ? context.options[1][option] === !spaced : false; - } - - const options = { - spaced, - arraysInObjectsException: isOptionSet("arraysInObjects"), - objectsInObjectsException: isOptionSet("objectsInObjects") - }; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports that there shouldn't be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoBeginningSpace(node, token) { - const nextToken = context.sourceCode.getTokenAfter(token, { includeComments: true }); - - context.report({ - node, - loc: { start: token.loc.end, end: nextToken.loc.start }, - messageId: "unexpectedSpaceAfter", - data: { - token: token.value - }, - fix(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } - - /** - * Reports that there shouldn't be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportNoEndingSpace(node, token) { - const previousToken = context.sourceCode.getTokenBefore(token, { includeComments: true }); - - context.report({ - node, - loc: { start: previousToken.loc.end, end: token.loc.start }, - messageId: "unexpectedSpaceBefore", - data: { - token: token.value - }, - fix(fixer) { - return fixer.removeRange([previousToken.range[1], token.range[0]]); - } - }); - } - - /** - * Reports that there should be a space after the first token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredBeginningSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "requireSpaceAfter", - data: { - token: token.value - }, - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } - - /** - * Reports that there should be a space before the last token - * @param {ASTNode} node The node to report in the event of an error. - * @param {Token} token The token to use for the report. - * @returns {void} - */ - function reportRequiredEndingSpace(node, token) { - context.report({ - node, - loc: token.loc, - messageId: "requireSpaceBefore", - data: { - token: token.value - }, - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - - /** - * Determines if spacing in curly braces is valid. - * @param {ASTNode} node The AST node to check. - * @param {Token} first The first token to check (should be the opening brace) - * @param {Token} second The second token to check (should be first after the opening brace) - * @param {Token} penultimate The penultimate token to check (should be last before closing brace) - * @param {Token} last The last token to check (should be closing brace) - * @returns {void} - */ - function validateBraceSpacing(node, first, second, penultimate, last) { - if (astUtils.isTokenOnSameLine(first, second)) { - const firstSpaced = sourceCode.isSpaceBetweenTokens(first, second); - - if (options.spaced && !firstSpaced) { - reportRequiredBeginningSpace(node, first); - } - if (!options.spaced && firstSpaced && second.type !== "Line") { - reportNoBeginningSpace(node, first); - } - } - - if (astUtils.isTokenOnSameLine(penultimate, last)) { - const shouldCheckPenultimate = ( - options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) || - options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate) - ); - const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type; - - const closingCurlyBraceMustBeSpaced = ( - options.arraysInObjectsException && penultimateType === "ArrayExpression" || - options.objectsInObjectsException && (penultimateType === "ObjectExpression" || penultimateType === "ObjectPattern") - ) ? !options.spaced : options.spaced; - - const lastSpaced = sourceCode.isSpaceBetweenTokens(penultimate, last); - - if (closingCurlyBraceMustBeSpaced && !lastSpaced) { - reportRequiredEndingSpace(node, last); - } - if (!closingCurlyBraceMustBeSpaced && lastSpaced) { - reportNoEndingSpace(node, last); - } - } - } - - /** - * Gets '}' token of an object node. - * - * Because the last token of object patterns might be a type annotation, - * this traverses tokens preceded by the last property, then returns the - * first '}' token. - * @param {ASTNode} node The node to get. This node is an - * ObjectExpression or an ObjectPattern. And this node has one or - * more properties. - * @returns {Token} '}' token. - */ - function getClosingBraceOfObject(node) { - const lastProperty = node.properties.at(-1); - - return sourceCode.getTokenAfter(lastProperty, astUtils.isClosingBraceToken); - } - - /** - * Reports a given object node if spacing in curly braces is invalid. - * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check. - * @returns {void} - */ - function checkForObject(node) { - if (node.properties.length === 0) { - return; - } - - const first = sourceCode.getFirstToken(node), - last = getClosingBraceOfObject(node), - second = sourceCode.getTokenAfter(first, { includeComments: true }), - penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); - - validateBraceSpacing(node, first, second, penultimate, last); - } - - /** - * Reports a given import node if spacing in curly braces is invalid. - * @param {ASTNode} node An ImportDeclaration node to check. - * @returns {void} - */ - function checkForImport(node) { - if (node.specifiers.length === 0) { - return; - } - - let firstSpecifier = node.specifiers[0]; - const lastSpecifier = node.specifiers.at(-1); - - if (lastSpecifier.type !== "ImportSpecifier") { - return; - } - if (firstSpecifier.type !== "ImportSpecifier") { - firstSpecifier = node.specifiers[1]; - } - - const first = sourceCode.getTokenBefore(firstSpecifier), - last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken), - second = sourceCode.getTokenAfter(first, { includeComments: true }), - penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); - - validateBraceSpacing(node, first, second, penultimate, last); - } - - /** - * Reports a given export node if spacing in curly braces is invalid. - * @param {ASTNode} node An ExportNamedDeclaration node to check. - * @returns {void} - */ - function checkForExport(node) { - if (node.specifiers.length === 0) { - return; - } - - const firstSpecifier = node.specifiers[0], - lastSpecifier = node.specifiers.at(-1), - first = sourceCode.getTokenBefore(firstSpecifier), - last = sourceCode.getTokenAfter(lastSpecifier, astUtils.isNotCommaToken), - second = sourceCode.getTokenAfter(first, { includeComments: true }), - penultimate = sourceCode.getTokenBefore(last, { includeComments: true }); - - validateBraceSpacing(node, first, second, penultimate, last); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - // var {x} = y; - ObjectPattern: checkForObject, - - // var y = {x: 'y'} - ObjectExpression: checkForObject, - - // import {y} from 'x'; - ImportDeclaration: checkForImport, - - // export {name} from 'yo'; - ExportNamedDeclaration: checkForExport - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "object-curly-spacing", + url: "https://eslint.style/rules/js/object-curly-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent spacing inside braces", + recommended: false, + url: "https://eslint.org/docs/latest/rules/object-curly-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + arraysInObjects: { + type: "boolean", + }, + objectsInObjects: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + requireSpaceBefore: "A space is required before '{{token}}'.", + requireSpaceAfter: "A space is required after '{{token}}'.", + unexpectedSpaceBefore: + "There should be no space before '{{token}}'.", + unexpectedSpaceAfter: "There should be no space after '{{token}}'.", + }, + }, + + create(context) { + const spaced = context.options[0] === "always", + sourceCode = context.sourceCode; + + /** + * Determines whether an option is set, relative to the spacing option. + * If spaced is "always", then check whether option is set to false. + * If spaced is "never", then check whether option is set to true. + * @param {Object} option The option to exclude. + * @returns {boolean} Whether or not the property is excluded. + */ + function isOptionSet(option) { + return context.options[1] + ? context.options[1][option] === !spaced + : false; + } + + const options = { + spaced, + arraysInObjectsException: isOptionSet("arraysInObjects"), + objectsInObjectsException: isOptionSet("objectsInObjects"), + }; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports that there shouldn't be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoBeginningSpace(node, token) { + const nextToken = context.sourceCode.getTokenAfter(token, { + includeComments: true, + }); + + context.report({ + node, + loc: { start: token.loc.end, end: nextToken.loc.start }, + messageId: "unexpectedSpaceAfter", + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + token.range[1], + nextToken.range[0], + ]); + }, + }); + } + + /** + * Reports that there shouldn't be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportNoEndingSpace(node, token) { + const previousToken = context.sourceCode.getTokenBefore(token, { + includeComments: true, + }); + + context.report({ + node, + loc: { start: previousToken.loc.end, end: token.loc.start }, + messageId: "unexpectedSpaceBefore", + data: { + token: token.value, + }, + fix(fixer) { + return fixer.removeRange([ + previousToken.range[1], + token.range[0], + ]); + }, + }); + } + + /** + * Reports that there should be a space after the first token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredBeginningSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "requireSpaceAfter", + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } + + /** + * Reports that there should be a space before the last token + * @param {ASTNode} node The node to report in the event of an error. + * @param {Token} token The token to use for the report. + * @returns {void} + */ + function reportRequiredEndingSpace(node, token) { + context.report({ + node, + loc: token.loc, + messageId: "requireSpaceBefore", + data: { + token: token.value, + }, + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } + + /** + * Determines if spacing in curly braces is valid. + * @param {ASTNode} node The AST node to check. + * @param {Token} first The first token to check (should be the opening brace) + * @param {Token} second The second token to check (should be first after the opening brace) + * @param {Token} penultimate The penultimate token to check (should be last before closing brace) + * @param {Token} last The last token to check (should be closing brace) + * @returns {void} + */ + function validateBraceSpacing(node, first, second, penultimate, last) { + if (astUtils.isTokenOnSameLine(first, second)) { + const firstSpaced = sourceCode.isSpaceBetweenTokens( + first, + second, + ); + + if (options.spaced && !firstSpaced) { + reportRequiredBeginningSpace(node, first); + } + if (!options.spaced && firstSpaced && second.type !== "Line") { + reportNoBeginningSpace(node, first); + } + } + + if (astUtils.isTokenOnSameLine(penultimate, last)) { + const shouldCheckPenultimate = + (options.arraysInObjectsException && + astUtils.isClosingBracketToken(penultimate)) || + (options.objectsInObjectsException && + astUtils.isClosingBraceToken(penultimate)); + const penultimateType = + shouldCheckPenultimate && + sourceCode.getNodeByRangeIndex(penultimate.range[0]).type; + + const closingCurlyBraceMustBeSpaced = + (options.arraysInObjectsException && + penultimateType === "ArrayExpression") || + (options.objectsInObjectsException && + (penultimateType === "ObjectExpression" || + penultimateType === "ObjectPattern")) + ? !options.spaced + : options.spaced; + + const lastSpaced = sourceCode.isSpaceBetweenTokens( + penultimate, + last, + ); + + if (closingCurlyBraceMustBeSpaced && !lastSpaced) { + reportRequiredEndingSpace(node, last); + } + if (!closingCurlyBraceMustBeSpaced && lastSpaced) { + reportNoEndingSpace(node, last); + } + } + } + + /** + * Gets '}' token of an object node. + * + * Because the last token of object patterns might be a type annotation, + * this traverses tokens preceded by the last property, then returns the + * first '}' token. + * @param {ASTNode} node The node to get. This node is an + * ObjectExpression or an ObjectPattern. And this node has one or + * more properties. + * @returns {Token} '}' token. + */ + function getClosingBraceOfObject(node) { + const lastProperty = node.properties.at(-1); + + return sourceCode.getTokenAfter( + lastProperty, + astUtils.isClosingBraceToken, + ); + } + + /** + * Reports a given object node if spacing in curly braces is invalid. + * @param {ASTNode} node An ObjectExpression or ObjectPattern node to check. + * @returns {void} + */ + function checkForObject(node) { + if (node.properties.length === 0) { + return; + } + + const first = sourceCode.getFirstToken(node), + last = getClosingBraceOfObject(node), + second = sourceCode.getTokenAfter(first, { + includeComments: true, + }), + penultimate = sourceCode.getTokenBefore(last, { + includeComments: true, + }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given import node if spacing in curly braces is invalid. + * @param {ASTNode} node An ImportDeclaration node to check. + * @returns {void} + */ + function checkForImport(node) { + if (node.specifiers.length === 0) { + return; + } + + let firstSpecifier = node.specifiers[0]; + const lastSpecifier = node.specifiers.at(-1); + + if (lastSpecifier.type !== "ImportSpecifier") { + return; + } + if (firstSpecifier.type !== "ImportSpecifier") { + firstSpecifier = node.specifiers[1]; + } + + const first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter( + lastSpecifier, + astUtils.isNotCommaToken, + ), + second = sourceCode.getTokenAfter(first, { + includeComments: true, + }), + penultimate = sourceCode.getTokenBefore(last, { + includeComments: true, + }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + /** + * Reports a given export node if spacing in curly braces is invalid. + * @param {ASTNode} node An ExportNamedDeclaration node to check. + * @returns {void} + */ + function checkForExport(node) { + if (node.specifiers.length === 0) { + return; + } + + const firstSpecifier = node.specifiers[0], + lastSpecifier = node.specifiers.at(-1), + first = sourceCode.getTokenBefore(firstSpecifier), + last = sourceCode.getTokenAfter( + lastSpecifier, + astUtils.isNotCommaToken, + ), + second = sourceCode.getTokenAfter(first, { + includeComments: true, + }), + penultimate = sourceCode.getTokenBefore(last, { + includeComments: true, + }); + + validateBraceSpacing(node, first, second, penultimate, last); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + // var {x} = y; + ObjectPattern: checkForObject, + + // var y = {x: 'y'} + ObjectExpression: checkForObject, + + // import {y} from 'x'; + ImportDeclaration: checkForImport, + + // export {name} from 'yo'; + ExportNamedDeclaration: checkForExport, + }; + }, }; diff --git a/lib/rules/object-property-newline.js b/lib/rules/object-property-newline.js index 1549ad77a0f7..21cf51db99e8 100644 --- a/lib/rules/object-property-newline.js +++ b/lib/rules/object-property-newline.js @@ -12,109 +12,140 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "object-property-newline", - url: "https://eslint.style/rules/js/object-property-newline" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce placing object properties on separate lines", - recommended: false, - url: "https://eslint.org/docs/latest/rules/object-property-newline" - }, - - schema: [ - { - type: "object", - properties: { - allowAllPropertiesOnSameLine: { - type: "boolean", - default: false - }, - allowMultiplePropertiesPerLine: { // Deprecated - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - fixable: "whitespace", - - messages: { - propertiesOnNewlineAll: "Object properties must go on a new line if they aren't all on the same line.", - propertiesOnNewline: "Object properties must go on a new line." - } - }, - - create(context) { - const allowSameLine = context.options[0] && ( - (context.options[0].allowAllPropertiesOnSameLine || context.options[0].allowMultiplePropertiesPerLine /* Deprecated */) - ); - const messageId = allowSameLine - ? "propertiesOnNewlineAll" - : "propertiesOnNewline"; - - const sourceCode = context.sourceCode; - - return { - ObjectExpression(node) { - if (allowSameLine) { - if (node.properties.length > 1) { - const firstTokenOfFirstProperty = sourceCode.getFirstToken(node.properties[0]); - const lastTokenOfLastProperty = sourceCode.getLastToken(node.properties.at(-1)); - - if (firstTokenOfFirstProperty.loc.end.line === lastTokenOfLastProperty.loc.start.line) { - - // All keys and values are on the same line - return; - } - } - } - - for (let i = 1; i < node.properties.length; i++) { - const lastTokenOfPreviousProperty = sourceCode.getLastToken(node.properties[i - 1]); - const firstTokenOfCurrentProperty = sourceCode.getFirstToken(node.properties[i]); - - if (lastTokenOfPreviousProperty.loc.end.line === firstTokenOfCurrentProperty.loc.start.line) { - context.report({ - node, - loc: firstTokenOfCurrentProperty.loc, - messageId, - fix(fixer) { - const comma = sourceCode.getTokenBefore(firstTokenOfCurrentProperty); - const rangeAfterComma = [comma.range[1], firstTokenOfCurrentProperty.range[0]]; - - // Don't perform a fix if there are any comments between the comma and the next property. - if (sourceCode.text.slice(rangeAfterComma[0], rangeAfterComma[1]).trim()) { - return null; - } - - return fixer.replaceTextRange(rangeAfterComma, "\n"); - } - }); - } - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "object-property-newline", + url: "https://eslint.style/rules/js/object-property-newline", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce placing object properties on separate lines", + recommended: false, + url: "https://eslint.org/docs/latest/rules/object-property-newline", + }, + + schema: [ + { + type: "object", + properties: { + allowAllPropertiesOnSameLine: { + type: "boolean", + default: false, + }, + allowMultiplePropertiesPerLine: { + // Deprecated + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + fixable: "whitespace", + + messages: { + propertiesOnNewlineAll: + "Object properties must go on a new line if they aren't all on the same line.", + propertiesOnNewline: "Object properties must go on a new line.", + }, + }, + + create(context) { + const allowSameLine = + context.options[0] && + (context.options[0].allowAllPropertiesOnSameLine || + context.options[0] + .allowMultiplePropertiesPerLine); /* Deprecated */ + const messageId = allowSameLine + ? "propertiesOnNewlineAll" + : "propertiesOnNewline"; + + const sourceCode = context.sourceCode; + + return { + ObjectExpression(node) { + if (allowSameLine) { + if (node.properties.length > 1) { + const firstTokenOfFirstProperty = + sourceCode.getFirstToken(node.properties[0]); + const lastTokenOfLastProperty = sourceCode.getLastToken( + node.properties.at(-1), + ); + + if ( + firstTokenOfFirstProperty.loc.end.line === + lastTokenOfLastProperty.loc.start.line + ) { + // All keys and values are on the same line + return; + } + } + } + + for (let i = 1; i < node.properties.length; i++) { + const lastTokenOfPreviousProperty = sourceCode.getLastToken( + node.properties[i - 1], + ); + const firstTokenOfCurrentProperty = + sourceCode.getFirstToken(node.properties[i]); + + if ( + lastTokenOfPreviousProperty.loc.end.line === + firstTokenOfCurrentProperty.loc.start.line + ) { + context.report({ + node, + loc: firstTokenOfCurrentProperty.loc, + messageId, + fix(fixer) { + const comma = sourceCode.getTokenBefore( + firstTokenOfCurrentProperty, + ); + const rangeAfterComma = [ + comma.range[1], + firstTokenOfCurrentProperty.range[0], + ]; + + // Don't perform a fix if there are any comments between the comma and the next property. + if ( + sourceCode.text + .slice( + rangeAfterComma[0], + rangeAfterComma[1], + ) + .trim() + ) { + return null; + } + + return fixer.replaceTextRange( + rangeAfterComma, + "\n", + ); + }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index 35428ac68123..c17d55f40322 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -6,12 +6,12 @@ "use strict"; const OPTIONS = { - always: "always", - never: "never", - methods: "methods", - properties: "properties", - consistent: "consistent", - consistentAsNeeded: "consistent-as-needed" + always: "always", + never: "never", + methods: "methods", + properties: "properties", + consistent: "consistent", + consistentAsNeeded: "consistent-as-needed", }; //------------------------------------------------------------------------------ @@ -24,499 +24,604 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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", - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always", "methods", "properties", "never", "consistent", "consistent-as-needed"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["always", "methods", "properties"] - }, - { - type: "object", - properties: { - avoidQuotes: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - }, - { - type: "array", - items: [ - { - enum: ["always", "methods"] - }, - { - type: "object", - properties: { - ignoreConstructors: { - type: "boolean" - }, - methodsIgnorePattern: { - type: "string" - }, - avoidQuotes: { - type: "boolean" - }, - avoidExplicitReturnArrows: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - messages: { - expectedAllPropertiesShorthanded: "Expected shorthand for all properties.", - expectedLiteralMethodLongform: "Expected longform method syntax for string literal keys.", - expectedPropertyShorthand: "Expected property shorthand.", - expectedPropertyLongform: "Expected longform property syntax.", - expectedMethodShorthand: "Expected method shorthand.", - expectedMethodLongform: "Expected longform method syntax.", - unexpectedMix: "Unexpected mix of shorthand and non-shorthand properties." - } - }, - - create(context) { - const APPLY = context.options[0] || OPTIONS.always; - const APPLY_TO_METHODS = APPLY === OPTIONS.methods || APPLY === OPTIONS.always; - const APPLY_TO_PROPS = APPLY === OPTIONS.properties || APPLY === OPTIONS.always; - const APPLY_NEVER = APPLY === OPTIONS.never; - const APPLY_CONSISTENT = APPLY === OPTIONS.consistent; - const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded; - - const PARAMS = context.options[1] || {}; - const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; - const METHODS_IGNORE_PATTERN = PARAMS.methodsIgnorePattern - ? new RegExp(PARAMS.methodsIgnorePattern, "u") - : null; - const AVOID_QUOTES = PARAMS.avoidQuotes; - const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const CTOR_PREFIX_REGEX = /[^_$0-9]/u; - - /** - * Determines if the first character of the name is a capital letter. - * @param {string} name The name of the node to evaluate. - * @returns {boolean} True if the first character of the property name is a capital letter, false if not. - * @private - */ - function isConstructor(name) { - const match = CTOR_PREFIX_REGEX.exec(name); - - // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8' - if (!match) { - return false; - } - - const firstChar = name.charAt(match.index); - - return firstChar === firstChar.toUpperCase(); - } - - /** - * Determines if the property can have a shorthand form. - * @param {ASTNode} property Property AST node - * @returns {boolean} True if the property can have a shorthand form - * @private - */ - function canHaveShorthand(property) { - return (property.kind !== "set" && property.kind !== "get" && property.type !== "SpreadElement" && property.type !== "SpreadProperty" && property.type !== "ExperimentalSpreadProperty"); - } - - /** - * Checks whether a node is a string literal. - * @param {ASTNode} node Any AST node. - * @returns {boolean} `true` if it is a string literal. - */ - function isStringLiteral(node) { - return node.type === "Literal" && typeof node.value === "string"; - } - - /** - * Determines if the property is a shorthand or not. - * @param {ASTNode} property Property AST node - * @returns {boolean} True if the property is considered shorthand, false if not. - * @private - */ - function isShorthand(property) { - - // property.method is true when `{a(){}}`. - return (property.shorthand || property.method); - } - - /** - * Determines if the property's key and method or value are named equally. - * @param {ASTNode} property Property AST node - * @returns {boolean} True if the key and value are named equally, false if not. - * @private - */ - function isRedundant(property) { - const value = property.value; - - if (value.type === "FunctionExpression") { - return !value.id; // Only anonymous should be shorthand method. - } - if (value.type === "Identifier") { - return astUtils.getStaticPropertyName(property) === value.name; - } - - return false; - } - - /** - * Ensures that an object's properties are consistently shorthand, or not shorthand at all. - * @param {ASTNode} node Property AST node - * @param {boolean} checkRedundancy Whether to check longform redundancy - * @returns {void} - */ - function checkConsistency(node, checkRedundancy) { - - // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand. - const properties = node.properties.filter(canHaveShorthand); - - // Do we still have properties left after filtering the getters and setters? - if (properties.length > 0) { - const shorthandProperties = properties.filter(isShorthand); - - /* - * If we do not have an equal number of longform properties as - * shorthand properties, we are using the annotations inconsistently - */ - if (shorthandProperties.length !== properties.length) { - - // We have at least 1 shorthand property - if (shorthandProperties.length > 0) { - context.report({ node, messageId: "unexpectedMix" }); - } else if (checkRedundancy) { - - /* - * If all properties of the object contain a method or value with a name matching it's key, - * all the keys are redundant. - */ - const canAlwaysUseShorthand = properties.every(isRedundant); - - if (canAlwaysUseShorthand) { - context.report({ node, messageId: "expectedAllPropertiesShorthanded" }); - } - } - } - } - } - - /** - * Fixes a FunctionExpression node by making it into a shorthand property. - * @param {SourceCodeFixer} fixer The fixer object - * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value - * @returns {Object} A fix for this node - */ - function makeFunctionShorthand(fixer, node) { - const firstKeyToken = node.computed - ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) - : sourceCode.getFirstToken(node.key); - const lastKeyToken = node.computed - ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken) - : sourceCode.getLastToken(node.key); - const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); - let keyPrefix = ""; - - // key: /* */ () => {} - if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) { - return null; - } - - if (node.value.async) { - keyPrefix += "async "; - } - if (node.value.generator) { - keyPrefix += "*"; - } - - const fixRange = [firstKeyToken.range[0], node.range[1]]; - const methodPrefix = keyPrefix + keyText; - - if (node.value.type === "FunctionExpression") { - const functionToken = sourceCode.getTokens(node.value).find(token => token.type === "Keyword" && token.value === "function"); - const tokenBeforeParams = node.value.generator ? sourceCode.getTokenAfter(functionToken) : functionToken; - - return fixer.replaceTextRange( - fixRange, - methodPrefix + sourceCode.text.slice(tokenBeforeParams.range[1], node.value.range[1]) - ); - } - - const arrowToken = sourceCode.getTokenBefore(node.value.body, astUtils.isArrowToken); - const fnBody = sourceCode.text.slice(arrowToken.range[1], node.value.range[1]); - - // First token should not be `async` - const firstValueToken = sourceCode.getFirstToken(node.value, { - skip: node.value.async ? 1 : 0 - }); - - const sliceStart = firstValueToken.range[0]; - const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1]; - const shouldAddParens = node.value.params.length === 1 && node.value.params[0].range[0] === sliceStart; - - const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd); - const newParamText = shouldAddParens ? `(${oldParamText})` : oldParamText; - - return fixer.replaceTextRange( - fixRange, - methodPrefix + newParamText + fnBody - ); - } - - /** - * Fixes a FunctionExpression node by making it into a longform property. - * @param {SourceCodeFixer} fixer The fixer object - * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value - * @returns {Object} A fix for this node - */ - function makeFunctionLongform(fixer, node) { - const firstKeyToken = node.computed ? sourceCode.getTokens(node).find(token => token.value === "[") : sourceCode.getFirstToken(node.key); - const lastKeyToken = node.computed ? sourceCode.getTokensBetween(node.key, node.value).find(token => token.value === "]") : sourceCode.getLastToken(node.key); - const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); - let functionHeader = "function"; - - if (node.value.async) { - functionHeader = `async ${functionHeader}`; - } - if (node.value.generator) { - functionHeader = `${functionHeader}*`; - } - - return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`); - } - - /* - * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`), - * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is - * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical - * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered, - * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited. - * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them - * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule, - * because converting it into a method would change the value of one of the lexical identifiers. - */ - const lexicalScopeStack = []; - const arrowsWithLexicalIdentifiers = new WeakSet(); - const argumentsIdentifiers = new WeakSet(); - - /** - * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. - * Also, this marks all `arguments` identifiers so that they can be detected later. - * @param {ASTNode} node The node representing the function. - * @returns {void} - */ - function enterFunction(node) { - lexicalScopeStack.unshift(new Set()); - sourceCode.getScope(node).variables.filter(variable => variable.name === "arguments").forEach(variable => { - variable.references.map(ref => ref.identifier).forEach(identifier => argumentsIdentifiers.add(identifier)); - }); - } - - /** - * Exits a function. This pops the current set of arrow functions off the lexical scope stack. - * @returns {void} - */ - function exitFunction() { - lexicalScopeStack.shift(); - } - - /** - * Marks the current function as having a lexical keyword. This implies that all arrow functions - * in the current lexical scope contain a reference to this lexical keyword. - * @returns {void} - */ - function reportLexicalIdentifier() { - lexicalScopeStack[0].forEach(arrowFunction => arrowsWithLexicalIdentifiers.add(arrowFunction)); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program: enterFunction, - FunctionDeclaration: enterFunction, - FunctionExpression: enterFunction, - "Program:exit": exitFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression:exit": exitFunction, - - ArrowFunctionExpression(node) { - lexicalScopeStack[0].add(node); - }, - "ArrowFunctionExpression:exit"(node) { - lexicalScopeStack[0].delete(node); - }, - - ThisExpression: reportLexicalIdentifier, - Super: reportLexicalIdentifier, - MetaProperty(node) { - if (node.meta.name === "new" && node.property.name === "target") { - reportLexicalIdentifier(); - } - }, - Identifier(node) { - if (argumentsIdentifiers.has(node)) { - reportLexicalIdentifier(); - } - }, - - ObjectExpression(node) { - if (APPLY_CONSISTENT) { - checkConsistency(node, false); - } else if (APPLY_CONSISTENT_AS_NEEDED) { - checkConsistency(node, true); - } - }, - - "Property:exit"(node) { - const isConciseProperty = node.method || node.shorthand; - - // Ignore destructuring assignment - if (node.parent.type === "ObjectPattern") { - return; - } - - // getters and setters are ignored - if (node.kind === "get" || node.kind === "set") { - return; - } - - // only computed methods can fail the following checks - if (node.computed && node.value.type !== "FunctionExpression" && node.value.type !== "ArrowFunctionExpression") { - return; - } - - //-------------------------------------------------------------- - // Checks for property/method shorthand. - if (isConciseProperty) { - if (node.method && (APPLY_NEVER || AVOID_QUOTES && isStringLiteral(node.key))) { - const messageId = APPLY_NEVER ? "expectedMethodLongform" : "expectedLiteralMethodLongform"; - - // { x() {} } should be written as { x: function() {} } - context.report({ - node, - messageId, - fix: fixer => makeFunctionLongform(fixer, node) - }); - } else if (APPLY_NEVER) { - - // { x } should be written as { x: x } - context.report({ - node, - messageId: "expectedPropertyLongform", - fix: fixer => fixer.insertTextAfter(node.key, `: ${node.key.name}`) - }); - } - } else if (APPLY_TO_METHODS && !node.value.id && (node.value.type === "FunctionExpression" || node.value.type === "ArrowFunctionExpression")) { - if (IGNORE_CONSTRUCTORS && node.key.type === "Identifier" && isConstructor(node.key.name)) { - return; - } - - if (METHODS_IGNORE_PATTERN) { - const propertyName = astUtils.getStaticPropertyName(node); - - if (propertyName !== null && METHODS_IGNORE_PATTERN.test(propertyName)) { - return; - } - } - - if (AVOID_QUOTES && isStringLiteral(node.key)) { - return; - } - - // {[x]: function(){}} should be written as {[x]() {}} - if (node.value.type === "FunctionExpression" || - node.value.type === "ArrowFunctionExpression" && - node.value.body.type === "BlockStatement" && - AVOID_EXPLICIT_RETURN_ARROWS && - !arrowsWithLexicalIdentifiers.has(node.value) - ) { - context.report({ - node, - messageId: "expectedMethodShorthand", - fix: fixer => makeFunctionShorthand(fixer, node) - }); - } - } else if (node.value.type === "Identifier" && node.key.name === node.value.name && APPLY_TO_PROPS) { - - // {x: x} should be written as {x} - context.report({ - node, - messageId: "expectedPropertyShorthand", - fix(fixer) { - - // x: /* */ x - // x: (/* */ x) - if (sourceCode.getCommentsInside(node).length > 0) { - return null; - } - - return fixer.replaceText(node, node.value.name); - } - }); - } else if (node.value.type === "Identifier" && node.key.type === "Literal" && node.key.value === node.value.name && APPLY_TO_PROPS) { - if (AVOID_QUOTES) { - return; - } - - // {"x": x} should be written as {x} - context.report({ - node, - messageId: "expectedPropertyShorthand", - fix(fixer) { - - // "x": /* */ x - // "x": (/* */ x) - if (sourceCode.getCommentsInside(node).length > 0) { - return null; - } - - return fixer.replaceText(node, node.value.name); - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + 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", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: [ + "always", + "methods", + "properties", + "never", + "consistent", + "consistent-as-needed", + ], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: ["always", "methods", "properties"], + }, + { + type: "object", + properties: { + avoidQuotes: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + { + type: "array", + items: [ + { + enum: ["always", "methods"], + }, + { + type: "object", + properties: { + ignoreConstructors: { + type: "boolean", + }, + methodsIgnorePattern: { + type: "string", + }, + avoidQuotes: { + type: "boolean", + }, + avoidExplicitReturnArrows: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + messages: { + expectedAllPropertiesShorthanded: + "Expected shorthand for all properties.", + expectedLiteralMethodLongform: + "Expected longform method syntax for string literal keys.", + expectedPropertyShorthand: "Expected property shorthand.", + expectedPropertyLongform: "Expected longform property syntax.", + expectedMethodShorthand: "Expected method shorthand.", + expectedMethodLongform: "Expected longform method syntax.", + unexpectedMix: + "Unexpected mix of shorthand and non-shorthand properties.", + }, + }, + + create(context) { + const APPLY = context.options[0] || OPTIONS.always; + const APPLY_TO_METHODS = + APPLY === OPTIONS.methods || APPLY === OPTIONS.always; + const APPLY_TO_PROPS = + APPLY === OPTIONS.properties || APPLY === OPTIONS.always; + const APPLY_NEVER = APPLY === OPTIONS.never; + const APPLY_CONSISTENT = APPLY === OPTIONS.consistent; + const APPLY_CONSISTENT_AS_NEEDED = APPLY === OPTIONS.consistentAsNeeded; + + const PARAMS = context.options[1] || {}; + const IGNORE_CONSTRUCTORS = PARAMS.ignoreConstructors; + const METHODS_IGNORE_PATTERN = PARAMS.methodsIgnorePattern + ? new RegExp(PARAMS.methodsIgnorePattern, "u") + : null; + const AVOID_QUOTES = PARAMS.avoidQuotes; + const AVOID_EXPLICIT_RETURN_ARROWS = !!PARAMS.avoidExplicitReturnArrows; + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const CTOR_PREFIX_REGEX = /[^_$0-9]/u; + + /** + * Determines if the first character of the name is a capital letter. + * @param {string} name The name of the node to evaluate. + * @returns {boolean} True if the first character of the property name is a capital letter, false if not. + * @private + */ + function isConstructor(name) { + const match = CTOR_PREFIX_REGEX.exec(name); + + // Not a constructor if name has no characters apart from '_', '$' and digits e.g. '_', '$$', '_8' + if (!match) { + return false; + } + + const firstChar = name.charAt(match.index); + + return firstChar === firstChar.toUpperCase(); + } + + /** + * Determines if the property can have a shorthand form. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the property can have a shorthand form + * @private + */ + function canHaveShorthand(property) { + return ( + property.kind !== "set" && + property.kind !== "get" && + property.type !== "SpreadElement" && + property.type !== "SpreadProperty" && + property.type !== "ExperimentalSpreadProperty" + ); + } + + /** + * Checks whether a node is a string literal. + * @param {ASTNode} node Any AST node. + * @returns {boolean} `true` if it is a string literal. + */ + function isStringLiteral(node) { + return node.type === "Literal" && typeof node.value === "string"; + } + + /** + * Determines if the property is a shorthand or not. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the property is considered shorthand, false if not. + * @private + */ + function isShorthand(property) { + // property.method is true when `{a(){}}`. + return property.shorthand || property.method; + } + + /** + * Determines if the property's key and method or value are named equally. + * @param {ASTNode} property Property AST node + * @returns {boolean} True if the key and value are named equally, false if not. + * @private + */ + function isRedundant(property) { + const value = property.value; + + if (value.type === "FunctionExpression") { + return !value.id; // Only anonymous should be shorthand method. + } + if (value.type === "Identifier") { + return astUtils.getStaticPropertyName(property) === value.name; + } + + return false; + } + + /** + * Ensures that an object's properties are consistently shorthand, or not shorthand at all. + * @param {ASTNode} node Property AST node + * @param {boolean} checkRedundancy Whether to check longform redundancy + * @returns {void} + */ + function checkConsistency(node, checkRedundancy) { + // We are excluding getters/setters and spread properties as they are considered neither longform nor shorthand. + const properties = node.properties.filter(canHaveShorthand); + + // Do we still have properties left after filtering the getters and setters? + if (properties.length > 0) { + const shorthandProperties = properties.filter(isShorthand); + + /* + * If we do not have an equal number of longform properties as + * shorthand properties, we are using the annotations inconsistently + */ + if (shorthandProperties.length !== properties.length) { + // We have at least 1 shorthand property + if (shorthandProperties.length > 0) { + context.report({ node, messageId: "unexpectedMix" }); + } else if (checkRedundancy) { + /* + * If all properties of the object contain a method or value with a name matching it's key, + * all the keys are redundant. + */ + const canAlwaysUseShorthand = + properties.every(isRedundant); + + if (canAlwaysUseShorthand) { + context.report({ + node, + messageId: "expectedAllPropertiesShorthanded", + }); + } + } + } + } + } + + /** + * Fixes a FunctionExpression node by making it into a shorthand property. + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A `Property` node that has a `FunctionExpression` or `ArrowFunctionExpression` as its value + * @returns {Object} A fix for this node + */ + function makeFunctionShorthand(fixer, node) { + const firstKeyToken = node.computed + ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) + : sourceCode.getFirstToken(node.key); + const lastKeyToken = node.computed + ? sourceCode.getFirstTokenBetween( + node.key, + node.value, + astUtils.isClosingBracketToken, + ) + : sourceCode.getLastToken(node.key); + const keyText = sourceCode.text.slice( + firstKeyToken.range[0], + lastKeyToken.range[1], + ); + let keyPrefix = ""; + + // key: /* */ () => {} + if (sourceCode.commentsExistBetween(lastKeyToken, node.value)) { + return null; + } + + if (node.value.async) { + keyPrefix += "async "; + } + if (node.value.generator) { + keyPrefix += "*"; + } + + const fixRange = [firstKeyToken.range[0], node.range[1]]; + const methodPrefix = keyPrefix + keyText; + + if (node.value.type === "FunctionExpression") { + const functionToken = sourceCode + .getTokens(node.value) + .find( + token => + token.type === "Keyword" && + token.value === "function", + ); + const tokenBeforeParams = node.value.generator + ? sourceCode.getTokenAfter(functionToken) + : functionToken; + + return fixer.replaceTextRange( + fixRange, + methodPrefix + + sourceCode.text.slice( + tokenBeforeParams.range[1], + node.value.range[1], + ), + ); + } + + const arrowToken = sourceCode.getTokenBefore( + node.value.body, + astUtils.isArrowToken, + ); + const fnBody = sourceCode.text.slice( + arrowToken.range[1], + node.value.range[1], + ); + + // First token should not be `async` + const firstValueToken = sourceCode.getFirstToken(node.value, { + skip: node.value.async ? 1 : 0, + }); + + const sliceStart = firstValueToken.range[0]; + const sliceEnd = sourceCode.getTokenBefore(arrowToken).range[1]; + const shouldAddParens = + node.value.params.length === 1 && + node.value.params[0].range[0] === sliceStart; + + const oldParamText = sourceCode.text.slice(sliceStart, sliceEnd); + const newParamText = shouldAddParens + ? `(${oldParamText})` + : oldParamText; + + return fixer.replaceTextRange( + fixRange, + methodPrefix + newParamText + fnBody, + ); + } + + /** + * Fixes a FunctionExpression node by making it into a longform property. + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A `Property` node that has a `FunctionExpression` as its value + * @returns {Object} A fix for this node + */ + function makeFunctionLongform(fixer, node) { + const firstKeyToken = node.computed + ? sourceCode.getTokens(node).find(token => token.value === "[") + : sourceCode.getFirstToken(node.key); + const lastKeyToken = node.computed + ? sourceCode + .getTokensBetween(node.key, node.value) + .find(token => token.value === "]") + : sourceCode.getLastToken(node.key); + const keyText = sourceCode.text.slice( + firstKeyToken.range[0], + lastKeyToken.range[1], + ); + let functionHeader = "function"; + + if (node.value.async) { + functionHeader = `async ${functionHeader}`; + } + if (node.value.generator) { + functionHeader = `${functionHeader}*`; + } + + return fixer.replaceTextRange( + [node.range[0], lastKeyToken.range[1]], + `${keyText}: ${functionHeader}`, + ); + } + + /* + * To determine whether a given arrow function has a lexical identifier (`this`, `arguments`, `super`, or `new.target`), + * create a stack of functions that define these identifiers (i.e. all functions except arrow functions) as the AST is + * traversed. Whenever a new function is encountered, create a new entry on the stack (corresponding to a different lexical + * scope of `this`), and whenever a function is exited, pop that entry off the stack. When an arrow function is entered, + * keep a reference to it on the current stack entry, and remove that reference when the arrow function is exited. + * When a lexical identifier is encountered, mark all the arrow functions on the current stack entry by adding them + * to an `arrowsWithLexicalIdentifiers` set. Any arrow function in that set will not be reported by this rule, + * because converting it into a method would change the value of one of the lexical identifiers. + */ + const lexicalScopeStack = []; + const arrowsWithLexicalIdentifiers = new WeakSet(); + const argumentsIdentifiers = new WeakSet(); + + /** + * Enters a function. This creates a new lexical identifier scope, so a new Set of arrow functions is pushed onto the stack. + * Also, this marks all `arguments` identifiers so that they can be detected later. + * @param {ASTNode} node The node representing the function. + * @returns {void} + */ + function enterFunction(node) { + lexicalScopeStack.unshift(new Set()); + sourceCode + .getScope(node) + .variables.filter(variable => variable.name === "arguments") + .forEach(variable => { + variable.references + .map(ref => ref.identifier) + .forEach(identifier => + argumentsIdentifiers.add(identifier), + ); + }); + } + + /** + * Exits a function. This pops the current set of arrow functions off the lexical scope stack. + * @returns {void} + */ + function exitFunction() { + lexicalScopeStack.shift(); + } + + /** + * Marks the current function as having a lexical keyword. This implies that all arrow functions + * in the current lexical scope contain a reference to this lexical keyword. + * @returns {void} + */ + function reportLexicalIdentifier() { + lexicalScopeStack[0].forEach(arrowFunction => + arrowsWithLexicalIdentifiers.add(arrowFunction), + ); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: enterFunction, + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + "Program:exit": exitFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + + ArrowFunctionExpression(node) { + lexicalScopeStack[0].add(node); + }, + "ArrowFunctionExpression:exit"(node) { + lexicalScopeStack[0].delete(node); + }, + + ThisExpression: reportLexicalIdentifier, + Super: reportLexicalIdentifier, + MetaProperty(node) { + if ( + node.meta.name === "new" && + node.property.name === "target" + ) { + reportLexicalIdentifier(); + } + }, + Identifier(node) { + if (argumentsIdentifiers.has(node)) { + reportLexicalIdentifier(); + } + }, + + ObjectExpression(node) { + if (APPLY_CONSISTENT) { + checkConsistency(node, false); + } else if (APPLY_CONSISTENT_AS_NEEDED) { + checkConsistency(node, true); + } + }, + + "Property:exit"(node) { + const isConciseProperty = node.method || node.shorthand; + + // Ignore destructuring assignment + if (node.parent.type === "ObjectPattern") { + return; + } + + // getters and setters are ignored + if (node.kind === "get" || node.kind === "set") { + return; + } + + // only computed methods can fail the following checks + if ( + node.computed && + node.value.type !== "FunctionExpression" && + node.value.type !== "ArrowFunctionExpression" + ) { + return; + } + + //-------------------------------------------------------------- + // Checks for property/method shorthand. + if (isConciseProperty) { + if ( + node.method && + (APPLY_NEVER || + (AVOID_QUOTES && isStringLiteral(node.key))) + ) { + const messageId = APPLY_NEVER + ? "expectedMethodLongform" + : "expectedLiteralMethodLongform"; + + // { x() {} } should be written as { x: function() {} } + context.report({ + node, + messageId, + fix: fixer => makeFunctionLongform(fixer, node), + }); + } else if (APPLY_NEVER) { + // { x } should be written as { x: x } + context.report({ + node, + messageId: "expectedPropertyLongform", + fix: fixer => + fixer.insertTextAfter( + node.key, + `: ${node.key.name}`, + ), + }); + } + } else if ( + APPLY_TO_METHODS && + !node.value.id && + (node.value.type === "FunctionExpression" || + node.value.type === "ArrowFunctionExpression") + ) { + if ( + IGNORE_CONSTRUCTORS && + node.key.type === "Identifier" && + isConstructor(node.key.name) + ) { + return; + } + + if (METHODS_IGNORE_PATTERN) { + const propertyName = + astUtils.getStaticPropertyName(node); + + if ( + propertyName !== null && + METHODS_IGNORE_PATTERN.test(propertyName) + ) { + return; + } + } + + if (AVOID_QUOTES && isStringLiteral(node.key)) { + return; + } + + // {[x]: function(){}} should be written as {[x]() {}} + if ( + node.value.type === "FunctionExpression" || + (node.value.type === "ArrowFunctionExpression" && + node.value.body.type === "BlockStatement" && + AVOID_EXPLICIT_RETURN_ARROWS && + !arrowsWithLexicalIdentifiers.has(node.value)) + ) { + context.report({ + node, + messageId: "expectedMethodShorthand", + fix: fixer => makeFunctionShorthand(fixer, node), + }); + } + } else if ( + node.value.type === "Identifier" && + node.key.name === node.value.name && + APPLY_TO_PROPS + ) { + // {x: x} should be written as {x} + context.report({ + node, + messageId: "expectedPropertyShorthand", + fix(fixer) { + // x: /* */ x + // x: (/* */ x) + if (sourceCode.getCommentsInside(node).length > 0) { + return null; + } + + return fixer.replaceText(node, node.value.name); + }, + }); + } else if ( + node.value.type === "Identifier" && + node.key.type === "Literal" && + node.key.value === node.value.name && + APPLY_TO_PROPS + ) { + if (AVOID_QUOTES) { + return; + } + + // {"x": x} should be written as {x} + context.report({ + node, + messageId: "expectedPropertyShorthand", + fix(fixer) { + // "x": /* */ x + // "x": (/* */ x) + if (sourceCode.getCommentsInside(node).length > 0) { + return null; + } + + return fixer.replaceText(node, node.value.name); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/one-var-declaration-per-line.js b/lib/rules/one-var-declaration-per-line.js index e3ed6c8f3739..af0c8e07145a 100644 --- a/lib/rules/one-var-declaration-per-line.js +++ b/lib/rules/one-var-declaration-per-line.js @@ -11,103 +11,107 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "one-var-declaration-per-line", - url: "https://eslint.style/rules/js/one-var-declaration-per-line" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Require or disallow newlines around variable declarations", - recommended: false, - url: "https://eslint.org/docs/latest/rules/one-var-declaration-per-line" - }, - - schema: [ - { - enum: ["always", "initializations"] - } - ], - - fixable: "whitespace", - - messages: { - expectVarOnNewline: "Expected variable declaration to be on a new line." - } - }, - - create(context) { - - const always = context.options[0] === "always"; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - - /** - * Determine if provided keyword is a variant of for specifiers - * @private - * @param {string} keyword keyword to test - * @returns {boolean} True if `keyword` is a variant of for specifier - */ - function isForTypeSpecifier(keyword) { - return keyword === "ForStatement" || keyword === "ForInStatement" || keyword === "ForOfStatement"; - } - - /** - * Checks newlines around variable declarations. - * @private - * @param {ASTNode} node `VariableDeclaration` node to test - * @returns {void} - */ - function checkForNewLine(node) { - if (isForTypeSpecifier(node.parent.type)) { - return; - } - - const declarations = node.declarations; - let prev; - - declarations.forEach(current => { - if (prev && prev.loc.end.line === current.loc.start.line) { - if (always || prev.init || current.init) { - context.report({ - node, - messageId: "expectVarOnNewline", - loc: current.loc, - fix: fixer => fixer.insertTextBefore(current, "\n") - }); - } - } - prev = current; - }); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - VariableDeclaration: checkForNewLine - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "one-var-declaration-per-line", + url: "https://eslint.style/rules/js/one-var-declaration-per-line", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: + "Require or disallow newlines around variable declarations", + recommended: false, + url: "https://eslint.org/docs/latest/rules/one-var-declaration-per-line", + }, + + schema: [ + { + enum: ["always", "initializations"], + }, + ], + + fixable: "whitespace", + + messages: { + expectVarOnNewline: + "Expected variable declaration to be on a new line.", + }, + }, + + create(context) { + const always = context.options[0] === "always"; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Determine if provided keyword is a variant of for specifiers + * @private + * @param {string} keyword keyword to test + * @returns {boolean} True if `keyword` is a variant of for specifier + */ + function isForTypeSpecifier(keyword) { + return ( + keyword === "ForStatement" || + keyword === "ForInStatement" || + keyword === "ForOfStatement" + ); + } + + /** + * Checks newlines around variable declarations. + * @private + * @param {ASTNode} node `VariableDeclaration` node to test + * @returns {void} + */ + function checkForNewLine(node) { + if (isForTypeSpecifier(node.parent.type)) { + return; + } + + const declarations = node.declarations; + let prev; + + declarations.forEach(current => { + if (prev && prev.loc.end.line === current.loc.start.line) { + if (always || prev.init || current.init) { + context.report({ + node, + messageId: "expectVarOnNewline", + loc: current.loc, + fix: fixer => fixer.insertTextBefore(current, "\n"), + }); + } + } + prev = current; + }); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForNewLine, + }; + }, }; diff --git a/lib/rules/one-var.js b/lib/rules/one-var.js index e81b5a52d4eb..fb9d4d3ea2bb 100644 --- a/lib/rules/one-var.js +++ b/lib/rules/one-var.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the given node is in a statement list */ function isInStatementList(node) { - return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type); + return astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type); } //------------------------------------------------------------------------------ @@ -30,539 +30,655 @@ function isInStatementList(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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", - - schema: [ - { - oneOf: [ - { - enum: ["always", "never", "consecutive"] - }, - { - type: "object", - properties: { - separateRequires: { - type: "boolean" - }, - var: { - enum: ["always", "never", "consecutive"] - }, - let: { - enum: ["always", "never", "consecutive"] - }, - const: { - enum: ["always", "never", "consecutive"] - } - }, - additionalProperties: false - }, - { - type: "object", - properties: { - initialized: { - enum: ["always", "never", "consecutive"] - }, - uninitialized: { - enum: ["always", "never", "consecutive"] - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - combineUninitialized: "Combine this with the previous '{{type}}' statement with uninitialized variables.", - combineInitialized: "Combine this with the previous '{{type}}' statement with initialized variables.", - splitUninitialized: "Split uninitialized '{{type}}' declarations into multiple statements.", - splitInitialized: "Split initialized '{{type}}' declarations into multiple statements.", - splitRequires: "Split requires to be separated into a single block.", - combine: "Combine this with the previous '{{type}}' statement.", - split: "Split '{{type}}' declarations into multiple statements." - } - }, - - create(context) { - const MODE_ALWAYS = "always"; - const MODE_NEVER = "never"; - const MODE_CONSECUTIVE = "consecutive"; - const mode = context.options[0] || MODE_ALWAYS; - - const options = {}; - - if (typeof mode === "string") { // simple options configuration with just a string - options.var = { uninitialized: mode, initialized: mode }; - options.let = { uninitialized: mode, initialized: mode }; - options.const = { uninitialized: mode, initialized: mode }; - } else if (typeof mode === "object") { // options configuration is an object - options.separateRequires = !!mode.separateRequires; - options.var = { uninitialized: mode.var, initialized: mode.var }; - options.let = { uninitialized: mode.let, initialized: mode.let }; - options.const = { uninitialized: mode.const, initialized: mode.const }; - if (Object.hasOwn(mode, "uninitialized")) { - options.var.uninitialized = mode.uninitialized; - options.let.uninitialized = mode.uninitialized; - options.const.uninitialized = mode.uninitialized; - } - if (Object.hasOwn(mode, "initialized")) { - options.var.initialized = mode.initialized; - options.let.initialized = mode.initialized; - options.const.initialized = mode.initialized; - } - } - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - const functionStack = []; - const blockStack = []; - - /** - * Increments the blockStack counter. - * @returns {void} - * @private - */ - function startBlock() { - blockStack.push({ - let: { initialized: false, uninitialized: false }, - const: { initialized: false, uninitialized: false } - }); - } - - /** - * Increments the functionStack counter. - * @returns {void} - * @private - */ - function startFunction() { - functionStack.push({ initialized: false, uninitialized: false }); - startBlock(); - } - - /** - * Decrements the blockStack counter. - * @returns {void} - * @private - */ - function endBlock() { - blockStack.pop(); - } - - /** - * Decrements the functionStack counter. - * @returns {void} - * @private - */ - function endFunction() { - functionStack.pop(); - endBlock(); - } - - /** - * Check if a variable declaration is a require. - * @param {ASTNode} decl variable declaration Node - * @returns {bool} if decl is a require, return true; else return false. - * @private - */ - function isRequire(decl) { - return decl.init && decl.init.type === "CallExpression" && decl.init.callee.name === "require"; - } - - /** - * Records whether initialized/uninitialized/required variables are defined in current scope. - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @param {ASTNode[]} declarations List of declarations - * @param {Object} currentScope The scope being investigated - * @returns {void} - * @private - */ - function recordTypes(statementType, declarations, currentScope) { - for (let i = 0; i < declarations.length; i++) { - if (declarations[i].init === null) { - if (options[statementType] && options[statementType].uninitialized === MODE_ALWAYS) { - currentScope.uninitialized = true; - } - } else { - if (options[statementType] && options[statementType].initialized === MODE_ALWAYS) { - if (options.separateRequires && isRequire(declarations[i])) { - currentScope.required = true; - } else { - currentScope.initialized = true; - } - } - } - } - } - - /** - * Determines the current scope (function or block) - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @returns {Object} The scope associated with statementType - */ - function getCurrentScope(statementType) { - let currentScope; - - if (statementType === "var") { - currentScope = functionStack.at(-1); - } else if (statementType === "let") { - currentScope = blockStack.at(-1).let; - } else if (statementType === "const") { - currentScope = blockStack.at(-1).const; - } - return currentScope; - } - - /** - * Counts the number of initialized and uninitialized declarations in a list of declarations - * @param {ASTNode[]} declarations List of declarations - * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations - * @private - */ - function countDeclarations(declarations) { - const counts = { uninitialized: 0, initialized: 0 }; - - for (let i = 0; i < declarations.length; i++) { - if (declarations[i].init === null) { - counts.uninitialized++; - } else { - counts.initialized++; - } - } - return counts; - } - - /** - * Determines if there is more than one var statement in the current scope. - * @param {string} statementType node.kind, one of: "var", "let", or "const" - * @param {ASTNode[]} declarations List of declarations - * @returns {boolean} Returns true if it is the first var declaration, false if not. - * @private - */ - function hasOnlyOneStatement(statementType, declarations) { - - const declarationCounts = countDeclarations(declarations); - const currentOptions = options[statementType] || {}; - const currentScope = getCurrentScope(statementType); - const hasRequires = declarations.some(isRequire); - - if (currentOptions.uninitialized === MODE_ALWAYS && currentOptions.initialized === MODE_ALWAYS) { - if (currentScope.uninitialized || currentScope.initialized) { - if (!hasRequires) { - return false; - } - } - } - - if (declarationCounts.uninitialized > 0) { - if (currentOptions.uninitialized === MODE_ALWAYS && currentScope.uninitialized) { - return false; - } - } - if (declarationCounts.initialized > 0) { - if (currentOptions.initialized === MODE_ALWAYS && currentScope.initialized) { - if (!hasRequires) { - return false; - } - } - } - if (currentScope.required && hasRequires) { - return false; - } - recordTypes(statementType, declarations, currentScope); - return true; - } - - /** - * Fixer to join VariableDeclaration's into a single declaration - * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join - * @returns {Function} The fixer function - */ - function joinDeclarations(declarations) { - const declaration = declarations[0]; - const body = Array.isArray(declaration.parent.parent.body) ? declaration.parent.parent.body : []; - const currentIndex = body.findIndex(node => node.range[0] === declaration.parent.range[0]); - const previousNode = body[currentIndex - 1]; - - return fixer => { - const type = sourceCode.getTokenBefore(declaration); - const prevSemi = sourceCode.getTokenBefore(type); - const res = []; - - if (previousNode && previousNode.kind === sourceCode.getText(type)) { - if (prevSemi.value === ";") { - res.push(fixer.replaceText(prevSemi, ",")); - } else { - res.push(fixer.insertTextAfter(prevSemi, ",")); - } - res.push(fixer.replaceText(type, "")); - } - - return res; - }; - } - - /** - * Fixer to split a VariableDeclaration into individual declarations - * @param {VariableDeclaration} declaration The `VariableDeclaration` to split - * @returns {Function|null} The fixer function - */ - function splitDeclarations(declaration) { - const { parent } = declaration; - - // don't autofix code such as: if (foo) var x, y; - if (!isInStatementList(parent.type === "ExportNamedDeclaration" ? parent : declaration)) { - return null; - } - - return fixer => declaration.declarations.map(declarator => { - const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator); - - if (tokenAfterDeclarator === null) { - return null; - } - - const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true }); - - if (tokenAfterDeclarator.value !== ",") { - return null; - } - - const exportPlacement = declaration.parent.type === "ExportNamedDeclaration" ? "export " : ""; - - /* - * `var x,y` - * tokenAfterDeclarator ^^ afterComma - */ - if (afterComma.range[0] === tokenAfterDeclarator.range[1]) { - return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind} `); - } - - /* - * `var x, - * tokenAfterDeclarator ^ - * y` - * ^ afterComma - */ - if ( - afterComma.loc.start.line > tokenAfterDeclarator.loc.end.line || - afterComma.type === "Line" || - afterComma.type === "Block" - ) { - let lastComment = afterComma; - - while (lastComment.type === "Line" || lastComment.type === "Block") { - lastComment = sourceCode.getTokenAfter(lastComment, { includeComments: true }); - } - - return fixer.replaceTextRange( - [tokenAfterDeclarator.range[0], lastComment.range[0]], - `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${exportPlacement}${declaration.kind} ` - ); - } - - return fixer.replaceText(tokenAfterDeclarator, `; ${exportPlacement}${declaration.kind}`); - }).filter(x => x); - } - - /** - * Checks a given VariableDeclaration node for errors. - * @param {ASTNode} node The VariableDeclaration node to check - * @returns {void} - * @private - */ - function checkVariableDeclaration(node) { - const parent = node.parent; - const type = node.kind; - - if (!options[type]) { - return; - } - - const declarations = node.declarations; - const declarationCounts = countDeclarations(declarations); - const mixedRequires = declarations.some(isRequire) && !declarations.every(isRequire); - - if (options[type].initialized === MODE_ALWAYS) { - if (options.separateRequires && mixedRequires) { - context.report({ - node, - messageId: "splitRequires" - }); - } - } - - // consecutive - const nodeIndex = (parent.body && parent.body.length > 0 && parent.body.indexOf(node)) || 0; - - if (nodeIndex > 0) { - const previousNode = parent.body[nodeIndex - 1]; - const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration"; - const declarationsWithPrevious = declarations.concat(previousNode.declarations || []); - - if ( - isPreviousNodeDeclaration && - previousNode.kind === type && - !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire)) - ) { - const previousDeclCounts = countDeclarations(previousNode.declarations); - - if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) { - context.report({ - node, - messageId: "combine", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } else if (options[type].initialized === MODE_CONSECUTIVE && declarationCounts.initialized > 0 && previousDeclCounts.initialized > 0) { - context.report({ - node, - messageId: "combineInitialized", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } else if (options[type].uninitialized === MODE_CONSECUTIVE && - declarationCounts.uninitialized > 0 && - previousDeclCounts.uninitialized > 0) { - context.report({ - node, - messageId: "combineUninitialized", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } - } - } - - // always - if (!hasOnlyOneStatement(type, declarations)) { - if (options[type].initialized === MODE_ALWAYS && options[type].uninitialized === MODE_ALWAYS) { - context.report({ - node, - messageId: "combine", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } else { - if (options[type].initialized === MODE_ALWAYS && declarationCounts.initialized > 0) { - context.report({ - node, - messageId: "combineInitialized", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } - if (options[type].uninitialized === MODE_ALWAYS && declarationCounts.uninitialized > 0) { - if (node.parent.left === node && (node.parent.type === "ForInStatement" || node.parent.type === "ForOfStatement")) { - return; - } - context.report({ - node, - messageId: "combineUninitialized", - data: { - type - }, - fix: joinDeclarations(declarations) - }); - } - } - } - - // never - if (parent.type !== "ForStatement" || parent.init !== node) { - const totalDeclarations = declarationCounts.uninitialized + declarationCounts.initialized; - - if (totalDeclarations > 1) { - if (options[type].initialized === MODE_NEVER && options[type].uninitialized === MODE_NEVER) { - - // both initialized and uninitialized - context.report({ - node, - messageId: "split", - data: { - type - }, - fix: splitDeclarations(node) - }); - } else if (options[type].initialized === MODE_NEVER && declarationCounts.initialized > 0) { - - // initialized - context.report({ - node, - messageId: "splitInitialized", - data: { - type - }, - fix: splitDeclarations(node) - }); - } else if (options[type].uninitialized === MODE_NEVER && declarationCounts.uninitialized > 0) { - - // uninitialized - context.report({ - node, - messageId: "splitUninitialized", - data: { - type - }, - fix: splitDeclarations(node) - }); - } - } - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - Program: startFunction, - FunctionDeclaration: startFunction, - FunctionExpression: startFunction, - ArrowFunctionExpression: startFunction, - StaticBlock: startFunction, // StaticBlock creates a new scope for `var` variables - - BlockStatement: startBlock, - ForStatement: startBlock, - ForInStatement: startBlock, - ForOfStatement: startBlock, - SwitchStatement: startBlock, - VariableDeclaration: checkVariableDeclaration, - "ForStatement:exit": endBlock, - "ForOfStatement:exit": endBlock, - "ForInStatement:exit": endBlock, - "SwitchStatement:exit": endBlock, - "BlockStatement:exit": endBlock, - - "Program:exit": endFunction, - "FunctionDeclaration:exit": endFunction, - "FunctionExpression:exit": endFunction, - "ArrowFunctionExpression:exit": endFunction, - "StaticBlock:exit": endFunction - }; - - } + meta: { + type: "suggestion", + + 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", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never", "consecutive"], + }, + { + type: "object", + properties: { + separateRequires: { + type: "boolean", + }, + var: { + enum: ["always", "never", "consecutive"], + }, + let: { + enum: ["always", "never", "consecutive"], + }, + const: { + enum: ["always", "never", "consecutive"], + }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + initialized: { + enum: ["always", "never", "consecutive"], + }, + uninitialized: { + enum: ["always", "never", "consecutive"], + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + combineUninitialized: + "Combine this with the previous '{{type}}' statement with uninitialized variables.", + combineInitialized: + "Combine this with the previous '{{type}}' statement with initialized variables.", + splitUninitialized: + "Split uninitialized '{{type}}' declarations into multiple statements.", + splitInitialized: + "Split initialized '{{type}}' declarations into multiple statements.", + splitRequires: + "Split requires to be separated into a single block.", + combine: "Combine this with the previous '{{type}}' statement.", + split: "Split '{{type}}' declarations into multiple statements.", + }, + }, + + create(context) { + const MODE_ALWAYS = "always"; + const MODE_NEVER = "never"; + const MODE_CONSECUTIVE = "consecutive"; + const mode = context.options[0] || MODE_ALWAYS; + + const options = {}; + + if (typeof mode === "string") { + // simple options configuration with just a string + options.var = { uninitialized: mode, initialized: mode }; + options.let = { uninitialized: mode, initialized: mode }; + options.const = { uninitialized: mode, initialized: mode }; + } else if (typeof mode === "object") { + // options configuration is an object + options.separateRequires = !!mode.separateRequires; + options.var = { uninitialized: mode.var, initialized: mode.var }; + options.let = { uninitialized: mode.let, initialized: mode.let }; + options.const = { + uninitialized: mode.const, + initialized: mode.const, + }; + if (Object.hasOwn(mode, "uninitialized")) { + options.var.uninitialized = mode.uninitialized; + options.let.uninitialized = mode.uninitialized; + options.const.uninitialized = mode.uninitialized; + } + if (Object.hasOwn(mode, "initialized")) { + options.var.initialized = mode.initialized; + options.let.initialized = mode.initialized; + options.const.initialized = mode.initialized; + } + } + + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + const functionStack = []; + const blockStack = []; + + /** + * Increments the blockStack counter. + * @returns {void} + * @private + */ + function startBlock() { + blockStack.push({ + let: { initialized: false, uninitialized: false }, + const: { initialized: false, uninitialized: false }, + }); + } + + /** + * Increments the functionStack counter. + * @returns {void} + * @private + */ + function startFunction() { + functionStack.push({ initialized: false, uninitialized: false }); + startBlock(); + } + + /** + * Decrements the blockStack counter. + * @returns {void} + * @private + */ + function endBlock() { + blockStack.pop(); + } + + /** + * Decrements the functionStack counter. + * @returns {void} + * @private + */ + function endFunction() { + functionStack.pop(); + endBlock(); + } + + /** + * Check if a variable declaration is a require. + * @param {ASTNode} decl variable declaration Node + * @returns {bool} if decl is a require, return true; else return false. + * @private + */ + function isRequire(decl) { + return ( + decl.init && + decl.init.type === "CallExpression" && + decl.init.callee.name === "require" + ); + } + + /** + * Records whether initialized/uninitialized/required variables are defined in current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @param {Object} currentScope The scope being investigated + * @returns {void} + * @private + */ + function recordTypes(statementType, declarations, currentScope) { + for (let i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + if ( + options[statementType] && + options[statementType].uninitialized === MODE_ALWAYS + ) { + currentScope.uninitialized = true; + } + } else { + if ( + options[statementType] && + options[statementType].initialized === MODE_ALWAYS + ) { + if ( + options.separateRequires && + isRequire(declarations[i]) + ) { + currentScope.required = true; + } else { + currentScope.initialized = true; + } + } + } + } + } + + /** + * Determines the current scope (function or block) + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @returns {Object} The scope associated with statementType + */ + function getCurrentScope(statementType) { + let currentScope; + + if (statementType === "var") { + currentScope = functionStack.at(-1); + } else if (statementType === "let") { + currentScope = blockStack.at(-1).let; + } else if (statementType === "const") { + currentScope = blockStack.at(-1).const; + } + return currentScope; + } + + /** + * Counts the number of initialized and uninitialized declarations in a list of declarations + * @param {ASTNode[]} declarations List of declarations + * @returns {Object} Counts of 'uninitialized' and 'initialized' declarations + * @private + */ + function countDeclarations(declarations) { + const counts = { uninitialized: 0, initialized: 0 }; + + for (let i = 0; i < declarations.length; i++) { + if (declarations[i].init === null) { + counts.uninitialized++; + } else { + counts.initialized++; + } + } + return counts; + } + + /** + * Determines if there is more than one var statement in the current scope. + * @param {string} statementType node.kind, one of: "var", "let", or "const" + * @param {ASTNode[]} declarations List of declarations + * @returns {boolean} Returns true if it is the first var declaration, false if not. + * @private + */ + function hasOnlyOneStatement(statementType, declarations) { + const declarationCounts = countDeclarations(declarations); + const currentOptions = options[statementType] || {}; + const currentScope = getCurrentScope(statementType); + const hasRequires = declarations.some(isRequire); + + if ( + currentOptions.uninitialized === MODE_ALWAYS && + currentOptions.initialized === MODE_ALWAYS + ) { + if (currentScope.uninitialized || currentScope.initialized) { + if (!hasRequires) { + return false; + } + } + } + + if (declarationCounts.uninitialized > 0) { + if ( + currentOptions.uninitialized === MODE_ALWAYS && + currentScope.uninitialized + ) { + return false; + } + } + if (declarationCounts.initialized > 0) { + if ( + currentOptions.initialized === MODE_ALWAYS && + currentScope.initialized + ) { + if (!hasRequires) { + return false; + } + } + } + if (currentScope.required && hasRequires) { + return false; + } + recordTypes(statementType, declarations, currentScope); + return true; + } + + /** + * Fixer to join VariableDeclaration's into a single declaration + * @param {VariableDeclarator[]} declarations The `VariableDeclaration` to join + * @returns {Function} The fixer function + */ + function joinDeclarations(declarations) { + const declaration = declarations[0]; + const body = Array.isArray(declaration.parent.parent.body) + ? declaration.parent.parent.body + : []; + const currentIndex = body.findIndex( + node => node.range[0] === declaration.parent.range[0], + ); + const previousNode = body[currentIndex - 1]; + + return fixer => { + const type = sourceCode.getTokenBefore(declaration); + const prevSemi = sourceCode.getTokenBefore(type); + const res = []; + + if ( + previousNode && + previousNode.kind === sourceCode.getText(type) + ) { + if (prevSemi.value === ";") { + res.push(fixer.replaceText(prevSemi, ",")); + } else { + res.push(fixer.insertTextAfter(prevSemi, ",")); + } + res.push(fixer.replaceText(type, "")); + } + + return res; + }; + } + + /** + * Fixer to split a VariableDeclaration into individual declarations + * @param {VariableDeclaration} declaration The `VariableDeclaration` to split + * @returns {Function|null} The fixer function + */ + function splitDeclarations(declaration) { + const { parent } = declaration; + + // don't autofix code such as: if (foo) var x, y; + if ( + !isInStatementList( + parent.type === "ExportNamedDeclaration" + ? parent + : declaration, + ) + ) { + return null; + } + + return fixer => + declaration.declarations + .map(declarator => { + const tokenAfterDeclarator = + sourceCode.getTokenAfter(declarator); + + if (tokenAfterDeclarator === null) { + return null; + } + + const afterComma = sourceCode.getTokenAfter( + tokenAfterDeclarator, + { includeComments: true }, + ); + + if (tokenAfterDeclarator.value !== ",") { + return null; + } + + const exportPlacement = + declaration.parent.type === "ExportNamedDeclaration" + ? "export " + : ""; + + /* + * `var x,y` + * tokenAfterDeclarator ^^ afterComma + */ + if ( + afterComma.range[0] === + tokenAfterDeclarator.range[1] + ) { + return fixer.replaceText( + tokenAfterDeclarator, + `; ${exportPlacement}${declaration.kind} `, + ); + } + + /* + * `var x, + * tokenAfterDeclarator ^ + * y` + * ^ afterComma + */ + if ( + afterComma.loc.start.line > + tokenAfterDeclarator.loc.end.line || + afterComma.type === "Line" || + afterComma.type === "Block" + ) { + let lastComment = afterComma; + + while ( + lastComment.type === "Line" || + lastComment.type === "Block" + ) { + lastComment = sourceCode.getTokenAfter( + lastComment, + { includeComments: true }, + ); + } + + return fixer.replaceTextRange( + [ + tokenAfterDeclarator.range[0], + lastComment.range[0], + ], + `;${sourceCode.text.slice(tokenAfterDeclarator.range[1], lastComment.range[0])}${exportPlacement}${declaration.kind} `, + ); + } + + return fixer.replaceText( + tokenAfterDeclarator, + `; ${exportPlacement}${declaration.kind}`, + ); + }) + .filter(x => x); + } + + /** + * Checks a given VariableDeclaration node for errors. + * @param {ASTNode} node The VariableDeclaration node to check + * @returns {void} + * @private + */ + function checkVariableDeclaration(node) { + const parent = node.parent; + const type = node.kind; + + if (!options[type]) { + return; + } + + const declarations = node.declarations; + const declarationCounts = countDeclarations(declarations); + const mixedRequires = + declarations.some(isRequire) && !declarations.every(isRequire); + + if (options[type].initialized === MODE_ALWAYS) { + if (options.separateRequires && mixedRequires) { + context.report({ + node, + messageId: "splitRequires", + }); + } + } + + // consecutive + const nodeIndex = + (parent.body && + parent.body.length > 0 && + parent.body.indexOf(node)) || + 0; + + if (nodeIndex > 0) { + const previousNode = parent.body[nodeIndex - 1]; + const isPreviousNodeDeclaration = + previousNode.type === "VariableDeclaration"; + const declarationsWithPrevious = declarations.concat( + previousNode.declarations || [], + ); + + if ( + isPreviousNodeDeclaration && + previousNode.kind === type && + !( + declarationsWithPrevious.some(isRequire) && + !declarationsWithPrevious.every(isRequire) + ) + ) { + const previousDeclCounts = countDeclarations( + previousNode.declarations, + ); + + if ( + options[type].initialized === MODE_CONSECUTIVE && + options[type].uninitialized === MODE_CONSECUTIVE + ) { + context.report({ + node, + messageId: "combine", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } else if ( + options[type].initialized === MODE_CONSECUTIVE && + declarationCounts.initialized > 0 && + previousDeclCounts.initialized > 0 + ) { + context.report({ + node, + messageId: "combineInitialized", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } else if ( + options[type].uninitialized === MODE_CONSECUTIVE && + declarationCounts.uninitialized > 0 && + previousDeclCounts.uninitialized > 0 + ) { + context.report({ + node, + messageId: "combineUninitialized", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } + } + } + + // always + if (!hasOnlyOneStatement(type, declarations)) { + if ( + options[type].initialized === MODE_ALWAYS && + options[type].uninitialized === MODE_ALWAYS + ) { + context.report({ + node, + messageId: "combine", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } else { + if ( + options[type].initialized === MODE_ALWAYS && + declarationCounts.initialized > 0 + ) { + context.report({ + node, + messageId: "combineInitialized", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } + if ( + options[type].uninitialized === MODE_ALWAYS && + declarationCounts.uninitialized > 0 + ) { + if ( + node.parent.left === node && + (node.parent.type === "ForInStatement" || + node.parent.type === "ForOfStatement") + ) { + return; + } + context.report({ + node, + messageId: "combineUninitialized", + data: { + type, + }, + fix: joinDeclarations(declarations), + }); + } + } + } + + // never + if (parent.type !== "ForStatement" || parent.init !== node) { + const totalDeclarations = + declarationCounts.uninitialized + + declarationCounts.initialized; + + if (totalDeclarations > 1) { + if ( + options[type].initialized === MODE_NEVER && + options[type].uninitialized === MODE_NEVER + ) { + // both initialized and uninitialized + context.report({ + node, + messageId: "split", + data: { + type, + }, + fix: splitDeclarations(node), + }); + } else if ( + options[type].initialized === MODE_NEVER && + declarationCounts.initialized > 0 + ) { + // initialized + context.report({ + node, + messageId: "splitInitialized", + data: { + type, + }, + fix: splitDeclarations(node), + }); + } else if ( + options[type].uninitialized === MODE_NEVER && + declarationCounts.uninitialized > 0 + ) { + // uninitialized + context.report({ + node, + messageId: "splitUninitialized", + data: { + type, + }, + fix: splitDeclarations(node), + }); + } + } + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + Program: startFunction, + FunctionDeclaration: startFunction, + FunctionExpression: startFunction, + ArrowFunctionExpression: startFunction, + StaticBlock: startFunction, // StaticBlock creates a new scope for `var` variables + + BlockStatement: startBlock, + ForStatement: startBlock, + ForInStatement: startBlock, + ForOfStatement: startBlock, + SwitchStatement: startBlock, + VariableDeclaration: checkVariableDeclaration, + "ForStatement:exit": endBlock, + "ForOfStatement:exit": endBlock, + "ForInStatement:exit": endBlock, + "SwitchStatement:exit": endBlock, + "BlockStatement:exit": endBlock, + + "Program:exit": endFunction, + "FunctionDeclaration:exit": endFunction, + "FunctionExpression:exit": endFunction, + "ArrowFunctionExpression:exit": endFunction, + "StaticBlock:exit": endFunction, + }; + }, }; diff --git a/lib/rules/operator-assignment.js b/lib/rules/operator-assignment.js index 00619a3851b9..12b6b4cc7a1c 100644 --- a/lib/rules/operator-assignment.js +++ b/lib/rules/operator-assignment.js @@ -22,7 +22,7 @@ const astUtils = require("./utils/ast-utils"); * shorthand form. */ function isCommutativeOperatorWithShorthand(operator) { - return ["*", "&", "^", "|"].includes(operator); + return ["*", "&", "^", "|"].includes(operator); } /** @@ -33,7 +33,7 @@ function isCommutativeOperatorWithShorthand(operator) { * a shorthand form. */ function isNonCommutativeOperatorWithShorthand(operator) { - return ["+", "-", "/", "%", "<<", ">>", ">>>", "**"].includes(operator); + return ["+", "-", "/", "%", "<<", ">>", ">>>", "**"].includes(operator); } //------------------------------------------------------------------------------ @@ -47,166 +47,224 @@ function isNonCommutativeOperatorWithShorthand(operator) { * @returns {boolean} `true` if the node can be fixed */ function canBeFixed(node) { - return ( - node.type === "Identifier" || - ( - node.type === "MemberExpression" && - (node.object.type === "Identifier" || node.object.type === "ThisExpression") && - (!node.computed || node.property.type === "Literal") - ) - ); + return ( + node.type === "Identifier" || + (node.type === "MemberExpression" && + (node.object.type === "Identifier" || + node.object.type === "ThisExpression") && + (!node.computed || node.property.type === "Literal")) + ); } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - enum: ["always", "never"] - } - ], - - fixable: "code", - messages: { - replaced: "Assignment (=) can be replaced with operator assignment ({{operator}}).", - unexpected: "Unexpected operator assignment ({{operator}}) shorthand." - } - }, - - create(context) { - const never = context.options[0] === "never"; - const sourceCode = context.sourceCode; - - /** - * Returns the operator token of an AssignmentExpression or BinaryExpression - * @param {ASTNode} node An AssignmentExpression or BinaryExpression node - * @returns {Token} The operator token in the node - */ - function getOperatorToken(node) { - return sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator); - } - - /** - * Ensures that an assignment uses the shorthand form where possible. - * @param {ASTNode} node An AssignmentExpression node. - * @returns {void} - */ - function verify(node) { - if (node.operator !== "=" || node.right.type !== "BinaryExpression") { - return; - } - - const left = node.left; - const expr = node.right; - const operator = expr.operator; - - if (isCommutativeOperatorWithShorthand(operator) || isNonCommutativeOperatorWithShorthand(operator)) { - const replacementOperator = `${operator}=`; - - if (astUtils.isSameReference(left, expr.left, true)) { - context.report({ - node, - messageId: "replaced", - data: { operator: replacementOperator }, - fix(fixer) { - if (canBeFixed(left) && canBeFixed(expr.left)) { - const equalsToken = getOperatorToken(node); - const operatorToken = getOperatorToken(expr); - const leftText = sourceCode.getText().slice(node.range[0], equalsToken.range[0]); - const rightText = sourceCode.getText().slice(operatorToken.range[1], node.right.range[1]); - - // Check for comments that would be removed. - if (sourceCode.commentsExistBetween(equalsToken, operatorToken)) { - return null; - } - - return fixer.replaceText(node, `${leftText}${replacementOperator}${rightText}`); - } - return null; - } - }); - } else if (astUtils.isSameReference(left, expr.right, true) && isCommutativeOperatorWithShorthand(operator)) { - - /* - * This case can't be fixed safely. - * If `a` and `b` both have custom valueOf() behavior, then fixing `a = b * a` to `a *= b` would - * change the execution order of the valueOf() functions. - */ - context.report({ - node, - messageId: "replaced", - data: { operator: replacementOperator } - }); - } - } - } - - /** - * Warns if an assignment expression uses operator assignment shorthand. - * @param {ASTNode} node An AssignmentExpression node. - * @returns {void} - */ - function prohibit(node) { - if (node.operator !== "=" && !astUtils.isLogicalAssignmentOperator(node.operator)) { - context.report({ - node, - messageId: "unexpected", - data: { operator: node.operator }, - fix(fixer) { - if (canBeFixed(node.left)) { - const firstToken = sourceCode.getFirstToken(node); - const operatorToken = getOperatorToken(node); - const leftText = sourceCode.getText().slice(node.range[0], operatorToken.range[0]); - const newOperator = node.operator.slice(0, -1); - let rightText; - - // Check for comments that would be duplicated. - if (sourceCode.commentsExistBetween(firstToken, operatorToken)) { - return null; - } - - // If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side. - if ( - astUtils.getPrecedence(node.right) <= astUtils.getPrecedence({ type: "BinaryExpression", operator: newOperator }) && - !astUtils.isParenthesised(sourceCode, node.right) - ) { - rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`; - } else { - const tokenAfterOperator = sourceCode.getTokenAfter(operatorToken, { includeComments: true }); - let rightTextPrefix = ""; - - if ( - operatorToken.range[1] === tokenAfterOperator.range[0] && - !astUtils.canTokensBeAdjacent({ type: "Punctuator", value: newOperator }, tokenAfterOperator) - ) { - rightTextPrefix = " "; // foo+=+bar -> foo= foo+ +bar - } - - rightText = `${rightTextPrefix}${sourceCode.text.slice(operatorToken.range[1], node.range[1])}`; - } - - return fixer.replaceText(node, `${leftText}= ${leftText}${newOperator}${rightText}`); - } - return null; - } - }); - } - } - - return { - AssignmentExpression: !never ? verify : prohibit - }; - - } + meta: { + 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", + }, + + schema: [ + { + enum: ["always", "never"], + }, + ], + + fixable: "code", + messages: { + replaced: + "Assignment (=) can be replaced with operator assignment ({{operator}}).", + unexpected: + "Unexpected operator assignment ({{operator}}) shorthand.", + }, + }, + + create(context) { + const never = context.options[0] === "never"; + const sourceCode = context.sourceCode; + + /** + * Returns the operator token of an AssignmentExpression or BinaryExpression + * @param {ASTNode} node An AssignmentExpression or BinaryExpression node + * @returns {Token} The operator token in the node + */ + function getOperatorToken(node) { + return sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + } + + /** + * Ensures that an assignment uses the shorthand form where possible. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function verify(node) { + if ( + node.operator !== "=" || + node.right.type !== "BinaryExpression" + ) { + return; + } + + const left = node.left; + const expr = node.right; + const operator = expr.operator; + + if ( + isCommutativeOperatorWithShorthand(operator) || + isNonCommutativeOperatorWithShorthand(operator) + ) { + const replacementOperator = `${operator}=`; + + if (astUtils.isSameReference(left, expr.left, true)) { + context.report({ + node, + messageId: "replaced", + data: { operator: replacementOperator }, + fix(fixer) { + if (canBeFixed(left) && canBeFixed(expr.left)) { + const equalsToken = getOperatorToken(node); + const operatorToken = getOperatorToken(expr); + const leftText = sourceCode + .getText() + .slice(node.range[0], equalsToken.range[0]); + const rightText = sourceCode + .getText() + .slice( + operatorToken.range[1], + node.right.range[1], + ); + + // Check for comments that would be removed. + if ( + sourceCode.commentsExistBetween( + equalsToken, + operatorToken, + ) + ) { + return null; + } + + return fixer.replaceText( + node, + `${leftText}${replacementOperator}${rightText}`, + ); + } + return null; + }, + }); + } else if ( + astUtils.isSameReference(left, expr.right, true) && + isCommutativeOperatorWithShorthand(operator) + ) { + /* + * This case can't be fixed safely. + * If `a` and `b` both have custom valueOf() behavior, then fixing `a = b * a` to `a *= b` would + * change the execution order of the valueOf() functions. + */ + context.report({ + node, + messageId: "replaced", + data: { operator: replacementOperator }, + }); + } + } + } + + /** + * Warns if an assignment expression uses operator assignment shorthand. + * @param {ASTNode} node An AssignmentExpression node. + * @returns {void} + */ + function prohibit(node) { + if ( + node.operator !== "=" && + !astUtils.isLogicalAssignmentOperator(node.operator) + ) { + context.report({ + node, + messageId: "unexpected", + data: { operator: node.operator }, + fix(fixer) { + if (canBeFixed(node.left)) { + const firstToken = sourceCode.getFirstToken(node); + const operatorToken = getOperatorToken(node); + const leftText = sourceCode + .getText() + .slice(node.range[0], operatorToken.range[0]); + const newOperator = node.operator.slice(0, -1); + let rightText; + + // Check for comments that would be duplicated. + if ( + sourceCode.commentsExistBetween( + firstToken, + operatorToken, + ) + ) { + return null; + } + + // If this change would modify precedence (e.g. `foo *= bar + 1` => `foo = foo * (bar + 1)`), parenthesize the right side. + if ( + astUtils.getPrecedence(node.right) <= + astUtils.getPrecedence({ + type: "BinaryExpression", + operator: newOperator, + }) && + !astUtils.isParenthesised( + sourceCode, + node.right, + ) + ) { + rightText = `${sourceCode.text.slice(operatorToken.range[1], node.right.range[0])}(${sourceCode.getText(node.right)})`; + } else { + const tokenAfterOperator = + sourceCode.getTokenAfter(operatorToken, { + includeComments: true, + }); + let rightTextPrefix = ""; + + if ( + operatorToken.range[1] === + tokenAfterOperator.range[0] && + !astUtils.canTokensBeAdjacent( + { + type: "Punctuator", + value: newOperator, + }, + tokenAfterOperator, + ) + ) { + rightTextPrefix = " "; // foo+=+bar -> foo= foo+ +bar + } + + rightText = `${rightTextPrefix}${sourceCode.text.slice(operatorToken.range[1], node.range[1])}`; + } + + return fixer.replaceText( + node, + `${leftText}= ${leftText}${newOperator}${rightText}`, + ); + } + return null; + }, + }); + } + } + + return { + AssignmentExpression: !never ? verify : prohibit, + }; + }, }; diff --git a/lib/rules/operator-linebreak.js b/lib/rules/operator-linebreak.js index 835a4005289c..0d1df457051f 100644 --- a/lib/rules/operator-linebreak.js +++ b/lib/rules/operator-linebreak.js @@ -18,254 +18,298 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "operator-linebreak", - url: "https://eslint.style/rules/js/operator-linebreak" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent linebreak style for operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/operator-linebreak" - }, - - schema: [ - { - enum: ["after", "before", "none", null] - }, - { - type: "object", - properties: { - overrides: { - type: "object", - additionalProperties: { - enum: ["after", "before", "none", "ignore"] - } - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - operatorAtBeginning: "'{{operator}}' should be placed at the beginning of the line.", - operatorAtEnd: "'{{operator}}' should be placed at the end of the line.", - badLinebreak: "Bad line breaking before and after '{{operator}}'.", - noLinebreak: "There should be no line break before or after '{{operator}}'." - } - }, - - create(context) { - - const usedDefaultGlobal = !context.options[0]; - const globalStyle = context.options[0] || "after"; - const options = context.options[1] || {}; - const styleOverrides = options.overrides ? Object.assign({}, options.overrides) : {}; - - if (usedDefaultGlobal && !styleOverrides["?"]) { - styleOverrides["?"] = "before"; - } - - if (usedDefaultGlobal && !styleOverrides[":"]) { - styleOverrides[":"] = "before"; - } - - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Gets a fixer function to fix rule issues - * @param {Token} operatorToken The operator token of an expression - * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none' - * @returns {Function} A fixer function - */ - function getFixer(operatorToken, desiredStyle) { - return fixer => { - const tokenBefore = sourceCode.getTokenBefore(operatorToken); - const tokenAfter = sourceCode.getTokenAfter(operatorToken); - const textBefore = sourceCode.text.slice(tokenBefore.range[1], operatorToken.range[0]); - const textAfter = sourceCode.text.slice(operatorToken.range[1], tokenAfter.range[0]); - const hasLinebreakBefore = !astUtils.isTokenOnSameLine(tokenBefore, operatorToken); - const hasLinebreakAfter = !astUtils.isTokenOnSameLine(operatorToken, tokenAfter); - let newTextBefore, newTextAfter; - - if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") { - - // If there is a comment before and after the operator, don't do a fix. - if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && - sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) { - - return null; - } - - /* - * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator. - * foo && - * bar - * would get fixed to - * foo - * && bar - */ - newTextBefore = textAfter; - newTextAfter = textBefore; - } else { - const LINEBREAK_REGEX = astUtils.createGlobalLinebreakMatcher(); - - // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings. - newTextBefore = desiredStyle === "before" || textBefore.trim() ? textBefore : textBefore.replace(LINEBREAK_REGEX, ""); - newTextAfter = desiredStyle === "after" || textAfter.trim() ? textAfter : textAfter.replace(LINEBREAK_REGEX, ""); - - // If there was no change (due to interfering comments), don't output a fix. - if (newTextBefore === textBefore && newTextAfter === textAfter) { - return null; - } - } - - if (newTextAfter === "" && tokenAfter.type === "Punctuator" && "+-".includes(operatorToken.value) && tokenAfter.value === operatorToken.value) { - - // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-. - newTextAfter += " "; - } - - return fixer.replaceTextRange([tokenBefore.range[1], tokenAfter.range[0]], newTextBefore + operatorToken.value + newTextAfter); - }; - } - - /** - * Checks the operator placement - * @param {ASTNode} node The node to check - * @param {ASTNode} rightSide The node that comes after the operator in `node` - * @param {string} operator The operator - * @private - * @returns {void} - */ - function validateNode(node, rightSide, operator) { - - /* - * Find the operator token by searching from the right side, because between the left side and the operator - * there could be additional tokens from type annotations. Search specifically for the token which - * value equals the operator, in order to skip possible opening parentheses before the right side node. - */ - const operatorToken = sourceCode.getTokenBefore(rightSide, token => token.value === operator); - const leftToken = sourceCode.getTokenBefore(operatorToken); - const rightToken = sourceCode.getTokenAfter(operatorToken); - const operatorStyleOverride = styleOverrides[operator]; - const style = operatorStyleOverride || globalStyle; - const fix = getFixer(operatorToken, style); - - // if single line - if (astUtils.isTokenOnSameLine(leftToken, operatorToken) && - astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - - // do nothing. - - } else if (operatorStyleOverride !== "ignore" && !astUtils.isTokenOnSameLine(leftToken, operatorToken) && - !astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - - // lone operator - context.report({ - node, - loc: operatorToken.loc, - messageId: "badLinebreak", - data: { - operator - }, - fix - }); - - } else if (style === "before" && astUtils.isTokenOnSameLine(leftToken, operatorToken)) { - - context.report({ - node, - loc: operatorToken.loc, - messageId: "operatorAtBeginning", - data: { - operator - }, - fix - }); - - } else if (style === "after" && astUtils.isTokenOnSameLine(operatorToken, rightToken)) { - - context.report({ - node, - loc: operatorToken.loc, - messageId: "operatorAtEnd", - data: { - operator - }, - fix - }); - - } else if (style === "none") { - - context.report({ - node, - loc: operatorToken.loc, - messageId: "noLinebreak", - data: { - operator - }, - fix - }); - - } - } - - /** - * Validates a binary expression using `validateNode` - * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated - * @returns {void} - */ - function validateBinaryExpression(node) { - validateNode(node, node.right, node.operator); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - BinaryExpression: validateBinaryExpression, - LogicalExpression: validateBinaryExpression, - AssignmentExpression: validateBinaryExpression, - VariableDeclarator(node) { - if (node.init) { - validateNode(node, node.init, "="); - } - }, - PropertyDefinition(node) { - if (node.value) { - validateNode(node, node.value, "="); - } - }, - ConditionalExpression(node) { - validateNode(node, node.consequent, "?"); - validateNode(node, node.alternate, ":"); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "operator-linebreak", + url: "https://eslint.style/rules/js/operator-linebreak", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent linebreak style for operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/operator-linebreak", + }, + + schema: [ + { + enum: ["after", "before", "none", null], + }, + { + type: "object", + properties: { + overrides: { + type: "object", + additionalProperties: { + enum: ["after", "before", "none", "ignore"], + }, + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + operatorAtBeginning: + "'{{operator}}' should be placed at the beginning of the line.", + operatorAtEnd: + "'{{operator}}' should be placed at the end of the line.", + badLinebreak: "Bad line breaking before and after '{{operator}}'.", + noLinebreak: + "There should be no line break before or after '{{operator}}'.", + }, + }, + + create(context) { + const usedDefaultGlobal = !context.options[0]; + const globalStyle = context.options[0] || "after"; + const options = context.options[1] || {}; + const styleOverrides = options.overrides + ? Object.assign({}, options.overrides) + : {}; + + if (usedDefaultGlobal && !styleOverrides["?"]) { + styleOverrides["?"] = "before"; + } + + if (usedDefaultGlobal && !styleOverrides[":"]) { + styleOverrides[":"] = "before"; + } + + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Gets a fixer function to fix rule issues + * @param {Token} operatorToken The operator token of an expression + * @param {string} desiredStyle The style for the rule. One of 'before', 'after', 'none' + * @returns {Function} A fixer function + */ + function getFixer(operatorToken, desiredStyle) { + return fixer => { + const tokenBefore = sourceCode.getTokenBefore(operatorToken); + const tokenAfter = sourceCode.getTokenAfter(operatorToken); + const textBefore = sourceCode.text.slice( + tokenBefore.range[1], + operatorToken.range[0], + ); + const textAfter = sourceCode.text.slice( + operatorToken.range[1], + tokenAfter.range[0], + ); + const hasLinebreakBefore = !astUtils.isTokenOnSameLine( + tokenBefore, + operatorToken, + ); + const hasLinebreakAfter = !astUtils.isTokenOnSameLine( + operatorToken, + tokenAfter, + ); + let newTextBefore, newTextAfter; + + if ( + hasLinebreakBefore !== hasLinebreakAfter && + desiredStyle !== "none" + ) { + // If there is a comment before and after the operator, don't do a fix. + if ( + sourceCode.getTokenBefore(operatorToken, { + includeComments: true, + }) !== tokenBefore && + sourceCode.getTokenAfter(operatorToken, { + includeComments: true, + }) !== tokenAfter + ) { + return null; + } + + /* + * If there is only one linebreak and it's on the wrong side of the operator, swap the text before and after the operator. + * foo && + * bar + * would get fixed to + * foo + * && bar + */ + newTextBefore = textAfter; + newTextAfter = textBefore; + } else { + const LINEBREAK_REGEX = + astUtils.createGlobalLinebreakMatcher(); + + // Otherwise, if no linebreak is desired and no comments interfere, replace the linebreaks with empty strings. + newTextBefore = + desiredStyle === "before" || textBefore.trim() + ? textBefore + : textBefore.replace(LINEBREAK_REGEX, ""); + newTextAfter = + desiredStyle === "after" || textAfter.trim() + ? textAfter + : textAfter.replace(LINEBREAK_REGEX, ""); + + // If there was no change (due to interfering comments), don't output a fix. + if ( + newTextBefore === textBefore && + newTextAfter === textAfter + ) { + return null; + } + } + + if ( + newTextAfter === "" && + tokenAfter.type === "Punctuator" && + "+-".includes(operatorToken.value) && + tokenAfter.value === operatorToken.value + ) { + // To avoid accidentally creating a ++ or -- operator, insert a space if the operator is a +/- and the following token is a unary +/-. + newTextAfter += " "; + } + + return fixer.replaceTextRange( + [tokenBefore.range[1], tokenAfter.range[0]], + newTextBefore + operatorToken.value + newTextAfter, + ); + }; + } + + /** + * Checks the operator placement + * @param {ASTNode} node The node to check + * @param {ASTNode} rightSide The node that comes after the operator in `node` + * @param {string} operator The operator + * @private + * @returns {void} + */ + function validateNode(node, rightSide, operator) { + /* + * Find the operator token by searching from the right side, because between the left side and the operator + * there could be additional tokens from type annotations. Search specifically for the token which + * value equals the operator, in order to skip possible opening parentheses before the right side node. + */ + const operatorToken = sourceCode.getTokenBefore( + rightSide, + token => token.value === operator, + ); + const leftToken = sourceCode.getTokenBefore(operatorToken); + const rightToken = sourceCode.getTokenAfter(operatorToken); + const operatorStyleOverride = styleOverrides[operator]; + const style = operatorStyleOverride || globalStyle; + const fix = getFixer(operatorToken, style); + + // if single line + if ( + astUtils.isTokenOnSameLine(leftToken, operatorToken) && + astUtils.isTokenOnSameLine(operatorToken, rightToken) + ) { + // do nothing. + } else if ( + operatorStyleOverride !== "ignore" && + !astUtils.isTokenOnSameLine(leftToken, operatorToken) && + !astUtils.isTokenOnSameLine(operatorToken, rightToken) + ) { + // lone operator + context.report({ + node, + loc: operatorToken.loc, + messageId: "badLinebreak", + data: { + operator, + }, + fix, + }); + } else if ( + style === "before" && + astUtils.isTokenOnSameLine(leftToken, operatorToken) + ) { + context.report({ + node, + loc: operatorToken.loc, + messageId: "operatorAtBeginning", + data: { + operator, + }, + fix, + }); + } else if ( + style === "after" && + astUtils.isTokenOnSameLine(operatorToken, rightToken) + ) { + context.report({ + node, + loc: operatorToken.loc, + messageId: "operatorAtEnd", + data: { + operator, + }, + fix, + }); + } else if (style === "none") { + context.report({ + node, + loc: operatorToken.loc, + messageId: "noLinebreak", + data: { + operator, + }, + fix, + }); + } + } + + /** + * Validates a binary expression using `validateNode` + * @param {BinaryExpression|LogicalExpression|AssignmentExpression} node node to be validated + * @returns {void} + */ + function validateBinaryExpression(node) { + validateNode(node, node.right, node.operator); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression: validateBinaryExpression, + LogicalExpression: validateBinaryExpression, + AssignmentExpression: validateBinaryExpression, + VariableDeclarator(node) { + if (node.init) { + validateNode(node, node.init, "="); + } + }, + PropertyDefinition(node) { + if (node.value) { + validateNode(node, node.value, "="); + } + }, + ConditionalExpression(node) { + validateNode(node, node.consequent, "?"); + validateNode(node, node.alternate, ":"); + }, + }; + }, }; diff --git a/lib/rules/padded-blocks.js b/lib/rules/padded-blocks.js index 9093d5573c09..872a47b762c8 100644 --- a/lib/rules/padded-blocks.js +++ b/lib/rules/padded-blocks.js @@ -18,311 +18,349 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "padded-blocks", - url: "https://eslint.style/rules/js/padded-blocks" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow padding within blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/padded-blocks" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - blocks: { - enum: ["always", "never"] - }, - switches: { - enum: ["always", "never"] - }, - classes: { - enum: ["always", "never"] - } - }, - additionalProperties: false, - minProperties: 1 - } - ] - }, - { - type: "object", - properties: { - allowSingleLineBlocks: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - alwaysPadBlock: "Block must be padded by blank lines.", - neverPadBlock: "Block must not be padded by blank lines." - } - }, - - create(context) { - const options = {}; - const typeOptions = context.options[0] || "always"; - const exceptOptions = context.options[1] || {}; - - if (typeof typeOptions === "string") { - const shouldHavePadding = typeOptions === "always"; - - options.blocks = shouldHavePadding; - options.switches = shouldHavePadding; - options.classes = shouldHavePadding; - } else { - if (Object.hasOwn(typeOptions, "blocks")) { - options.blocks = typeOptions.blocks === "always"; - } - if (Object.hasOwn(typeOptions, "switches")) { - options.switches = typeOptions.switches === "always"; - } - if (Object.hasOwn(typeOptions, "classes")) { - options.classes = typeOptions.classes === "always"; - } - } - - if (Object.hasOwn(exceptOptions, "allowSingleLineBlocks")) { - options.allowSingleLineBlocks = exceptOptions.allowSingleLineBlocks === true; - } - - const sourceCode = context.sourceCode; - - /** - * Gets the open brace token from a given node. - * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace. - * @returns {Token} The token of the open brace. - */ - function getOpenBrace(node) { - if (node.type === "SwitchStatement") { - return sourceCode.getTokenBefore(node.cases[0]); - } - - if (node.type === "StaticBlock") { - return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token - } - - // `BlockStatement` or `ClassBody` - return sourceCode.getFirstToken(node); - } - - /** - * Checks if the given parameter is a comment node - * @param {ASTNode|Token} node An AST node or token - * @returns {boolean} True if node is a comment - */ - function isComment(node) { - return node.type === "Line" || node.type === "Block"; - } - - /** - * Checks if there is padding between two tokens - * @param {Token} first The first token - * @param {Token} second The second token - * @returns {boolean} True if there is at least a line between the tokens - */ - function isPaddingBetweenTokens(first, second) { - return second.loc.start.line - first.loc.end.line >= 2; - } - - - /** - * Checks if the given token has a blank line after it. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is followed by a blank line. - */ - function getFirstBlockToken(token) { - let prev, - first = token; - - do { - prev = first; - first = sourceCode.getTokenAfter(first, { includeComments: true }); - } while (isComment(first) && first.loc.start.line === prev.loc.end.line); - - return first; - } - - /** - * Checks if the given token is preceded by a blank line. - * @param {Token} token The token to check - * @returns {boolean} Whether or not the token is preceded by a blank line - */ - function getLastBlockToken(token) { - let last = token, - next; - - do { - next = last; - last = sourceCode.getTokenBefore(last, { includeComments: true }); - } while (isComment(last) && last.loc.end.line === next.loc.start.line); - - return last; - } - - /** - * Checks if a node should be padded, according to the rule config. - * @param {ASTNode} node The AST node to check. - * @throws {Error} (Unreachable) - * @returns {boolean} True if the node should be padded, false otherwise. - */ - function requirePaddingFor(node) { - switch (node.type) { - case "BlockStatement": - case "StaticBlock": - return options.blocks; - case "SwitchStatement": - return options.switches; - case "ClassBody": - return options.classes; - - /* c8 ignore next */ - default: - throw new Error("unreachable"); - } - } - - /** - * Checks the given BlockStatement node to be padded if the block is not empty. - * @param {ASTNode} node The AST node of a BlockStatement. - * @returns {void} undefined. - */ - function checkPadding(node) { - const openBrace = getOpenBrace(node), - firstBlockToken = getFirstBlockToken(openBrace), - tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { includeComments: true }), - closeBrace = sourceCode.getLastToken(node), - lastBlockToken = getLastBlockToken(closeBrace), - tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { includeComments: true }), - blockHasTopPadding = isPaddingBetweenTokens(tokenBeforeFirst, firstBlockToken), - blockHasBottomPadding = isPaddingBetweenTokens(lastBlockToken, tokenAfterLast); - - if (options.allowSingleLineBlocks && astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast)) { - return; - } - - if (requirePaddingFor(node)) { - - if (!blockHasTopPadding) { - context.report({ - node, - loc: { - start: tokenBeforeFirst.loc.start, - end: firstBlockToken.loc.start - }, - fix(fixer) { - return fixer.insertTextAfter(tokenBeforeFirst, "\n"); - }, - messageId: "alwaysPadBlock" - }); - } - if (!blockHasBottomPadding) { - context.report({ - node, - loc: { - end: tokenAfterLast.loc.start, - start: lastBlockToken.loc.end - }, - fix(fixer) { - return fixer.insertTextBefore(tokenAfterLast, "\n"); - }, - messageId: "alwaysPadBlock" - }); - } - } else { - if (blockHasTopPadding) { - - context.report({ - node, - loc: { - start: tokenBeforeFirst.loc.start, - end: firstBlockToken.loc.start - }, - fix(fixer) { - return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n"); - }, - messageId: "neverPadBlock" - }); - } - - if (blockHasBottomPadding) { - - context.report({ - node, - loc: { - end: tokenAfterLast.loc.start, - start: lastBlockToken.loc.end - }, - messageId: "neverPadBlock", - fix(fixer) { - return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n"); - } - }); - } - } - } - - const rule = {}; - - if (Object.hasOwn(options, "switches")) { - rule.SwitchStatement = function(node) { - if (node.cases.length === 0) { - return; - } - checkPadding(node); - }; - } - - if (Object.hasOwn(options, "blocks")) { - rule.BlockStatement = function(node) { - if (node.body.length === 0) { - return; - } - checkPadding(node); - }; - rule.StaticBlock = rule.BlockStatement; - } - - if (Object.hasOwn(options, "classes")) { - rule.ClassBody = function(node) { - if (node.body.length === 0) { - return; - } - checkPadding(node); - }; - } - - return rule; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "padded-blocks", + url: "https://eslint.style/rules/js/padded-blocks", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require or disallow padding within blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/padded-blocks", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + blocks: { + enum: ["always", "never"], + }, + switches: { + enum: ["always", "never"], + }, + classes: { + enum: ["always", "never"], + }, + }, + additionalProperties: false, + minProperties: 1, + }, + ], + }, + { + type: "object", + properties: { + allowSingleLineBlocks: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + alwaysPadBlock: "Block must be padded by blank lines.", + neverPadBlock: "Block must not be padded by blank lines.", + }, + }, + + create(context) { + const options = {}; + const typeOptions = context.options[0] || "always"; + const exceptOptions = context.options[1] || {}; + + if (typeof typeOptions === "string") { + const shouldHavePadding = typeOptions === "always"; + + options.blocks = shouldHavePadding; + options.switches = shouldHavePadding; + options.classes = shouldHavePadding; + } else { + if (Object.hasOwn(typeOptions, "blocks")) { + options.blocks = typeOptions.blocks === "always"; + } + if (Object.hasOwn(typeOptions, "switches")) { + options.switches = typeOptions.switches === "always"; + } + if (Object.hasOwn(typeOptions, "classes")) { + options.classes = typeOptions.classes === "always"; + } + } + + if (Object.hasOwn(exceptOptions, "allowSingleLineBlocks")) { + options.allowSingleLineBlocks = + exceptOptions.allowSingleLineBlocks === true; + } + + const sourceCode = context.sourceCode; + + /** + * Gets the open brace token from a given node. + * @param {ASTNode} node A BlockStatement or SwitchStatement node from which to get the open brace. + * @returns {Token} The token of the open brace. + */ + function getOpenBrace(node) { + if (node.type === "SwitchStatement") { + return sourceCode.getTokenBefore(node.cases[0]); + } + + if (node.type === "StaticBlock") { + return sourceCode.getFirstToken(node, { skip: 1 }); // skip the `static` token + } + + // `BlockStatement` or `ClassBody` + return sourceCode.getFirstToken(node); + } + + /** + * Checks if the given parameter is a comment node + * @param {ASTNode|Token} node An AST node or token + * @returns {boolean} True if node is a comment + */ + function isComment(node) { + return node.type === "Line" || node.type === "Block"; + } + + /** + * Checks if there is padding between two tokens + * @param {Token} first The first token + * @param {Token} second The second token + * @returns {boolean} True if there is at least a line between the tokens + */ + function isPaddingBetweenTokens(first, second) { + return second.loc.start.line - first.loc.end.line >= 2; + } + + /** + * Checks if the given token has a blank line after it. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is followed by a blank line. + */ + function getFirstBlockToken(token) { + let prev, + first = token; + + do { + prev = first; + first = sourceCode.getTokenAfter(first, { + includeComments: true, + }); + } while ( + isComment(first) && + first.loc.start.line === prev.loc.end.line + ); + + return first; + } + + /** + * Checks if the given token is preceded by a blank line. + * @param {Token} token The token to check + * @returns {boolean} Whether or not the token is preceded by a blank line + */ + function getLastBlockToken(token) { + let last = token, + next; + + do { + next = last; + last = sourceCode.getTokenBefore(last, { + includeComments: true, + }); + } while ( + isComment(last) && + last.loc.end.line === next.loc.start.line + ); + + return last; + } + + /** + * Checks if a node should be padded, according to the rule config. + * @param {ASTNode} node The AST node to check. + * @throws {Error} (Unreachable) + * @returns {boolean} True if the node should be padded, false otherwise. + */ + function requirePaddingFor(node) { + switch (node.type) { + case "BlockStatement": + case "StaticBlock": + return options.blocks; + case "SwitchStatement": + return options.switches; + case "ClassBody": + return options.classes; + + /* c8 ignore next */ + default: + throw new Error("unreachable"); + } + } + + /** + * Checks the given BlockStatement node to be padded if the block is not empty. + * @param {ASTNode} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPadding(node) { + const openBrace = getOpenBrace(node), + firstBlockToken = getFirstBlockToken(openBrace), + tokenBeforeFirst = sourceCode.getTokenBefore(firstBlockToken, { + includeComments: true, + }), + closeBrace = sourceCode.getLastToken(node), + lastBlockToken = getLastBlockToken(closeBrace), + tokenAfterLast = sourceCode.getTokenAfter(lastBlockToken, { + includeComments: true, + }), + blockHasTopPadding = isPaddingBetweenTokens( + tokenBeforeFirst, + firstBlockToken, + ), + blockHasBottomPadding = isPaddingBetweenTokens( + lastBlockToken, + tokenAfterLast, + ); + + if ( + options.allowSingleLineBlocks && + astUtils.isTokenOnSameLine(tokenBeforeFirst, tokenAfterLast) + ) { + return; + } + + if (requirePaddingFor(node)) { + if (!blockHasTopPadding) { + context.report({ + node, + loc: { + start: tokenBeforeFirst.loc.start, + end: firstBlockToken.loc.start, + }, + fix(fixer) { + return fixer.insertTextAfter( + tokenBeforeFirst, + "\n", + ); + }, + messageId: "alwaysPadBlock", + }); + } + if (!blockHasBottomPadding) { + context.report({ + node, + loc: { + end: tokenAfterLast.loc.start, + start: lastBlockToken.loc.end, + }, + fix(fixer) { + return fixer.insertTextBefore(tokenAfterLast, "\n"); + }, + messageId: "alwaysPadBlock", + }); + } + } else { + if (blockHasTopPadding) { + context.report({ + node, + loc: { + start: tokenBeforeFirst.loc.start, + end: firstBlockToken.loc.start, + }, + fix(fixer) { + return fixer.replaceTextRange( + [ + tokenBeforeFirst.range[1], + firstBlockToken.range[0] - + firstBlockToken.loc.start.column, + ], + "\n", + ); + }, + messageId: "neverPadBlock", + }); + } + + if (blockHasBottomPadding) { + context.report({ + node, + loc: { + end: tokenAfterLast.loc.start, + start: lastBlockToken.loc.end, + }, + messageId: "neverPadBlock", + fix(fixer) { + return fixer.replaceTextRange( + [ + lastBlockToken.range[1], + tokenAfterLast.range[0] - + tokenAfterLast.loc.start.column, + ], + "\n", + ); + }, + }); + } + } + } + + const rule = {}; + + if (Object.hasOwn(options, "switches")) { + rule.SwitchStatement = function (node) { + if (node.cases.length === 0) { + return; + } + checkPadding(node); + }; + } + + if (Object.hasOwn(options, "blocks")) { + rule.BlockStatement = function (node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + rule.StaticBlock = rule.BlockStatement; + } + + if (Object.hasOwn(options, "classes")) { + rule.ClassBody = function (node) { + if (node.body.length === 0) { + return; + } + checkPadding(node); + }; + } + + return rule; + }, }; diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js index b0421e5d9f0c..9bb7d64e91c8 100644 --- a/lib/rules/padding-line-between-statements.js +++ b/lib/rules/padding-line-between-statements.js @@ -18,8 +18,8 @@ const astUtils = require("./utils/ast-utils"); const LT = `[${Array.from(astUtils.LINEBREAKS).join("")}]`; const PADDING_LINE_SEQUENCE = new RegExp( - String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, - "u" + String.raw`^(\s*?${LT})\s*${LT}(\s*;?)$`, + "u", ); const CJS_EXPORT = /^(?:module\s*\.\s*)?exports(?:\s*\.|\s*\[|$)/u; const CJS_IMPORT = /^require\(/u; @@ -31,10 +31,10 @@ const CJS_IMPORT = /^require\(/u; * @private */ function newKeywordTester(keyword) { - return { - test: (node, sourceCode) => - sourceCode.getFirstToken(node).value === keyword - }; + return { + test: (node, sourceCode) => + sourceCode.getFirstToken(node).value === keyword, + }; } /** @@ -44,11 +44,11 @@ function newKeywordTester(keyword) { * @private */ function newSinglelineKeywordTester(keyword) { - return { - test: (node, sourceCode) => - node.loc.start.line === node.loc.end.line && - sourceCode.getFirstToken(node).value === keyword - }; + return { + test: (node, sourceCode) => + node.loc.start.line === node.loc.end.line && + sourceCode.getFirstToken(node).value === keyword, + }; } /** @@ -58,11 +58,11 @@ function newSinglelineKeywordTester(keyword) { * @private */ function newMultilineKeywordTester(keyword) { - return { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - sourceCode.getFirstToken(node).value === keyword - }; + return { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + sourceCode.getFirstToken(node).value === keyword, + }; } /** @@ -72,10 +72,9 @@ function newMultilineKeywordTester(keyword) { * @private */ function newNodeTypeTester(type) { - return { - test: node => - node.type === type - }; + return { + test: node => node.type === type, + }; } /** @@ -85,15 +84,17 @@ function newNodeTypeTester(type) { * @private */ function isIIFEStatement(node) { - if (node.type === "ExpressionStatement") { - let call = astUtils.skipChainExpression(node.expression); - - if (call.type === "UnaryExpression") { - call = astUtils.skipChainExpression(call.argument); - } - return call.type === "CallExpression" && astUtils.isFunction(call.callee); - } - return false; + if (node.type === "ExpressionStatement") { + let call = astUtils.skipChainExpression(node.expression); + + if (call.type === "UnaryExpression") { + call = astUtils.skipChainExpression(call.argument); + } + return ( + call.type === "CallExpression" && astUtils.isFunction(call.callee) + ); + } + return false; } /** @@ -105,30 +106,37 @@ function isIIFEStatement(node) { * @private */ function isBlockLikeStatement(sourceCode, node) { - - // do-while with a block is a block-like statement. - if (node.type === "DoWhileStatement" && node.body.type === "BlockStatement") { - return true; - } - - /* - * IIFE is a block-like statement specially from - * JSCS#disallowPaddingNewLinesAfterBlocks. - */ - if (isIIFEStatement(node)) { - return true; - } - - // Checks the last token is a closing brace of blocks. - const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken); - const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken) - ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) - : null; - - return Boolean(belongingNode) && ( - belongingNode.type === "BlockStatement" || - belongingNode.type === "SwitchStatement" - ); + // do-while with a block is a block-like statement. + if ( + node.type === "DoWhileStatement" && + node.body.type === "BlockStatement" + ) { + return true; + } + + /* + * IIFE is a block-like statement specially from + * JSCS#disallowPaddingNewLinesAfterBlocks. + */ + if (isIIFEStatement(node)) { + return true; + } + + // Checks the last token is a closing brace of blocks. + const lastToken = sourceCode.getLastToken( + node, + astUtils.isNotSemicolonToken, + ); + const belongingNode = + lastToken && astUtils.isClosingBraceToken(lastToken) + ? sourceCode.getNodeByRangeIndex(lastToken.range[0]) + : null; + + return ( + Boolean(belongingNode) && + (belongingNode.type === "BlockStatement" || + belongingNode.type === "SwitchStatement") + ); } /** @@ -145,19 +153,19 @@ function isBlockLikeStatement(sourceCode, node) { * @private */ function getActualLastToken(sourceCode, node) { - const semiToken = sourceCode.getLastToken(node); - const prevToken = sourceCode.getTokenBefore(semiToken); - const nextToken = sourceCode.getTokenAfter(semiToken); - const isSemicolonLessStyle = Boolean( - prevToken && - nextToken && - prevToken.range[0] >= node.range[0] && - astUtils.isSemicolonToken(semiToken) && - semiToken.loc.start.line !== prevToken.loc.end.line && - semiToken.loc.end.line === nextToken.loc.start.line - ); - - return isSemicolonLessStyle ? prevToken : semiToken; + const semiToken = sourceCode.getLastToken(node); + const prevToken = sourceCode.getTokenBefore(semiToken); + const nextToken = sourceCode.getTokenAfter(semiToken); + const isSemicolonLessStyle = Boolean( + prevToken && + nextToken && + prevToken.range[0] >= node.range[0] && + astUtils.isSemicolonToken(semiToken) && + semiToken.loc.start.line !== prevToken.loc.end.line && + semiToken.loc.end.line === nextToken.loc.start.line, + ); + + return isSemicolonLessStyle ? prevToken : semiToken; } /** @@ -169,7 +177,7 @@ function getActualLastToken(sourceCode, node) { * @private */ function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) { - return trailingSpaces + indentSpaces; + return trailingSpaces + indentSpaces; } /** @@ -178,8 +186,7 @@ function replacerToRemovePaddingLines(_, trailingSpaces, indentSpaces) { * @returns {void} * @private */ -function verifyForAny() { -} +function verifyForAny() {} /** * Check and report statements for `never` configuration. @@ -195,29 +202,29 @@ function verifyForAny() { * @private */ function verifyForNever(context, _, nextNode, paddingLines) { - if (paddingLines.length === 0) { - return; - } - - context.report({ - node: nextNode, - messageId: "unexpectedBlankLine", - fix(fixer) { - if (paddingLines.length >= 2) { - return null; - } - - const prevToken = paddingLines[0][0]; - const nextToken = paddingLines[0][1]; - const start = prevToken.range[1]; - const end = nextToken.range[0]; - const text = context.sourceCode.text - .slice(start, end) - .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); - - return fixer.replaceTextRange([start, end], text); - } - }); + if (paddingLines.length === 0) { + return; + } + + context.report({ + node: nextNode, + messageId: "unexpectedBlankLine", + fix(fixer) { + if (paddingLines.length >= 2) { + return null; + } + + const prevToken = paddingLines[0][0]; + const nextToken = paddingLines[0][1]; + const start = prevToken.range[1]; + const end = nextToken.range[0]; + const text = context.sourceCode.text + .slice(start, end) + .replace(PADDING_LINE_SEQUENCE, replacerToRemovePaddingLines); + + return fixer.replaceTextRange([start, end], text); + }, + }); } /** @@ -234,58 +241,55 @@ function verifyForNever(context, _, nextNode, paddingLines) { * @private */ function verifyForAlways(context, prevNode, nextNode, paddingLines) { - if (paddingLines.length > 0) { - return; - } - - context.report({ - node: nextNode, - messageId: "expectedBlankLine", - fix(fixer) { - const sourceCode = context.sourceCode; - let prevToken = getActualLastToken(sourceCode, prevNode); - const nextToken = sourceCode.getFirstTokenBetween( - prevToken, - nextNode, - { - includeComments: true, - - /** - * Skip the trailing comments of the previous node. - * This inserts a blank line after the last trailing comment. - * - * For example: - * - * foo(); // trailing comment. - * // comment. - * bar(); - * - * Get fixed to: - * - * foo(); // trailing comment. - * - * // comment. - * bar(); - * @param {Token} token The token to check. - * @returns {boolean} `true` if the token is not a trailing comment. - * @private - */ - filter(token) { - if (astUtils.isTokenOnSameLine(prevToken, token)) { - prevToken = token; - return false; - } - return true; - } - } - ) || nextNode; - const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken) - ? "\n\n" - : "\n"; - - return fixer.insertTextAfter(prevToken, insertText); - } - }); + if (paddingLines.length > 0) { + return; + } + + context.report({ + node: nextNode, + messageId: "expectedBlankLine", + fix(fixer) { + const sourceCode = context.sourceCode; + let prevToken = getActualLastToken(sourceCode, prevNode); + const nextToken = + sourceCode.getFirstTokenBetween(prevToken, nextNode, { + includeComments: true, + + /** + * Skip the trailing comments of the previous node. + * This inserts a blank line after the last trailing comment. + * + * For example: + * + * foo(); // trailing comment. + * // comment. + * bar(); + * + * Get fixed to: + * + * foo(); // trailing comment. + * + * // comment. + * bar(); + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is not a trailing comment. + * @private + */ + filter(token) { + if (astUtils.isTokenOnSameLine(prevToken, token)) { + prevToken = token; + return false; + } + return true; + }, + }) || nextNode; + const insertText = astUtils.isTokenOnSameLine(prevToken, nextToken) + ? "\n\n" + : "\n"; + + return fixer.insertTextAfter(prevToken, insertText); + }, + }); } /** @@ -295,9 +299,9 @@ function verifyForAlways(context, prevNode, nextNode, paddingLines) { * @private */ const PaddingTypes = { - any: { verify: verifyForAny }, - never: { verify: verifyForNever }, - always: { verify: verifyForAlways } + any: { verify: verifyForAny }, + never: { verify: verifyForNever }, + always: { verify: verifyForAlways }, }; /** @@ -306,75 +310,76 @@ const PaddingTypes = { * @private */ const StatementTypes = { - "*": { test: () => true }, - "block-like": { - test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node) - }, - "cjs-export": { - test: (node, sourceCode) => - node.type === "ExpressionStatement" && - node.expression.type === "AssignmentExpression" && - CJS_EXPORT.test(sourceCode.getText(node.expression.left)) - }, - "cjs-import": { - test: (node, sourceCode) => - node.type === "VariableDeclaration" && - node.declarations.length > 0 && - Boolean(node.declarations[0].init) && - CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)) - }, - directive: { - test: astUtils.isDirective - }, - expression: { - test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node) - }, - iife: { - test: isIIFEStatement - }, - "multiline-block-like": { - test: (node, sourceCode) => - node.loc.start.line !== node.loc.end.line && - isBlockLikeStatement(sourceCode, node) - }, - "multiline-expression": { - test: node => - node.loc.start.line !== node.loc.end.line && - node.type === "ExpressionStatement" && - !astUtils.isDirective(node) - }, - - "multiline-const": newMultilineKeywordTester("const"), - "multiline-let": newMultilineKeywordTester("let"), - "multiline-var": newMultilineKeywordTester("var"), - "singleline-const": newSinglelineKeywordTester("const"), - "singleline-let": newSinglelineKeywordTester("let"), - "singleline-var": newSinglelineKeywordTester("var"), - - block: newNodeTypeTester("BlockStatement"), - empty: newNodeTypeTester("EmptyStatement"), - function: newNodeTypeTester("FunctionDeclaration"), - - break: newKeywordTester("break"), - case: newKeywordTester("case"), - class: newKeywordTester("class"), - const: newKeywordTester("const"), - continue: newKeywordTester("continue"), - debugger: newKeywordTester("debugger"), - default: newKeywordTester("default"), - do: newKeywordTester("do"), - export: newKeywordTester("export"), - for: newKeywordTester("for"), - if: newKeywordTester("if"), - import: newKeywordTester("import"), - let: newKeywordTester("let"), - return: newKeywordTester("return"), - switch: newKeywordTester("switch"), - throw: newKeywordTester("throw"), - try: newKeywordTester("try"), - var: newKeywordTester("var"), - while: newKeywordTester("while"), - with: newKeywordTester("with") + "*": { test: () => true }, + "block-like": { + test: (node, sourceCode) => isBlockLikeStatement(sourceCode, node), + }, + "cjs-export": { + test: (node, sourceCode) => + node.type === "ExpressionStatement" && + node.expression.type === "AssignmentExpression" && + CJS_EXPORT.test(sourceCode.getText(node.expression.left)), + }, + "cjs-import": { + test: (node, sourceCode) => + node.type === "VariableDeclaration" && + node.declarations.length > 0 && + Boolean(node.declarations[0].init) && + CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init)), + }, + directive: { + test: astUtils.isDirective, + }, + expression: { + test: node => + node.type === "ExpressionStatement" && !astUtils.isDirective(node), + }, + iife: { + test: isIIFEStatement, + }, + "multiline-block-like": { + test: (node, sourceCode) => + node.loc.start.line !== node.loc.end.line && + isBlockLikeStatement(sourceCode, node), + }, + "multiline-expression": { + test: node => + node.loc.start.line !== node.loc.end.line && + node.type === "ExpressionStatement" && + !astUtils.isDirective(node), + }, + + "multiline-const": newMultilineKeywordTester("const"), + "multiline-let": newMultilineKeywordTester("let"), + "multiline-var": newMultilineKeywordTester("var"), + "singleline-const": newSinglelineKeywordTester("const"), + "singleline-let": newSinglelineKeywordTester("let"), + "singleline-var": newSinglelineKeywordTester("var"), + + block: newNodeTypeTester("BlockStatement"), + empty: newNodeTypeTester("EmptyStatement"), + function: newNodeTypeTester("FunctionDeclaration"), + + break: newKeywordTester("break"), + case: newKeywordTester("case"), + class: newKeywordTester("class"), + const: newKeywordTester("const"), + continue: newKeywordTester("continue"), + debugger: newKeywordTester("debugger"), + default: newKeywordTester("default"), + do: newKeywordTester("do"), + export: newKeywordTester("export"), + for: newKeywordTester("for"), + if: newKeywordTester("if"), + import: newKeywordTester("import"), + let: newKeywordTester("let"), + return: newKeywordTester("return"), + switch: newKeywordTester("switch"), + throw: newKeywordTester("throw"), + try: newKeywordTester("try"), + var: newKeywordTester("var"), + while: newKeywordTester("while"), + with: newKeywordTester("with"), }; //------------------------------------------------------------------------------ @@ -383,226 +388,225 @@ const StatementTypes = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "padding-line-between-statements", - url: "https://eslint.style/rules/js/padding-line-between-statements" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow padding lines between statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/padding-line-between-statements" - }, - - fixable: "whitespace", - - schema: { - definitions: { - paddingType: { - enum: Object.keys(PaddingTypes) - }, - statementType: { - anyOf: [ - { enum: Object.keys(StatementTypes) }, - { - type: "array", - items: { enum: Object.keys(StatementTypes) }, - minItems: 1, - uniqueItems: true - } - ] - } - }, - type: "array", - items: { - type: "object", - properties: { - blankLine: { $ref: "#/definitions/paddingType" }, - prev: { $ref: "#/definitions/statementType" }, - next: { $ref: "#/definitions/statementType" } - }, - additionalProperties: false, - required: ["blankLine", "prev", "next"] - } - }, - - messages: { - unexpectedBlankLine: "Unexpected blank line before this statement.", - expectedBlankLine: "Expected blank line before this statement." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const configureList = context.options || []; - let scopeInfo = null; - - /** - * Processes to enter to new scope. - * This manages the current previous statement. - * @returns {void} - * @private - */ - function enterScope() { - scopeInfo = { - upper: scopeInfo, - prevNode: null - }; - } - - /** - * Processes to exit from the current scope. - * @returns {void} - * @private - */ - function exitScope() { - scopeInfo = scopeInfo.upper; - } - - /** - * Checks whether the given node matches the given type. - * @param {ASTNode} node The statement node to check. - * @param {string|string[]} type The statement type to check. - * @returns {boolean} `true` if the statement node matched the type. - * @private - */ - function match(node, type) { - let innerStatementNode = node; - - while (innerStatementNode.type === "LabeledStatement") { - innerStatementNode = innerStatementNode.body; - } - if (Array.isArray(type)) { - return type.some(match.bind(null, innerStatementNode)); - } - return StatementTypes[type].test(innerStatementNode, sourceCode); - } - - /** - * Finds the last matched configure from configureList. - * @param {ASTNode} prevNode The previous statement to match. - * @param {ASTNode} nextNode The current statement to match. - * @returns {Object} The tester of the last matched configure. - * @private - */ - function getPaddingType(prevNode, nextNode) { - for (let i = configureList.length - 1; i >= 0; --i) { - const configure = configureList[i]; - const matched = - match(prevNode, configure.prev) && - match(nextNode, configure.next); - - if (matched) { - return PaddingTypes[configure.blankLine]; - } - } - return PaddingTypes.any; - } - - /** - * Gets padding line sequences between the given 2 statements. - * Comments are separators of the padding line sequences. - * @param {ASTNode} prevNode The previous statement to count. - * @param {ASTNode} nextNode The current statement to count. - * @returns {Array} The array of token pairs. - * @private - */ - function getPaddingLineSequences(prevNode, nextNode) { - const pairs = []; - let prevToken = getActualLastToken(sourceCode, prevNode); - - if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { - do { - const token = sourceCode.getTokenAfter( - prevToken, - { includeComments: true } - ); - - if (token.loc.start.line - prevToken.loc.end.line >= 2) { - pairs.push([prevToken, token]); - } - prevToken = token; - - } while (prevToken.range[0] < nextNode.range[0]); - } - - return pairs; - } - - /** - * Verify padding lines between the given node and the previous node. - * @param {ASTNode} node The node to verify. - * @returns {void} - * @private - */ - function verify(node) { - const parentType = node.parent.type; - const validParent = - astUtils.STATEMENT_LIST_PARENTS.has(parentType) || - parentType === "SwitchStatement"; - - if (!validParent) { - return; - } - - // Save this node as the current previous statement. - const prevNode = scopeInfo.prevNode; - - // Verify. - if (prevNode) { - const type = getPaddingType(prevNode, node); - const paddingLines = getPaddingLineSequences(prevNode, node); - - type.verify(context, prevNode, node, paddingLines); - } - - scopeInfo.prevNode = node; - } - - /** - * Verify padding lines between the given node and the previous node. - * Then process to enter to new scope. - * @param {ASTNode} node The node to verify. - * @returns {void} - * @private - */ - function verifyThenEnterScope(node) { - verify(node); - enterScope(); - } - - return { - Program: enterScope, - BlockStatement: enterScope, - SwitchStatement: enterScope, - StaticBlock: enterScope, - "Program:exit": exitScope, - "BlockStatement:exit": exitScope, - "SwitchStatement:exit": exitScope, - "StaticBlock:exit": exitScope, - - ":statement": verify, - - SwitchCase: verifyThenEnterScope, - "SwitchCase:exit": exitScope - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "padding-line-between-statements", + url: "https://eslint.style/rules/js/padding-line-between-statements", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require or disallow padding lines between statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/padding-line-between-statements", + }, + + fixable: "whitespace", + + schema: { + definitions: { + paddingType: { + enum: Object.keys(PaddingTypes), + }, + statementType: { + anyOf: [ + { enum: Object.keys(StatementTypes) }, + { + type: "array", + items: { enum: Object.keys(StatementTypes) }, + minItems: 1, + uniqueItems: true, + }, + ], + }, + }, + type: "array", + items: { + type: "object", + properties: { + blankLine: { $ref: "#/definitions/paddingType" }, + prev: { $ref: "#/definitions/statementType" }, + next: { $ref: "#/definitions/statementType" }, + }, + additionalProperties: false, + required: ["blankLine", "prev", "next"], + }, + }, + + messages: { + unexpectedBlankLine: "Unexpected blank line before this statement.", + expectedBlankLine: "Expected blank line before this statement.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const configureList = context.options || []; + let scopeInfo = null; + + /** + * Processes to enter to new scope. + * This manages the current previous statement. + * @returns {void} + * @private + */ + function enterScope() { + scopeInfo = { + upper: scopeInfo, + prevNode: null, + }; + } + + /** + * Processes to exit from the current scope. + * @returns {void} + * @private + */ + function exitScope() { + scopeInfo = scopeInfo.upper; + } + + /** + * Checks whether the given node matches the given type. + * @param {ASTNode} node The statement node to check. + * @param {string|string[]} type The statement type to check. + * @returns {boolean} `true` if the statement node matched the type. + * @private + */ + function match(node, type) { + let innerStatementNode = node; + + while (innerStatementNode.type === "LabeledStatement") { + innerStatementNode = innerStatementNode.body; + } + if (Array.isArray(type)) { + return type.some(match.bind(null, innerStatementNode)); + } + return StatementTypes[type].test(innerStatementNode, sourceCode); + } + + /** + * Finds the last matched configure from configureList. + * @param {ASTNode} prevNode The previous statement to match. + * @param {ASTNode} nextNode The current statement to match. + * @returns {Object} The tester of the last matched configure. + * @private + */ + function getPaddingType(prevNode, nextNode) { + for (let i = configureList.length - 1; i >= 0; --i) { + const configure = configureList[i]; + const matched = + match(prevNode, configure.prev) && + match(nextNode, configure.next); + + if (matched) { + return PaddingTypes[configure.blankLine]; + } + } + return PaddingTypes.any; + } + + /** + * Gets padding line sequences between the given 2 statements. + * Comments are separators of the padding line sequences. + * @param {ASTNode} prevNode The previous statement to count. + * @param {ASTNode} nextNode The current statement to count. + * @returns {Array} The array of token pairs. + * @private + */ + function getPaddingLineSequences(prevNode, nextNode) { + const pairs = []; + let prevToken = getActualLastToken(sourceCode, prevNode); + + if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) { + do { + const token = sourceCode.getTokenAfter(prevToken, { + includeComments: true, + }); + + if (token.loc.start.line - prevToken.loc.end.line >= 2) { + pairs.push([prevToken, token]); + } + prevToken = token; + } while (prevToken.range[0] < nextNode.range[0]); + } + + return pairs; + } + + /** + * Verify padding lines between the given node and the previous node. + * @param {ASTNode} node The node to verify. + * @returns {void} + * @private + */ + function verify(node) { + const parentType = node.parent.type; + const validParent = + astUtils.STATEMENT_LIST_PARENTS.has(parentType) || + parentType === "SwitchStatement"; + + if (!validParent) { + return; + } + + // Save this node as the current previous statement. + const prevNode = scopeInfo.prevNode; + + // Verify. + if (prevNode) { + const type = getPaddingType(prevNode, node); + const paddingLines = getPaddingLineSequences(prevNode, node); + + type.verify(context, prevNode, node, paddingLines); + } + + scopeInfo.prevNode = node; + } + + /** + * Verify padding lines between the given node and the previous node. + * Then process to enter to new scope. + * @param {ASTNode} node The node to verify. + * @returns {void} + * @private + */ + function verifyThenEnterScope(node) { + verify(node); + enterScope(); + } + + return { + Program: enterScope, + BlockStatement: enterScope, + SwitchStatement: enterScope, + StaticBlock: enterScope, + "Program:exit": exitScope, + "BlockStatement:exit": exitScope, + "SwitchStatement:exit": exitScope, + "StaticBlock:exit": exitScope, + + ":statement": verify, + + SwitchCase: verifyThenEnterScope, + "SwitchCase:exit": exitScope, + }; + }, }; diff --git a/lib/rules/prefer-arrow-callback.js b/lib/rules/prefer-arrow-callback.js index 982246e0857f..ecccea44c0bb 100644 --- a/lib/rules/prefer-arrow-callback.js +++ b/lib/rules/prefer-arrow-callback.js @@ -17,7 +17,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the variable is a function name. */ function isFunctionName(variable) { - return variable && variable.defs[0].type === "FunctionName"; + return variable && variable.defs[0].type === "FunctionName"; } /** @@ -28,7 +28,7 @@ function isFunctionName(variable) { * @returns {boolean} `true` if the node is the specific value. */ function checkMetaProperty(node, metaName, propertyName) { - return node.meta.name === metaName && node.property.name === propertyName; + return node.meta.name === metaName && node.property.name === propertyName; } /** @@ -37,24 +37,23 @@ function checkMetaProperty(node, metaName, propertyName) { * @returns {eslint-scope.Variable} The found variable object. */ function getVariableOfArguments(scope) { - const variables = scope.variables; - - for (let i = 0; i < variables.length; ++i) { - const variable = variables[i]; - - if (variable.name === "arguments") { - - /* - * If there was a parameter which is named "arguments", the - * implicit "arguments" is not defined. - * So does fast return with null. - */ - return (variable.identifiers.length === 0) ? variable : null; - } - } - - /* c8 ignore next */ - return null; + const variables = scope.variables; + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + + if (variable.name === "arguments") { + /* + * If there was a parameter which is named "arguments", the + * implicit "arguments" is not defined. + * So does fast return with null. + */ + return variable.identifiers.length === 0 ? variable : null; + } + } + + /* c8 ignore next */ + return null; } /** @@ -66,68 +65,68 @@ function getVariableOfArguments(scope) { * {boolean} retv.isLexicalThis - `true` if the node is with `.bind(this)`. */ function getCallbackInfo(node) { - const retv = { isCallback: false, isLexicalThis: false }; - let currentNode = node; - let parent = node.parent; - let bound = false; - - while (currentNode) { - switch (parent.type) { - - // Checks parents recursively. - - case "LogicalExpression": - case "ChainExpression": - case "ConditionalExpression": - break; - - // Checks whether the parent node is `.bind(this)` call. - case "MemberExpression": - if ( - parent.object === currentNode && - !parent.property.computed && - parent.property.type === "Identifier" && - parent.property.name === "bind" - ) { - const maybeCallee = parent.parent.type === "ChainExpression" - ? parent.parent - : parent; - - if (astUtils.isCallee(maybeCallee)) { - if (!bound) { - bound = true; // Use only the first `.bind()` to make `isLexicalThis` value. - retv.isLexicalThis = ( - maybeCallee.parent.arguments.length === 1 && - maybeCallee.parent.arguments[0].type === "ThisExpression" - ); - } - parent = maybeCallee.parent; - } else { - return retv; - } - } else { - return retv; - } - break; - - // Checks whether the node is a callback. - case "CallExpression": - case "NewExpression": - if (parent.callee !== currentNode) { - retv.isCallback = true; - } - return retv; - - default: - return retv; - } - - currentNode = parent; - parent = parent.parent; - } - - /* c8 ignore next */ - throw new Error("unreachable"); + const retv = { isCallback: false, isLexicalThis: false }; + let currentNode = node; + let parent = node.parent; + let bound = false; + + while (currentNode) { + switch (parent.type) { + // Checks parents recursively. + + case "LogicalExpression": + case "ChainExpression": + case "ConditionalExpression": + break; + + // Checks whether the parent node is `.bind(this)` call. + case "MemberExpression": + if ( + parent.object === currentNode && + !parent.property.computed && + parent.property.type === "Identifier" && + parent.property.name === "bind" + ) { + const maybeCallee = + parent.parent.type === "ChainExpression" + ? parent.parent + : parent; + + if (astUtils.isCallee(maybeCallee)) { + if (!bound) { + bound = true; // Use only the first `.bind()` to make `isLexicalThis` value. + retv.isLexicalThis = + maybeCallee.parent.arguments.length === 1 && + maybeCallee.parent.arguments[0].type === + "ThisExpression"; + } + parent = maybeCallee.parent; + } else { + return retv; + } + } else { + return retv; + } + break; + + // Checks whether the node is a callback. + case "CallExpression": + case "NewExpression": + if (parent.callee !== currentNode) { + retv.isCallback = true; + } + return retv; + + default: + return retv; + } + + currentNode = parent; + parent = parent.parent; + } + + /* c8 ignore next */ + throw new Error("unreachable"); } /** @@ -138,7 +137,10 @@ function getCallbackInfo(node) { * @returns {boolean} `true` if the list of parameters contains any duplicates */ function hasDuplicateParams(paramsList) { - return paramsList.every(param => param.type === "Identifier") && paramsList.length !== new Set(paramsList.map(param => param.name)).size; + return ( + paramsList.every(param => param.type === "Identifier") && + paramsList.length !== new Set(paramsList.map(param => param.name)).size + ); } //------------------------------------------------------------------------------ @@ -147,233 +149,280 @@ function hasDuplicateParams(paramsList) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [ - { - type: "object", - properties: { - allowNamedFunctions: { - type: "boolean" - }, - allowUnboundThis: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - preferArrowCallback: "Unexpected function expression." - } - }, - - create(context) { - const [{ allowNamedFunctions, allowUnboundThis }] = context.options; - const sourceCode = context.sourceCode; - - /* - * {Array<{this: boolean, super: boolean, meta: boolean}>} - * - this - A flag which shows there are one or more ThisExpression. - * - super - A flag which shows there are one or more Super. - * - meta - A flag which shows there are one or more MethProperty. - */ - let stack = []; - - /** - * Pushes new function scope with all `false` flags. - * @returns {void} - */ - function enterScope() { - stack.push({ this: false, super: false, meta: false }); - } - - /** - * Pops a function scope from the stack. - * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope. - */ - function exitScope() { - return stack.pop(); - } - - return { - - // Reset internal state. - Program() { - stack = []; - }, - - // If there are below, it cannot replace with arrow functions merely. - ThisExpression() { - const info = stack.at(-1); - - if (info) { - info.this = true; - } - }, - - Super() { - const info = stack.at(-1); - - if (info) { - info.super = true; - } - }, - - MetaProperty(node) { - const info = stack.at(-1); - - if (info && checkMetaProperty(node, "new", "target")) { - info.meta = true; - } - }, - - // To skip nested scopes. - FunctionDeclaration: enterScope, - "FunctionDeclaration:exit": exitScope, - - // Main. - FunctionExpression: enterScope, - "FunctionExpression:exit"(node) { - const scopeInfo = exitScope(); - - // Skip named function expressions - if (allowNamedFunctions && node.id && node.id.name) { - return; - } - - // Skip generators. - if (node.generator) { - return; - } - - // Skip recursive functions. - const nameVar = sourceCode.getDeclaredVariables(node)[0]; - - if (isFunctionName(nameVar) && nameVar.references.length > 0) { - return; - } - - // Skip if it's using arguments. - const variable = getVariableOfArguments(sourceCode.getScope(node)); - - if (variable && variable.references.length > 0) { - return; - } - - // Reports if it's a callback which can replace with arrows. - const callbackInfo = getCallbackInfo(node); - - if (callbackInfo.isCallback && - (!allowUnboundThis || !scopeInfo.this || callbackInfo.isLexicalThis) && - !scopeInfo.super && - !scopeInfo.meta - ) { - context.report({ - node, - messageId: "preferArrowCallback", - *fix(fixer) { - if ((!callbackInfo.isLexicalThis && scopeInfo.this) || hasDuplicateParams(node.params)) { - - /* - * If the callback function does not have .bind(this) and contains a reference to `this`, there - * is no way to determine what `this` should be, so don't perform any fixes. - * If the callback function has duplicates in its list of parameters (possible in sloppy mode), - * don't replace it with an arrow function, because this is a SyntaxError with arrow functions. - */ - return; - } - - // Remove `.bind(this)` if exists. - if (callbackInfo.isLexicalThis) { - const memberNode = node.parent; - - /* - * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically. - * E.g. `(foo || function(){}).bind(this)` - */ - if (memberNode.type !== "MemberExpression") { - return; - } - - const callNode = memberNode.parent; - const firstTokenToRemove = sourceCode.getTokenAfter(memberNode.object, astUtils.isNotClosingParenToken); - const lastTokenToRemove = sourceCode.getLastToken(callNode); - - /* - * If the member expression is parenthesized, don't remove the right paren. - * E.g. `(function(){}.bind)(this)` - * ^^^^^^^^^^^^ - */ - if (astUtils.isParenthesised(sourceCode, memberNode)) { - return; - } - - // If comments exist in the `.bind(this)`, don't remove those. - if (sourceCode.commentsExistBetween(firstTokenToRemove, lastTokenToRemove)) { - return; - } - - yield fixer.removeRange([firstTokenToRemove.range[0], lastTokenToRemove.range[1]]); - } - - // Convert the function expression to an arrow function. - const functionToken = sourceCode.getFirstToken(node, node.async ? 1 : 0); - const leftParenToken = sourceCode.getTokenAfter(functionToken, astUtils.isOpeningParenToken); - const tokenBeforeBody = sourceCode.getTokenBefore(node.body); - - if (sourceCode.commentsExistBetween(functionToken, leftParenToken)) { - - // Remove only extra tokens to keep comments. - yield fixer.remove(functionToken); - if (node.id) { - yield fixer.remove(node.id); - } - } else { - - // Remove extra tokens and spaces. - yield fixer.removeRange([functionToken.range[0], leftParenToken.range[0]]); - } - yield fixer.insertTextAfter(tokenBeforeBody, " =>"); - - // Get the node that will become the new arrow function. - let replacedNode = callbackInfo.isLexicalThis ? node.parent.parent : node; - - if (replacedNode.type === "ChainExpression") { - replacedNode = replacedNode.parent; - } - - /* - * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then - * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even - * though `foo || function() {}` is valid. - */ - if ( - replacedNode.parent.type !== "CallExpression" && - replacedNode.parent.type !== "ConditionalExpression" && - !astUtils.isParenthesised(sourceCode, replacedNode) && - !astUtils.isParenthesised(sourceCode, node) - ) { - yield fixer.insertTextBefore(replacedNode, "("); - yield fixer.insertTextAfter(replacedNode, ")"); - } - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [ + { + type: "object", + properties: { + allowNamedFunctions: { + type: "boolean", + }, + allowUnboundThis: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + preferArrowCallback: "Unexpected function expression.", + }, + }, + + create(context) { + const [{ allowNamedFunctions, allowUnboundThis }] = context.options; + const sourceCode = context.sourceCode; + + /* + * {Array<{this: boolean, super: boolean, meta: boolean}>} + * - this - A flag which shows there are one or more ThisExpression. + * - super - A flag which shows there are one or more Super. + * - meta - A flag which shows there are one or more MethProperty. + */ + let stack = []; + + /** + * Pushes new function scope with all `false` flags. + * @returns {void} + */ + function enterScope() { + stack.push({ this: false, super: false, meta: false }); + } + + /** + * Pops a function scope from the stack. + * @returns {{this: boolean, super: boolean, meta: boolean}} The information of the last scope. + */ + function exitScope() { + return stack.pop(); + } + + return { + // Reset internal state. + Program() { + stack = []; + }, + + // If there are below, it cannot replace with arrow functions merely. + ThisExpression() { + const info = stack.at(-1); + + if (info) { + info.this = true; + } + }, + + Super() { + const info = stack.at(-1); + + if (info) { + info.super = true; + } + }, + + MetaProperty(node) { + const info = stack.at(-1); + + if (info && checkMetaProperty(node, "new", "target")) { + info.meta = true; + } + }, + + // To skip nested scopes. + FunctionDeclaration: enterScope, + "FunctionDeclaration:exit": exitScope, + + // Main. + FunctionExpression: enterScope, + "FunctionExpression:exit"(node) { + const scopeInfo = exitScope(); + + // Skip named function expressions + if (allowNamedFunctions && node.id && node.id.name) { + return; + } + + // Skip generators. + if (node.generator) { + return; + } + + // Skip recursive functions. + const nameVar = sourceCode.getDeclaredVariables(node)[0]; + + if (isFunctionName(nameVar) && nameVar.references.length > 0) { + return; + } + + // Skip if it's using arguments. + const variable = getVariableOfArguments( + sourceCode.getScope(node), + ); + + if (variable && variable.references.length > 0) { + return; + } + + // Reports if it's a callback which can replace with arrows. + const callbackInfo = getCallbackInfo(node); + + if ( + callbackInfo.isCallback && + (!allowUnboundThis || + !scopeInfo.this || + callbackInfo.isLexicalThis) && + !scopeInfo.super && + !scopeInfo.meta + ) { + context.report({ + node, + messageId: "preferArrowCallback", + *fix(fixer) { + if ( + (!callbackInfo.isLexicalThis && + scopeInfo.this) || + hasDuplicateParams(node.params) + ) { + /* + * If the callback function does not have .bind(this) and contains a reference to `this`, there + * is no way to determine what `this` should be, so don't perform any fixes. + * If the callback function has duplicates in its list of parameters (possible in sloppy mode), + * don't replace it with an arrow function, because this is a SyntaxError with arrow functions. + */ + return; + } + + // Remove `.bind(this)` if exists. + if (callbackInfo.isLexicalThis) { + const memberNode = node.parent; + + /* + * If `.bind(this)` exists but the parent is not `.bind(this)`, don't remove it automatically. + * E.g. `(foo || function(){}).bind(this)` + */ + if (memberNode.type !== "MemberExpression") { + return; + } + + const callNode = memberNode.parent; + const firstTokenToRemove = + sourceCode.getTokenAfter( + memberNode.object, + astUtils.isNotClosingParenToken, + ); + const lastTokenToRemove = + sourceCode.getLastToken(callNode); + + /* + * If the member expression is parenthesized, don't remove the right paren. + * E.g. `(function(){}.bind)(this)` + * ^^^^^^^^^^^^ + */ + if ( + astUtils.isParenthesised( + sourceCode, + memberNode, + ) + ) { + return; + } + + // If comments exist in the `.bind(this)`, don't remove those. + if ( + sourceCode.commentsExistBetween( + firstTokenToRemove, + lastTokenToRemove, + ) + ) { + return; + } + + yield fixer.removeRange([ + firstTokenToRemove.range[0], + lastTokenToRemove.range[1], + ]); + } + + // Convert the function expression to an arrow function. + const functionToken = sourceCode.getFirstToken( + node, + node.async ? 1 : 0, + ); + const leftParenToken = sourceCode.getTokenAfter( + functionToken, + astUtils.isOpeningParenToken, + ); + const tokenBeforeBody = sourceCode.getTokenBefore( + node.body, + ); + + if ( + sourceCode.commentsExistBetween( + functionToken, + leftParenToken, + ) + ) { + // Remove only extra tokens to keep comments. + yield fixer.remove(functionToken); + if (node.id) { + yield fixer.remove(node.id); + } + } else { + // Remove extra tokens and spaces. + yield fixer.removeRange([ + functionToken.range[0], + leftParenToken.range[0], + ]); + } + yield fixer.insertTextAfter(tokenBeforeBody, " =>"); + + // Get the node that will become the new arrow function. + let replacedNode = callbackInfo.isLexicalThis + ? node.parent.parent + : node; + + if (replacedNode.type === "ChainExpression") { + replacedNode = replacedNode.parent; + } + + /* + * If the replaced node is part of a BinaryExpression, LogicalExpression, or MemberExpression, then + * the arrow function needs to be parenthesized, because `foo || () => {}` is invalid syntax even + * though `foo || function() {}` is valid. + */ + if ( + replacedNode.parent.type !== "CallExpression" && + replacedNode.parent.type !== + "ConditionalExpression" && + !astUtils.isParenthesised( + sourceCode, + replacedNode, + ) && + !astUtils.isParenthesised(sourceCode, node) + ) { + yield fixer.insertTextBefore(replacedNode, "("); + yield fixer.insertTextAfter(replacedNode, ")"); + } + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js index b495fcc89851..9e2d64a24ef2 100644 --- a/lib/rules/prefer-const.js +++ b/lib/rules/prefer-const.js @@ -16,9 +16,12 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u; -const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|StaticBlock|SwitchCase)$/u; -const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u; +const PATTERN_TYPE = + /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRestProperty|Property)$/u; +const DECLARATION_HOST_TYPE = + /^(?:Program|BlockStatement|StaticBlock|SwitchCase)$/u; +const DESTRUCTURING_HOST_TYPE = + /^(?:VariableDeclarator|AssignmentExpression)$/u; /** * Checks whether a given node is located at `ForStatement.init` or not. @@ -26,7 +29,7 @@ const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/u * @returns {boolean} `true` if the node is located at `ForStatement.init`. */ function isInitOfForStatement(node) { - return node.parent.type === "ForStatement" && node.parent.init === node; + return node.parent.type === "ForStatement" && node.parent.init === node; } /** @@ -35,20 +38,18 @@ function isInitOfForStatement(node) { * @returns {boolean} `true` if the node can become a VariableDeclaration. */ function canBecomeVariableDeclaration(identifier) { - let node = identifier.parent; - - while (PATTERN_TYPE.test(node.type)) { - node = node.parent; - } - - return ( - node.type === "VariableDeclarator" || - ( - node.type === "AssignmentExpression" && - node.parent.type === "ExpressionStatement" && - DECLARATION_HOST_TYPE.test(node.parent.parent.type) - ) - ); + let node = identifier.parent; + + while (PATTERN_TYPE.test(node.type)) { + node = node.parent; + } + + return ( + node.type === "VariableDeclarator" || + (node.type === "AssignmentExpression" && + node.parent.type === "ExpressionStatement" && + DECLARATION_HOST_TYPE.test(node.parent.parent.type)) + ); } /** @@ -59,18 +60,21 @@ function canBecomeVariableDeclaration(identifier) { * @returns {boolean} Indicates if the variable is from outer scope or function parameters. */ function isOuterVariableInDestructing(name, initScope) { + if ( + initScope.through.some( + ref => ref.resolved && ref.resolved.name === name, + ) + ) { + return true; + } - if (initScope.through.some(ref => ref.resolved && ref.resolved.name === name)) { - return true; - } + const variable = astUtils.getVariableByName(initScope, name); - const variable = astUtils.getVariableByName(initScope, name); + if (variable !== null) { + return variable.defs.some(def => def.type === "Parameter"); + } - if (variable !== null) { - return variable.defs.some(def => def.type === "Parameter"); - } - - return false; + return false; } /** @@ -83,19 +87,19 @@ function isOuterVariableInDestructing(name, initScope) { * null. */ function getDestructuringHost(reference) { - if (!reference.isWrite()) { - return null; - } - let node = reference.identifier.parent; - - while (PATTERN_TYPE.test(node.type)) { - node = node.parent; - } - - if (!DESTRUCTURING_HOST_TYPE.test(node.type)) { - return null; - } - return node; + if (!reference.isWrite()) { + return null; + } + let node = reference.identifier.parent; + + while (PATTERN_TYPE.test(node.type)) { + node = node.parent; + } + + if (!DESTRUCTURING_HOST_TYPE.test(node.type)) { + return null; + } + return node; } /** @@ -108,42 +112,43 @@ function getDestructuringHost(reference) { * a MemberExpression, false if not. */ function hasMemberExpressionAssignment(node) { - switch (node.type) { - case "ObjectPattern": - return node.properties.some(prop => { - if (prop) { - - /* - * Spread elements have an argument property while - * others have a value property. Because different - * parsers use different node types for spread elements, - * we just check if there is an argument property. - */ - return hasMemberExpressionAssignment(prop.argument || prop.value); - } - - return false; - }); - - case "ArrayPattern": - return node.elements.some(element => { - if (element) { - return hasMemberExpressionAssignment(element); - } - - return false; - }); - - case "AssignmentPattern": - return hasMemberExpressionAssignment(node.left); - - case "MemberExpression": - return true; - - // no default - } - - return false; + switch (node.type) { + case "ObjectPattern": + return node.properties.some(prop => { + if (prop) { + /* + * Spread elements have an argument property while + * others have a value property. Because different + * parsers use different node types for spread elements, + * we just check if there is an argument property. + */ + return hasMemberExpressionAssignment( + prop.argument || prop.value, + ); + } + + return false; + }); + + case "ArrayPattern": + return node.elements.some(element => { + if (element) { + return hasMemberExpressionAssignment(element); + } + + return false; + }); + + case "AssignmentPattern": + return hasMemberExpressionAssignment(node.left); + + case "MemberExpression": + return true; + + // no default + } + + return false; } /** @@ -171,90 +176,91 @@ function hasMemberExpressionAssignment(node) { * Otherwise, null. */ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) { - if (variable.eslintUsed && variable.scope.type === "global") { - return null; - } - - // Finds the unique WriteReference. - let writer = null; - let isReadBeforeInit = false; - const references = variable.references; - - for (let i = 0; i < references.length; ++i) { - const reference = references[i]; - - if (reference.isWrite()) { - const isReassigned = ( - writer !== null && - writer.identifier !== reference.identifier - ); - - if (isReassigned) { - return null; - } - - const destructuringHost = getDestructuringHost(reference); - - if (destructuringHost !== null && destructuringHost.left !== void 0) { - const leftNode = destructuringHost.left; - let hasOuterVariables = false, - hasNonIdentifiers = false; - - if (leftNode.type === "ObjectPattern") { - const properties = leftNode.properties; - - hasOuterVariables = properties - .filter(prop => prop.value) - .map(prop => prop.value.name) - .some(name => isOuterVariableInDestructing(name, variable.scope)); - - hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); - - } else if (leftNode.type === "ArrayPattern") { - const elements = leftNode.elements; - - hasOuterVariables = elements - .map(element => element && element.name) - .some(name => isOuterVariableInDestructing(name, variable.scope)); - - hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); - } - - if (hasOuterVariables || hasNonIdentifiers) { - return null; - } - - } - - writer = reference; - - } else if (reference.isRead() && writer === null) { - if (ignoreReadBeforeAssign) { - return null; - } - isReadBeforeInit = true; - } - } - - /* - * If the assignment is from a different scope, ignore it. - * If the assignment cannot change to a declaration, ignore it. - */ - const shouldBeConst = ( - writer !== null && - writer.from === variable.scope && - canBecomeVariableDeclaration(writer.identifier) - ); - - if (!shouldBeConst) { - return null; - } - - if (isReadBeforeInit) { - return variable.defs[0].name; - } - - return writer.identifier; + if (variable.eslintUsed && variable.scope.type === "global") { + return null; + } + + // Finds the unique WriteReference. + let writer = null; + let isReadBeforeInit = false; + const references = variable.references; + + for (let i = 0; i < references.length; ++i) { + const reference = references[i]; + + if (reference.isWrite()) { + const isReassigned = + writer !== null && writer.identifier !== reference.identifier; + + if (isReassigned) { + return null; + } + + const destructuringHost = getDestructuringHost(reference); + + if ( + destructuringHost !== null && + destructuringHost.left !== void 0 + ) { + const leftNode = destructuringHost.left; + let hasOuterVariables = false, + hasNonIdentifiers = false; + + if (leftNode.type === "ObjectPattern") { + const properties = leftNode.properties; + + hasOuterVariables = properties + .filter(prop => prop.value) + .map(prop => prop.value.name) + .some(name => + isOuterVariableInDestructing(name, variable.scope), + ); + + hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); + } else if (leftNode.type === "ArrayPattern") { + const elements = leftNode.elements; + + hasOuterVariables = elements + .map(element => element && element.name) + .some(name => + isOuterVariableInDestructing(name, variable.scope), + ); + + hasNonIdentifiers = hasMemberExpressionAssignment(leftNode); + } + + if (hasOuterVariables || hasNonIdentifiers) { + return null; + } + } + + writer = reference; + } else if (reference.isRead() && writer === null) { + if (ignoreReadBeforeAssign) { + return null; + } + isReadBeforeInit = true; + } + } + + /* + * If the assignment is from a different scope, ignore it. + * If the assignment cannot change to a declaration, ignore it. + */ + const shouldBeConst = + writer !== null && + writer.from === variable.scope && + canBecomeVariableDeclaration(writer.identifier); + + if (!shouldBeConst) { + return null; + } + + if (isReadBeforeInit) { + return variable.defs[0].name; + } + + return writer.identifier; } /** @@ -268,41 +274,44 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) { * @returns {Map} Grouped identifier nodes. */ function groupByDestructuring(variables, ignoreReadBeforeAssign) { - const identifierMap = new Map(); - - for (let i = 0; i < variables.length; ++i) { - const variable = variables[i]; - const references = variable.references; - const identifier = getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign); - let prevId = null; - - for (let j = 0; j < references.length; ++j) { - const reference = references[j]; - const id = reference.identifier; - - /* - * Avoid counting a reference twice or more for default values of - * destructuring. - */ - if (id === prevId) { - continue; - } - prevId = id; - - // Add the identifier node into the destructuring group. - const group = getDestructuringHost(reference); - - if (group) { - if (identifierMap.has(group)) { - identifierMap.get(group).push(identifier); - } else { - identifierMap.set(group, [identifier]); - } - } - } - } - - return identifierMap; + const identifierMap = new Map(); + + for (let i = 0; i < variables.length; ++i) { + const variable = variables[i]; + const references = variable.references; + const identifier = getIdentifierIfShouldBeConst( + variable, + ignoreReadBeforeAssign, + ); + let prevId = null; + + for (let j = 0; j < references.length; ++j) { + const reference = references[j]; + const id = reference.identifier; + + /* + * Avoid counting a reference twice or more for default values of + * destructuring. + */ + if (id === prevId) { + continue; + } + prevId = id; + + // Add the identifier node into the destructuring group. + const group = getDestructuringHost(reference); + + if (group) { + if (identifierMap.has(group)) { + identifierMap.get(group).push(identifier); + } else { + identifierMap.set(group, [identifier]); + } + } + } + } + + return identifierMap; } /** @@ -313,13 +322,13 @@ function groupByDestructuring(variables, ignoreReadBeforeAssign) { * @returns {ASTNode} The closest ancestor with the specified type; null if no such ancestor exists. */ function findUp(node, type, shouldStop) { - if (!node || shouldStop(node)) { - return null; - } - if (node.type === type) { - return node; - } - return findUp(node.parent, type, shouldStop); + if (!node || shouldStop(node)) { + return null; + } + if (node.type === type) { + return node; + } + return findUp(node.parent, type, shouldStop); } //------------------------------------------------------------------------------ @@ -328,178 +337,210 @@ function findUp(node, type, shouldStop) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - destructuring: "any", - ignoreReadBeforeAssign: false - }], - - docs: { - description: "Require `const` declarations for variables that are never reassigned after declared", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-const" - }, - - fixable: "code", - - schema: [ - { - type: "object", - properties: { - destructuring: { enum: ["any", "all"] }, - ignoreReadBeforeAssign: { type: "boolean" } - }, - additionalProperties: false - } - ], - messages: { - useConst: "'{{name}}' is never reassigned. Use 'const' instead." - } - }, - - create(context) { - const [{ destructuring, ignoreReadBeforeAssign }] = context.options; - const shouldMatchAnyDestructuredVariable = destructuring !== "all"; - const sourceCode = context.sourceCode; - const variables = []; - let reportCount = 0; - let checkedId = null; - let checkedName = ""; - - - /** - * Reports given identifier nodes if all of the nodes should be declared - * as const. - * - * The argument 'nodes' is an array of Identifier nodes. - * This node is the result of 'getIdentifierIfShouldBeConst()', so it's - * nullable. In simple declaration or assignment cases, the length of - * the array is 1. In destructuring cases, the length of the array can - * be 2 or more. - * @param {(eslint-scope.Reference|null)[]} nodes - * References which are grouped by destructuring to report. - * @returns {void} - */ - function checkGroup(nodes) { - const nodesToReport = nodes.filter(Boolean); - - if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) { - const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement")); - const isVarDecParentNull = varDeclParent === null; - - if (!isVarDecParentNull && varDeclParent.declarations.length > 0) { - const firstDeclaration = varDeclParent.declarations[0]; - - if (firstDeclaration.init) { - const firstDecParent = firstDeclaration.init.parent; - - /* - * First we check the declaration type and then depending on - * if the type is a "VariableDeclarator" or its an "ObjectPattern" - * we compare the name and id from the first identifier, if the names are different - * we assign the new name, id and reset the count of reportCount and nodeCount in - * order to check each block for the number of reported errors and base our fix - * based on comparing nodes.length and nodesToReport.length. - */ - - if (firstDecParent.type === "VariableDeclarator") { - - if (firstDecParent.id.name !== checkedName) { - checkedName = firstDecParent.id.name; - reportCount = 0; - } - - if (firstDecParent.id.type === "ObjectPattern") { - if (firstDecParent.init.name !== checkedName) { - checkedName = firstDecParent.init.name; - reportCount = 0; - } - } - - if (firstDecParent.id !== checkedId) { - checkedId = firstDecParent.id; - reportCount = 0; - } - } - } - } - - let shouldFix = varDeclParent && - - // Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop) - (varDeclParent.parent.type === "ForInStatement" || varDeclParent.parent.type === "ForOfStatement" || - varDeclParent.declarations.every(declaration => declaration.init)) && - - /* - * If options.destructuring is "all", then this warning will not occur unless - * every assignment in the destructuring should be const. In that case, it's safe - * to apply the fix. - */ - nodesToReport.length === nodes.length; - - if (!isVarDecParentNull && varDeclParent.declarations && varDeclParent.declarations.length !== 1) { - - if (varDeclParent && varDeclParent.declarations && varDeclParent.declarations.length >= 1) { - - /* - * Add nodesToReport.length to a count, then comparing the count to the length - * of the declarations in the current block. - */ - - reportCount += nodesToReport.length; - - let totalDeclarationsCount = 0; - - varDeclParent.declarations.forEach(declaration => { - if (declaration.id.type === "ObjectPattern") { - totalDeclarationsCount += declaration.id.properties.length; - } else if (declaration.id.type === "ArrayPattern") { - totalDeclarationsCount += declaration.id.elements.length; - } else { - totalDeclarationsCount += 1; - } - }); - - shouldFix = shouldFix && (reportCount === totalDeclarationsCount); - } - } - - nodesToReport.forEach(node => { - context.report({ - node, - messageId: "useConst", - data: node, - fix: shouldFix - ? fixer => { - const letKeywordToken = sourceCode.getFirstToken(varDeclParent, t => t.value === varDeclParent.kind); - - /** - * Extend the replacement range to the whole declaration, - * in order to prevent other fixes in the same pass - * https://github.com/eslint/eslint/issues/13899 - */ - return new FixTracker(fixer, sourceCode) - .retainRange(varDeclParent.range) - .replaceTextRange(letKeywordToken.range, "const"); - } - : null - }); - }); - } - } - - return { - "Program:exit"() { - groupByDestructuring(variables, ignoreReadBeforeAssign).forEach(checkGroup); - }, - - VariableDeclaration(node) { - if (node.kind === "let" && !isInitOfForStatement(node)) { - variables.push(...sourceCode.getDeclaredVariables(node)); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + destructuring: "any", + ignoreReadBeforeAssign: false, + }, + ], + + docs: { + description: + "Require `const` declarations for variables that are never reassigned after declared", + recommended: false, + url: "https://eslint.org/docs/latest/rules/prefer-const", + }, + + fixable: "code", + + schema: [ + { + type: "object", + properties: { + destructuring: { enum: ["any", "all"] }, + ignoreReadBeforeAssign: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + messages: { + useConst: "'{{name}}' is never reassigned. Use 'const' instead.", + }, + }, + + create(context) { + const [{ destructuring, ignoreReadBeforeAssign }] = context.options; + const shouldMatchAnyDestructuredVariable = destructuring !== "all"; + const sourceCode = context.sourceCode; + const variables = []; + let reportCount = 0; + let checkedId = null; + let checkedName = ""; + + /** + * Reports given identifier nodes if all of the nodes should be declared + * as const. + * + * The argument 'nodes' is an array of Identifier nodes. + * This node is the result of 'getIdentifierIfShouldBeConst()', so it's + * nullable. In simple declaration or assignment cases, the length of + * the array is 1. In destructuring cases, the length of the array can + * be 2 or more. + * @param {(eslint-scope.Reference|null)[]} nodes + * References which are grouped by destructuring to report. + * @returns {void} + */ + function checkGroup(nodes) { + const nodesToReport = nodes.filter(Boolean); + + if ( + nodes.length && + (shouldMatchAnyDestructuredVariable || + nodesToReport.length === nodes.length) + ) { + const varDeclParent = findUp( + nodes[0], + "VariableDeclaration", + parentNode => parentNode.type.endsWith("Statement"), + ); + const isVarDecParentNull = varDeclParent === null; + + if ( + !isVarDecParentNull && + varDeclParent.declarations.length > 0 + ) { + const firstDeclaration = varDeclParent.declarations[0]; + + if (firstDeclaration.init) { + const firstDecParent = firstDeclaration.init.parent; + + /* + * First we check the declaration type and then depending on + * if the type is a "VariableDeclarator" or its an "ObjectPattern" + * we compare the name and id from the first identifier, if the names are different + * we assign the new name, id and reset the count of reportCount and nodeCount in + * order to check each block for the number of reported errors and base our fix + * based on comparing nodes.length and nodesToReport.length. + */ + + if (firstDecParent.type === "VariableDeclarator") { + if (firstDecParent.id.name !== checkedName) { + checkedName = firstDecParent.id.name; + reportCount = 0; + } + + if (firstDecParent.id.type === "ObjectPattern") { + if (firstDecParent.init.name !== checkedName) { + checkedName = firstDecParent.init.name; + reportCount = 0; + } + } + + if (firstDecParent.id !== checkedId) { + checkedId = firstDecParent.id; + reportCount = 0; + } + } + } + } + + let shouldFix = + varDeclParent && + // Don't do a fix unless all variables in the declarations are initialized (or it's in a for-in or for-of loop) + (varDeclParent.parent.type === "ForInStatement" || + varDeclParent.parent.type === "ForOfStatement" || + varDeclParent.declarations.every( + declaration => declaration.init, + )) && + /* + * If options.destructuring is "all", then this warning will not occur unless + * every assignment in the destructuring should be const. In that case, it's safe + * to apply the fix. + */ + nodesToReport.length === nodes.length; + + if ( + !isVarDecParentNull && + varDeclParent.declarations && + varDeclParent.declarations.length !== 1 + ) { + if ( + varDeclParent && + varDeclParent.declarations && + varDeclParent.declarations.length >= 1 + ) { + /* + * Add nodesToReport.length to a count, then comparing the count to the length + * of the declarations in the current block. + */ + + reportCount += nodesToReport.length; + + let totalDeclarationsCount = 0; + + varDeclParent.declarations.forEach(declaration => { + if (declaration.id.type === "ObjectPattern") { + totalDeclarationsCount += + declaration.id.properties.length; + } else if (declaration.id.type === "ArrayPattern") { + totalDeclarationsCount += + declaration.id.elements.length; + } else { + totalDeclarationsCount += 1; + } + }); + + shouldFix = + shouldFix && reportCount === totalDeclarationsCount; + } + } + + nodesToReport.forEach(node => { + context.report({ + node, + messageId: "useConst", + data: node, + fix: shouldFix + ? fixer => { + const letKeywordToken = + sourceCode.getFirstToken( + varDeclParent, + t => t.value === varDeclParent.kind, + ); + + /** + * Extend the replacement range to the whole declaration, + * in order to prevent other fixes in the same pass + * https://github.com/eslint/eslint/issues/13899 + */ + return new FixTracker(fixer, sourceCode) + .retainRange(varDeclParent.range) + .replaceTextRange( + letKeywordToken.range, + "const", + ); + } + : null, + }); + }); + } + } + + return { + "Program:exit"() { + groupByDestructuring(variables, ignoreReadBeforeAssign).forEach( + checkGroup, + ); + }, + + VariableDeclaration(node) { + if (node.kind === "let" && !isInitOfForStatement(node)) { + variables.push(...sourceCode.getDeclaredVariables(node)); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-destructuring.js b/lib/rules/prefer-destructuring.js index c0af567931f8..f3b0ec0e19b7 100644 --- a/lib/rules/prefer-destructuring.js +++ b/lib/rules/prefer-destructuring.js @@ -14,7 +14,9 @@ const astUtils = require("./utils/ast-utils"); // Helpers //------------------------------------------------------------------------------ -const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" }); +const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ + type: "AssignmentExpression", +}); //------------------------------------------------------------------------------ // Rule Definition @@ -22,281 +24,301 @@ const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "Assignment /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require destructuring from arrays and/or objects", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/prefer-destructuring" - }, - - fixable: "code", - - schema: [ - { - - /* - * old support {array: Boolean, object: Boolean} - * new support {VariableDeclarator: {}, AssignmentExpression: {}} - */ - 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 - } - ], - - messages: { - preferDestructuring: "Use {{type}} destructuring." - } - }, - create(context) { - - const enabledTypes = context.options[0]; - const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties; - let normalizedOptions = { - VariableDeclarator: { array: true, object: true }, - AssignmentExpression: { array: true, object: true } - }; - - if (enabledTypes) { - normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined" - ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes } - : enabledTypes; - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Checks if destructuring type should be checked. - * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator" - * @param {string} destructuringType "array" or "object" - * @returns {boolean} `true` if the destructuring type should be checked for the given node - */ - function shouldCheck(nodeType, destructuringType) { - return normalizedOptions && - normalizedOptions[nodeType] && - normalizedOptions[nodeType][destructuringType]; - } - - /** - * Determines if the given node is accessing an array index - * - * This is used to differentiate array index access from object property - * access. - * @param {ASTNode} node the node to evaluate - * @returns {boolean} whether or not the node is an integer - */ - function isArrayIndexAccess(node) { - return Number.isInteger(node.property.value); - } - - /** - * Report that the given node should use destructuring - * @param {ASTNode} reportNode the node to report - * @param {string} type the type of destructuring that should have been done - * @param {Function|null} fix the fix function or null to pass to context.report - * @returns {void} - */ - function report(reportNode, type, fix) { - context.report({ - node: reportNode, - messageId: "preferDestructuring", - data: { type }, - fix - }); - } - - /** - * Determines if a node should be fixed into object destructuring - * - * The fixer only fixes the simplest case of object destructuring, - * like: `let x = a.x`; - * - * Assignment expression is not fixed. - * Array destructuring is not fixed. - * Renamed property is not fixed. - * @param {ASTNode} node the node to evaluate - * @returns {boolean} whether or not the node should be fixed - */ - function shouldFix(node) { - return node.type === "VariableDeclarator" && - node.id.type === "Identifier" && - node.init.type === "MemberExpression" && - !node.init.computed && - node.init.property.type === "Identifier" && - node.id.name === node.init.property.name; - } - - /** - * Fix a node into object destructuring. - * This function only handles the simplest case of object destructuring, - * see {@link shouldFix}. - * @param {SourceCodeFixer} fixer the fixer object - * @param {ASTNode} node the node to be fixed. - * @returns {Object} a fix for the node - */ - function fixIntoObjectDestructuring(fixer, node) { - const rightNode = node.init; - const sourceCode = context.sourceCode; - - // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved. - if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) { - return null; - } - - let objectText = sourceCode.getText(rightNode.object); - - if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) { - objectText = `(${objectText})`; - } - - return fixer.replaceText( - node, - `{${rightNode.property.name}} = ${objectText}` - ); - } - - /** - * Check that the `prefer-destructuring` rules are followed based on the - * given left- and right-hand side of the assignment. - * - * Pulled out into a separate method so that VariableDeclarators and - * AssignmentExpressions can share the same verification logic. - * @param {ASTNode} leftNode the left-hand side of the assignment - * @param {ASTNode} rightNode the right-hand side of the assignment - * @param {ASTNode} reportNode the node to report the error on - * @returns {void} - */ - function performCheck(leftNode, rightNode, reportNode) { - if ( - rightNode.type !== "MemberExpression" || - rightNode.object.type === "Super" || - rightNode.property.type === "PrivateIdentifier" - ) { - return; - } - - if (isArrayIndexAccess(rightNode)) { - if (shouldCheck(reportNode.type, "array")) { - report(reportNode, "array", null); - } - return; - } - - const fix = shouldFix(reportNode) - ? fixer => fixIntoObjectDestructuring(fixer, reportNode) - : null; - - if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) { - report(reportNode, "object", fix); - return; - } - - if (shouldCheck(reportNode.type, "object")) { - const property = rightNode.property; - - if ( - (property.type === "Literal" && leftNode.name === property.value) || - (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed) - ) { - report(reportNode, "object", fix); - } - } - } - - /** - * Check if a given variable declarator is coming from an property access - * that should be using destructuring instead - * @param {ASTNode} node the variable declarator to check - * @returns {void} - */ - function checkVariableDeclarator(node) { - - // Skip if variable is declared without assignment - if (!node.init) { - return; - } - - // We only care about member expressions past this point - if (node.init.type !== "MemberExpression") { - return; - } - - performCheck(node.id, node.init, node); - } - - /** - * Run the `prefer-destructuring` check on an AssignmentExpression - * @param {ASTNode} node the AssignmentExpression node - * @returns {void} - */ - function checkAssignmentExpression(node) { - if (node.operator === "=") { - performCheck(node.left, node.right, node); - } - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - VariableDeclarator: checkVariableDeclarator, - AssignmentExpression: checkAssignmentExpression - }; - } + meta: { + type: "suggestion", + + docs: { + description: "Require destructuring from arrays and/or objects", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/prefer-destructuring", + }, + + fixable: "code", + + schema: [ + { + /* + * old support {array: Boolean, object: Boolean} + * new support {VariableDeclarator: {}, AssignmentExpression: {}} + */ + 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, + }, + ], + + messages: { + preferDestructuring: "Use {{type}} destructuring.", + }, + }, + create(context) { + const enabledTypes = context.options[0]; + const enforceForRenamedProperties = + context.options[1] && + context.options[1].enforceForRenamedProperties; + let normalizedOptions = { + VariableDeclarator: { array: true, object: true }, + AssignmentExpression: { array: true, object: true }, + }; + + if (enabledTypes) { + normalizedOptions = + typeof enabledTypes.array !== "undefined" || + typeof enabledTypes.object !== "undefined" + ? { + VariableDeclarator: enabledTypes, + AssignmentExpression: enabledTypes, + } + : enabledTypes; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Checks if destructuring type should be checked. + * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator" + * @param {string} destructuringType "array" or "object" + * @returns {boolean} `true` if the destructuring type should be checked for the given node + */ + function shouldCheck(nodeType, destructuringType) { + return ( + normalizedOptions && + normalizedOptions[nodeType] && + normalizedOptions[nodeType][destructuringType] + ); + } + + /** + * Determines if the given node is accessing an array index + * + * This is used to differentiate array index access from object property + * access. + * @param {ASTNode} node the node to evaluate + * @returns {boolean} whether or not the node is an integer + */ + function isArrayIndexAccess(node) { + return Number.isInteger(node.property.value); + } + + /** + * Report that the given node should use destructuring + * @param {ASTNode} reportNode the node to report + * @param {string} type the type of destructuring that should have been done + * @param {Function|null} fix the fix function or null to pass to context.report + * @returns {void} + */ + function report(reportNode, type, fix) { + context.report({ + node: reportNode, + messageId: "preferDestructuring", + data: { type }, + fix, + }); + } + + /** + * Determines if a node should be fixed into object destructuring + * + * The fixer only fixes the simplest case of object destructuring, + * like: `let x = a.x`; + * + * Assignment expression is not fixed. + * Array destructuring is not fixed. + * Renamed property is not fixed. + * @param {ASTNode} node the node to evaluate + * @returns {boolean} whether or not the node should be fixed + */ + function shouldFix(node) { + return ( + node.type === "VariableDeclarator" && + node.id.type === "Identifier" && + node.init.type === "MemberExpression" && + !node.init.computed && + node.init.property.type === "Identifier" && + node.id.name === node.init.property.name + ); + } + + /** + * Fix a node into object destructuring. + * This function only handles the simplest case of object destructuring, + * see {@link shouldFix}. + * @param {SourceCodeFixer} fixer the fixer object + * @param {ASTNode} node the node to be fixed. + * @returns {Object} a fix for the node + */ + function fixIntoObjectDestructuring(fixer, node) { + const rightNode = node.init; + const sourceCode = context.sourceCode; + + // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved. + if ( + sourceCode.getCommentsInside(node).length > + sourceCode.getCommentsInside(rightNode.object).length + ) { + return null; + } + + let objectText = sourceCode.getText(rightNode.object); + + if ( + astUtils.getPrecedence(rightNode.object) < + PRECEDENCE_OF_ASSIGNMENT_EXPR + ) { + objectText = `(${objectText})`; + } + + return fixer.replaceText( + node, + `{${rightNode.property.name}} = ${objectText}`, + ); + } + + /** + * Check that the `prefer-destructuring` rules are followed based on the + * given left- and right-hand side of the assignment. + * + * Pulled out into a separate method so that VariableDeclarators and + * AssignmentExpressions can share the same verification logic. + * @param {ASTNode} leftNode the left-hand side of the assignment + * @param {ASTNode} rightNode the right-hand side of the assignment + * @param {ASTNode} reportNode the node to report the error on + * @returns {void} + */ + function performCheck(leftNode, rightNode, reportNode) { + if ( + rightNode.type !== "MemberExpression" || + rightNode.object.type === "Super" || + rightNode.property.type === "PrivateIdentifier" + ) { + return; + } + + if (isArrayIndexAccess(rightNode)) { + if (shouldCheck(reportNode.type, "array")) { + report(reportNode, "array", null); + } + return; + } + + const fix = shouldFix(reportNode) + ? fixer => fixIntoObjectDestructuring(fixer, reportNode) + : null; + + if ( + shouldCheck(reportNode.type, "object") && + enforceForRenamedProperties + ) { + report(reportNode, "object", fix); + return; + } + + if (shouldCheck(reportNode.type, "object")) { + const property = rightNode.property; + + if ( + (property.type === "Literal" && + leftNode.name === property.value) || + (property.type === "Identifier" && + leftNode.name === property.name && + !rightNode.computed) + ) { + report(reportNode, "object", fix); + } + } + } + + /** + * Check if a given variable declarator is coming from an property access + * that should be using destructuring instead + * @param {ASTNode} node the variable declarator to check + * @returns {void} + */ + function checkVariableDeclarator(node) { + // Skip if variable is declared without assignment + if (!node.init) { + return; + } + + // We only care about member expressions past this point + if (node.init.type !== "MemberExpression") { + return; + } + + performCheck(node.id, node.init, node); + } + + /** + * Run the `prefer-destructuring` check on an AssignmentExpression + * @param {ASTNode} node the AssignmentExpression node + * @returns {void} + */ + function checkAssignmentExpression(node) { + if (node.operator === "=") { + performCheck(node.left, node.right, node); + } + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + VariableDeclarator: checkVariableDeclarator, + AssignmentExpression: checkAssignmentExpression, + }; + }, }; diff --git a/lib/rules/prefer-exponentiation-operator.js b/lib/rules/prefer-exponentiation-operator.js index cc9b51f2d8cc..f27b57f02db1 100644 --- a/lib/rules/prefer-exponentiation-operator.js +++ b/lib/rules/prefer-exponentiation-operator.js @@ -16,7 +16,10 @@ const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); // Helpers //------------------------------------------------------------------------------ -const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "BinaryExpression", operator: "**" }); +const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ + type: "BinaryExpression", + operator: "**", +}); /** * Determines whether the given node needs parens if used as the base in an exponentiation binary expression. @@ -24,15 +27,13 @@ const PRECEDENCE_OF_EXPONENTIATION_EXPR = astUtils.getPrecedence({ type: "Binary * @returns {boolean} `true` if the node needs to be parenthesised. */ function doesBaseNeedParens(base) { - return ( - - // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c - astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR || - - // An unary operator cannot be used immediately before an exponentiation expression - base.type === "AwaitExpression" || - base.type === "UnaryExpression" - ); + return ( + // '**' is right-associative, parens are needed when Math.pow(a ** b, c) is converted to (a ** b) ** c + astUtils.getPrecedence(base) <= PRECEDENCE_OF_EXPONENTIATION_EXPR || + // An unary operator cannot be used immediately before an exponentiation expression + base.type === "AwaitExpression" || + base.type === "UnaryExpression" + ); } /** @@ -41,9 +42,8 @@ function doesBaseNeedParens(base) { * @returns {boolean} `true` if the node needs to be parenthesised. */ function doesExponentNeedParens(exponent) { - - // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c - return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR; + // '**' is right-associative, there is no need for parens when Math.pow(a, b ** c) is converted to a ** b ** c + return astUtils.getPrecedence(exponent) < PRECEDENCE_OF_EXPONENTIATION_EXPR; } /** @@ -53,22 +53,35 @@ function doesExponentNeedParens(exponent) { * @returns {boolean} `true` if the expression needs to be parenthesised. */ function doesExponentiationExpressionNeedParens(node, sourceCode) { - const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent; - - const parentPrecedence = astUtils.getPrecedence(parent); - const needsParens = ( - parent.type === "ClassDeclaration" || - ( - parent.type.endsWith("Expression") && - (parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) && - !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) && - !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) && - !(parent.type === "MemberExpression" && parent.computed && parent.property === node) && - !(parent.type === "ArrayExpression") - ) - ); - - return needsParens && !astUtils.isParenthesised(sourceCode, node); + const parent = + node.parent.type === "ChainExpression" + ? node.parent.parent + : node.parent; + + const parentPrecedence = astUtils.getPrecedence(parent); + const needsParens = + parent.type === "ClassDeclaration" || + (parent.type.endsWith("Expression") && + (parentPrecedence === -1 || + parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) && + !( + parent.type === "BinaryExpression" && + parent.operator === "**" && + parent.right === node + ) && + !( + (parent.type === "CallExpression" || + parent.type === "NewExpression") && + parent.arguments.includes(node) + ) && + !( + parent.type === "MemberExpression" && + parent.computed && + parent.property === node + ) && + !(parent.type === "ArrayExpression")); + + return needsParens && !astUtils.isParenthesised(sourceCode, node); } /** @@ -78,7 +91,7 @@ function doesExponentiationExpressionNeedParens(node, sourceCode) { * @returns {string} parenthesised or unchanged text. */ function parenthesizeIfShould(text, shouldParenthesize) { - return shouldParenthesize ? `(${text})` : text; + return shouldParenthesize ? `(${text})` : text; } //------------------------------------------------------------------------------ @@ -87,106 +100,136 @@ function parenthesizeIfShould(text, shouldParenthesize) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [], - fixable: "code", - - messages: { - useExponentiation: "Use the '**' operator instead of 'Math.pow'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Reports the given node. - * @param {ASTNode} node 'Math.pow()' node to report. - * @returns {void} - */ - function report(node) { - context.report({ - node, - messageId: "useExponentiation", - fix(fixer) { - if ( - node.arguments.length !== 2 || - node.arguments.some(arg => arg.type === "SpreadElement") || - sourceCode.getCommentsInside(node).length > 0 - ) { - return null; - } - - const base = node.arguments[0], - exponent = node.arguments[1], - baseText = sourceCode.getText(base), - exponentText = sourceCode.getText(exponent), - shouldParenthesizeBase = doesBaseNeedParens(base), - shouldParenthesizeExponent = doesExponentNeedParens(exponent), - shouldParenthesizeAll = doesExponentiationExpressionNeedParens(node, sourceCode); - - let prefix = "", - suffix = ""; - - if (!shouldParenthesizeAll) { - if (!shouldParenthesizeBase) { - const firstReplacementToken = sourceCode.getFirstToken(base), - tokenBefore = sourceCode.getTokenBefore(node); - - if ( - tokenBefore && - tokenBefore.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken) - ) { - prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c - } - } - if (!shouldParenthesizeExponent) { - const lastReplacementToken = sourceCode.getLastToken(exponent), - tokenAfter = sourceCode.getTokenAfter(node); - - if ( - tokenAfter && - node.range[1] === tokenAfter.range[0] && - !astUtils.canTokensBeAdjacent(lastReplacementToken, tokenAfter) - ) { - suffix = " "; // Math.pow(a, b)in c -> a**b in c - } - } - } - - const baseReplacement = parenthesizeIfShould(baseText, shouldParenthesizeBase), - exponentReplacement = parenthesizeIfShould(exponentText, shouldParenthesizeExponent), - replacement = parenthesizeIfShould(`${baseReplacement}**${exponentReplacement}`, shouldParenthesizeAll); - - return fixer.replaceText(node, `${prefix}${replacement}${suffix}`); - } - }); - } - - return { - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const trackMap = { - Math: { - pow: { [CALL]: true } - } - }; - - for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { - report(refNode); - } - } - }; - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [], + fixable: "code", + + messages: { + useExponentiation: "Use the '**' operator instead of 'Math.pow'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Reports the given node. + * @param {ASTNode} node 'Math.pow()' node to report. + * @returns {void} + */ + function report(node) { + context.report({ + node, + messageId: "useExponentiation", + fix(fixer) { + if ( + node.arguments.length !== 2 || + node.arguments.some( + arg => arg.type === "SpreadElement", + ) || + sourceCode.getCommentsInside(node).length > 0 + ) { + return null; + } + + const base = node.arguments[0], + exponent = node.arguments[1], + baseText = sourceCode.getText(base), + exponentText = sourceCode.getText(exponent), + shouldParenthesizeBase = doesBaseNeedParens(base), + shouldParenthesizeExponent = + doesExponentNeedParens(exponent), + shouldParenthesizeAll = + doesExponentiationExpressionNeedParens( + node, + sourceCode, + ); + + let prefix = "", + suffix = ""; + + if (!shouldParenthesizeAll) { + if (!shouldParenthesizeBase) { + const firstReplacementToken = + sourceCode.getFirstToken(base), + tokenBefore = sourceCode.getTokenBefore(node); + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBefore, + firstReplacementToken, + ) + ) { + prefix = " "; // a+Math.pow(++b, c) -> a+ ++b**c + } + } + if (!shouldParenthesizeExponent) { + const lastReplacementToken = + sourceCode.getLastToken(exponent), + tokenAfter = sourceCode.getTokenAfter(node); + + if ( + tokenAfter && + node.range[1] === tokenAfter.range[0] && + !astUtils.canTokensBeAdjacent( + lastReplacementToken, + tokenAfter, + ) + ) { + suffix = " "; // Math.pow(a, b)in c -> a**b in c + } + } + } + + const baseReplacement = parenthesizeIfShould( + baseText, + shouldParenthesizeBase, + ), + exponentReplacement = parenthesizeIfShould( + exponentText, + shouldParenthesizeExponent, + ), + replacement = parenthesizeIfShould( + `${baseReplacement}**${exponentReplacement}`, + shouldParenthesizeAll, + ); + + return fixer.replaceText( + node, + `${prefix}${replacement}${suffix}`, + ); + }, + }); + } + + return { + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + const trackMap = { + Math: { + pow: { [CALL]: true }, + }, + }; + + for (const { node: refNode } of tracker.iterateGlobalReferences( + trackMap, + )) { + report(refNode); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-named-capture-group.js b/lib/rules/prefer-named-capture-group.js index a82ee1f78356..9ee65c5ce828 100644 --- a/lib/rules/prefer-named-capture-group.js +++ b/lib/rules/prefer-named-capture-group.js @@ -10,10 +10,10 @@ //------------------------------------------------------------------------------ const { - CALL, - CONSTRUCT, - ReferenceTracker, - getStringIfConstant + CALL, + CONSTRUCT, + ReferenceTracker, + getStringIfConstant, } = require("@eslint-community/eslint-utils"); const regexpp = require("@eslint-community/regexpp"); @@ -32,50 +32,50 @@ const parser = new regexpp.RegExpParser(); * @returns {Array} Fixer suggestions for the regex, if statically determinable. */ function suggestIfPossible(groupStart, pattern, rawText, regexNode) { - switch (regexNode.type) { - case "Literal": - if (typeof regexNode.value === "string" && rawText.includes("\\")) { - return null; - } - break; - case "TemplateLiteral": - if (regexNode.expressions.length || rawText.slice(1, -1) !== pattern) { - return null; - } - break; - default: - return null; - } - - const start = regexNode.range[0] + groupStart + 2; - - return [ - { - fix(fixer) { - const existingTemps = pattern.match(/temp\d+/gu) || []; - const highestTempCount = existingTemps.reduce( - (previous, next) => - Math.max(previous, Number(next.slice("temp".length))), - 0 - ); - - return fixer.insertTextBeforeRange( - [start, start], - `?` - ); - }, - messageId: "addGroupName" - }, - { - fix(fixer) { - return fixer.insertTextBeforeRange( - [start, start], - "?:" - ); - }, - messageId: "addNonCapture" - } - ]; + switch (regexNode.type) { + case "Literal": + if (typeof regexNode.value === "string" && rawText.includes("\\")) { + return null; + } + break; + case "TemplateLiteral": + if ( + regexNode.expressions.length || + rawText.slice(1, -1) !== pattern + ) { + return null; + } + break; + default: + return null; + } + + const start = regexNode.range[0] + groupStart + 2; + + return [ + { + fix(fixer) { + const existingTemps = pattern.match(/temp\d+/gu) || []; + const highestTempCount = existingTemps.reduce( + (previous, next) => + Math.max(previous, Number(next.slice("temp".length))), + 0, + ); + + return fixer.insertTextBeforeRange( + [start, start], + `?`, + ); + }, + messageId: "addGroupName", + }, + { + fix(fixer) { + return fixer.insertTextBeforeRange([start, start], "?:"); + }, + messageId: "addNonCapture", + }, + ]; } //------------------------------------------------------------------------------ @@ -84,95 +84,108 @@ function suggestIfPossible(groupStart, pattern, rawText, regexNode) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce using named capture group in regular expression", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group" - }, - - hasSuggestions: true, - - schema: [], - - messages: { - addGroupName: "Add name to capture group.", - addNonCapture: "Convert group to non-capturing.", - required: "Capture group '{{group}}' should be converted to a named or non-capturing group." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - /** - * Function to check regular expression. - * @param {string} pattern The regular expression pattern to be checked. - * @param {ASTNode} node AST node which contains the regular expression or a call/new expression. - * @param {ASTNode} regexNode AST node which contains the regular expression. - * @param {string|null} flags The regular expression flags to be checked. - * @returns {void} - */ - function checkRegex(pattern, node, regexNode, flags) { - let ast; - - try { - ast = parser.parsePattern(pattern, 0, pattern.length, { - unicode: Boolean(flags && flags.includes("u")), - unicodeSets: Boolean(flags && flags.includes("v")) - }); - } catch { - - // ignore regex syntax errors - return; - } - - regexpp.visitRegExpAST(ast, { - onCapturingGroupEnter(group) { - if (!group.name) { - const rawText = sourceCode.getText(regexNode); - const suggest = suggestIfPossible(group.start, pattern, rawText, regexNode); - - context.report({ - node, - messageId: "required", - data: { - group: group.raw - }, - suggest - }); - } - } - }); - } - - return { - Literal(node) { - if (node.regex) { - checkRegex(node.regex.pattern, node, node, node.regex.flags); - } - }, - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const traceMap = { - RegExp: { - [CALL]: true, - [CONSTRUCT]: true - } - }; - - for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { - const regex = getStringIfConstant(refNode.arguments[0]); - const flags = getStringIfConstant(refNode.arguments[1]); - - if (regex) { - checkRegex(regex, refNode, refNode.arguments[0], flags); - } - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce using named capture group in regular expression", + recommended: false, + url: "https://eslint.org/docs/latest/rules/prefer-named-capture-group", + }, + + hasSuggestions: true, + + schema: [], + + messages: { + addGroupName: "Add name to capture group.", + addNonCapture: "Convert group to non-capturing.", + required: + "Capture group '{{group}}' should be converted to a named or non-capturing group.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + /** + * Function to check regular expression. + * @param {string} pattern The regular expression pattern to be checked. + * @param {ASTNode} node AST node which contains the regular expression or a call/new expression. + * @param {ASTNode} regexNode AST node which contains the regular expression. + * @param {string|null} flags The regular expression flags to be checked. + * @returns {void} + */ + function checkRegex(pattern, node, regexNode, flags) { + let ast; + + try { + ast = parser.parsePattern(pattern, 0, pattern.length, { + unicode: Boolean(flags && flags.includes("u")), + unicodeSets: Boolean(flags && flags.includes("v")), + }); + } catch { + // ignore regex syntax errors + return; + } + + regexpp.visitRegExpAST(ast, { + onCapturingGroupEnter(group) { + if (!group.name) { + const rawText = sourceCode.getText(regexNode); + const suggest = suggestIfPossible( + group.start, + pattern, + rawText, + regexNode, + ); + + context.report({ + node, + messageId: "required", + data: { + group: group.raw, + }, + suggest, + }); + } + }, + }); + } + + return { + Literal(node) { + if (node.regex) { + checkRegex( + node.regex.pattern, + node, + node, + node.regex.flags, + ); + } + }, + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + const traceMap = { + RegExp: { + [CALL]: true, + [CONSTRUCT]: true, + }, + }; + + for (const { node: refNode } of tracker.iterateGlobalReferences( + traceMap, + )) { + const regex = getStringIfConstant(refNode.arguments[0]); + const flags = getStringIfConstant(refNode.arguments[1]); + + if (regex) { + checkRegex(regex, refNode, refNode.arguments[0], flags); + } + } + }, + }; + }, }; diff --git a/lib/rules/prefer-numeric-literals.js b/lib/rules/prefer-numeric-literals.js index 4233b59fdc4e..33f88f36bb65 100644 --- a/lib/rules/prefer-numeric-literals.js +++ b/lib/rules/prefer-numeric-literals.js @@ -16,9 +16,9 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const radixMap = new Map([ - [2, { system: "binary", literalPrefix: "0b" }], - [8, { system: "octal", literalPrefix: "0o" }], - [16, { system: "hexadecimal", literalPrefix: "0x" }] + [2, { system: "binary", literalPrefix: "0b" }], + [8, { system: "octal", literalPrefix: "0o" }], + [16, { system: "hexadecimal", literalPrefix: "0x" }], ]); /** @@ -29,10 +29,10 @@ const radixMap = new Map([ * false otherwise. */ function isParseInt(calleeNode) { - return ( - astUtils.isSpecificId(calleeNode, "parseInt") || - astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt") - ); + return ( + astUtils.isSpecificId(calleeNode, "parseInt") || + astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt") + ); } //------------------------------------------------------------------------------ @@ -41,109 +41,117 @@ function isParseInt(calleeNode) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [], - - messages: { - useLiteral: "Use {{system}} literals instead of {{functionName}}()." - }, - - fixable: "code" - }, - - create(context) { - const sourceCode = context.sourceCode; - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - - "CallExpression[arguments.length=2]"(node) { - const [strNode, radixNode] = node.arguments, - str = astUtils.getStaticStringValue(strNode), - radix = radixNode.value; - - if ( - str !== null && - astUtils.isStringLiteral(strNode) && - radixNode.type === "Literal" && - typeof radix === "number" && - radixMap.has(radix) && - isParseInt(node.callee) - ) { - - const { system, literalPrefix } = radixMap.get(radix); - - context.report({ - node, - messageId: "useLiteral", - data: { - system, - functionName: sourceCode.getText(node.callee) - }, - fix(fixer) { - if (sourceCode.getCommentsInside(node).length) { - return null; - } - - const replacement = `${literalPrefix}${str}`; - - if (+replacement !== parseInt(str, radix)) { - - /* - * If the newly-produced literal would be invalid, (e.g. 0b1234), - * or it would yield an incorrect parseInt result for some other reason, don't make a fix. - * - * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+` - * per the specification doesn't support numeric separators. Thus, the above condition will be `true` - * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value. - * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also - * doesn't support numeric separators, but it does parse part of the string before the first `_`, - * so the autofix would be invalid: - * - * parseInt("1_1", 2) // === 1 - * 0b1_1 // === 3 - */ - return null; - } - - const tokenBefore = sourceCode.getTokenBefore(node), - tokenAfter = sourceCode.getTokenAfter(node); - let prefix = "", - suffix = ""; - - if ( - tokenBefore && - tokenBefore.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, replacement) - ) { - prefix = " "; - } - - if ( - tokenAfter && - node.range[1] === tokenAfter.range[0] && - !astUtils.canTokensBeAdjacent(replacement, tokenAfter) - ) { - suffix = " "; - } - - return fixer.replaceText(node, `${prefix}${replacement}${suffix}`); - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [], + + messages: { + useLiteral: + "Use {{system}} literals instead of {{functionName}}().", + }, + + fixable: "code", + }, + + create(context) { + const sourceCode = context.sourceCode; + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + "CallExpression[arguments.length=2]"(node) { + const [strNode, radixNode] = node.arguments, + str = astUtils.getStaticStringValue(strNode), + radix = radixNode.value; + + if ( + str !== null && + astUtils.isStringLiteral(strNode) && + radixNode.type === "Literal" && + typeof radix === "number" && + radixMap.has(radix) && + isParseInt(node.callee) + ) { + const { system, literalPrefix } = radixMap.get(radix); + + context.report({ + node, + messageId: "useLiteral", + data: { + system, + functionName: sourceCode.getText(node.callee), + }, + fix(fixer) { + if (sourceCode.getCommentsInside(node).length) { + return null; + } + + const replacement = `${literalPrefix}${str}`; + + if (+replacement !== parseInt(str, radix)) { + /* + * If the newly-produced literal would be invalid, (e.g. 0b1234), + * or it would yield an incorrect parseInt result for some other reason, don't make a fix. + * + * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+` + * per the specification doesn't support numeric separators. Thus, the above condition will be `true` + * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value. + * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also + * doesn't support numeric separators, but it does parse part of the string before the first `_`, + * so the autofix would be invalid: + * + * parseInt("1_1", 2) // === 1 + * 0b1_1 // === 3 + */ + return null; + } + + const tokenBefore = sourceCode.getTokenBefore(node), + tokenAfter = sourceCode.getTokenAfter(node); + let prefix = "", + suffix = ""; + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent( + tokenBefore, + replacement, + ) + ) { + prefix = " "; + } + + if ( + tokenAfter && + node.range[1] === tokenAfter.range[0] && + !astUtils.canTokensBeAdjacent( + replacement, + tokenAfter, + ) + ) { + suffix = " "; + } + + return fixer.replaceText( + node, + `${prefix}${replacement}${suffix}`, + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-object-has-own.js b/lib/rules/prefer-object-has-own.js index 97ea64fa6735..18f450ec679e 100644 --- a/lib/rules/prefer-object-has-own.js +++ b/lib/rules/prefer-object-has-own.js @@ -22,22 +22,31 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if `node.object` is `Object`, `Object.prototype`, or `{}` (empty 'ObjectExpression' node). */ function hasLeftHandObject(node) { - - /* - * ({}).hasOwnProperty.call(obj, prop) - `true` - * ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty - */ - if (node.object.type === "ObjectExpression" && node.object.properties.length === 0) { - return true; - } - - const objectNodeToCheck = node.object.type === "MemberExpression" && astUtils.getStaticPropertyName(node.object) === "prototype" ? node.object.object : node.object; - - if (objectNodeToCheck.type === "Identifier" && objectNodeToCheck.name === "Object") { - return true; - } - - return false; + /* + * ({}).hasOwnProperty.call(obj, prop) - `true` + * ({ foo }.hasOwnProperty.call(obj, prop)) - `false`, object literal should be empty + */ + if ( + node.object.type === "ObjectExpression" && + node.object.properties.length === 0 + ) { + return true; + } + + const objectNodeToCheck = + node.object.type === "MemberExpression" && + astUtils.getStaticPropertyName(node.object) === "prototype" + ? node.object.object + : node.object; + + if ( + objectNodeToCheck.type === "Identifier" && + objectNodeToCheck.name === "Object" + ) { + return true; + } + + return false; } //------------------------------------------------------------------------------ @@ -46,69 +55,94 @@ function hasLeftHandObject(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - docs: { - description: - "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-object-has-own" - }, - schema: [], - messages: { - useHasOwn: "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'." - }, - fixable: "code" - }, - create(context) { - - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - if (!(node.callee.type === "MemberExpression" && node.callee.object.type === "MemberExpression")) { - return; - } - - const calleePropertyName = astUtils.getStaticPropertyName(node.callee); - const objectPropertyName = astUtils.getStaticPropertyName(node.callee.object); - const isObject = hasLeftHandObject(node.callee.object); - - // check `Object` scope - const scope = sourceCode.getScope(node); - const variable = astUtils.getVariableByName(scope, "Object"); - - if ( - calleePropertyName === "call" && - objectPropertyName === "hasOwnProperty" && - isObject && - variable && variable.scope.type === "global" - ) { - context.report({ - node, - messageId: "useHasOwn", - fix(fixer) { - - if (sourceCode.getCommentsInside(node.callee).length > 0) { - return null; - } - - const tokenJustBeforeNode = sourceCode.getTokenBefore(node.callee, { includeComments: true }); - - // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335 - if ( - tokenJustBeforeNode && - tokenJustBeforeNode.range[1] === node.callee.range[0] && - !astUtils.canTokensBeAdjacent(tokenJustBeforeNode, "Object.hasOwn") - ) { - return fixer.replaceText(node.callee, " Object.hasOwn"); - } - - return fixer.replaceText(node.callee, "Object.hasOwn"); - } - }); - } - } - }; - } + meta: { + type: "suggestion", + docs: { + description: + "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/prefer-object-has-own", + }, + schema: [], + messages: { + useHasOwn: + "Use 'Object.hasOwn()' instead of 'Object.prototype.hasOwnProperty.call()'.", + }, + fixable: "code", + }, + create(context) { + const sourceCode = context.sourceCode; + + return { + CallExpression(node) { + if ( + !( + node.callee.type === "MemberExpression" && + node.callee.object.type === "MemberExpression" + ) + ) { + return; + } + + const calleePropertyName = astUtils.getStaticPropertyName( + node.callee, + ); + const objectPropertyName = astUtils.getStaticPropertyName( + node.callee.object, + ); + const isObject = hasLeftHandObject(node.callee.object); + + // check `Object` scope + const scope = sourceCode.getScope(node); + const variable = astUtils.getVariableByName(scope, "Object"); + + if ( + calleePropertyName === "call" && + objectPropertyName === "hasOwnProperty" && + isObject && + variable && + variable.scope.type === "global" + ) { + context.report({ + node, + messageId: "useHasOwn", + fix(fixer) { + if ( + sourceCode.getCommentsInside(node.callee) + .length > 0 + ) { + return null; + } + + const tokenJustBeforeNode = + sourceCode.getTokenBefore(node.callee, { + includeComments: true, + }); + + // for https://github.com/eslint/eslint/pull/15346#issuecomment-991417335 + if ( + tokenJustBeforeNode && + tokenJustBeforeNode.range[1] === + node.callee.range[0] && + !astUtils.canTokensBeAdjacent( + tokenJustBeforeNode, + "Object.hasOwn", + ) + ) { + return fixer.replaceText( + node.callee, + " Object.hasOwn", + ); + } + + return fixer.replaceText( + node.callee, + "Object.hasOwn", + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js index eb282dcfbe83..054a15e841ce 100644 --- a/lib/rules/prefer-object-spread.js +++ b/lib/rules/prefer-object-spread.js @@ -7,10 +7,10 @@ const { CALL, ReferenceTracker } = require("@eslint-community/eslint-utils"); const { - isCommaToken, - isOpeningParenToken, - isClosingParenToken, - isParenthesised + isCommaToken, + isOpeningParenToken, + isClosingParenToken, + isParenthesised, } = require("./utils/ast-utils"); const ANY_SPACE = /\s/u; @@ -21,7 +21,7 @@ const ANY_SPACE = /\s/u; * @returns {boolean} - Returns true if the Object.assign call has array spread */ function hasArraySpread(node) { - return node.arguments.some(arg => arg.type === "SpreadElement"); + return node.arguments.some(arg => arg.type === "SpreadElement"); } /** @@ -30,8 +30,9 @@ function hasArraySpread(node) { * @returns {boolean} `true` if the node is a getter or a setter. */ function isAccessorProperty(node) { - return node.type === "Property" && - (node.kind === "get" || node.kind === "set"); + return ( + node.type === "Property" && (node.kind === "get" || node.kind === "set") + ); } /** @@ -40,7 +41,7 @@ function isAccessorProperty(node) { * @returns {boolean} `true` if the node has at least one getter/setter. */ function hasAccessors(node) { - return node.properties.some(isAccessorProperty); + return node.properties.some(isAccessorProperty); } /** @@ -49,9 +50,9 @@ function hasAccessors(node) { * @returns {boolean} `true` if the node has at least one argument that is an object expression with at least one getter/setter. */ function hasArgumentsWithAccessors(node) { - return node.arguments - .filter(arg => arg.type === "ObjectExpression") - .some(hasAccessors); + return node.arguments + .filter(arg => arg.type === "ObjectExpression") + .some(hasAccessors); } /** @@ -62,20 +63,20 @@ function hasArgumentsWithAccessors(node) { * @returns {boolean} - Returns true if the node needs parentheses */ function needsParens(node, sourceCode) { - const parent = node.parent; - - switch (parent.type) { - case "VariableDeclarator": - case "ArrayExpression": - case "ReturnStatement": - case "CallExpression": - case "Property": - return false; - case "AssignmentExpression": - return parent.left === node && !isParenthesised(sourceCode, node); - default: - return !isParenthesised(sourceCode, node); - } + const parent = node.parent; + + switch (parent.type) { + case "VariableDeclarator": + case "ArrayExpression": + case "ReturnStatement": + case "CallExpression": + case "Property": + return false; + case "AssignmentExpression": + return parent.left === node && !isParenthesised(sourceCode, node); + default: + return !isParenthesised(sourceCode, node); + } } /** @@ -85,14 +86,14 @@ function needsParens(node, sourceCode) { * @returns {boolean} True if the node needs parentheses */ function argNeedsParens(node, sourceCode) { - switch (node.type) { - case "AssignmentExpression": - case "ArrowFunctionExpression": - case "ConditionalExpression": - return !isParenthesised(sourceCode, node); - default: - return false; - } + switch (node.type) { + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "ConditionalExpression": + return !isParenthesised(sourceCode, node); + default: + return false; + } } /** @@ -104,24 +105,27 @@ function argNeedsParens(node, sourceCode) { * @returns {Token[]} The parenthesis tokens of the node. This is sorted by the location. */ function getParenTokens(node, leftArgumentListParen, sourceCode) { - const parens = [sourceCode.getFirstToken(node), sourceCode.getLastToken(node)]; - let leftNext = sourceCode.getTokenBefore(node); - let rightNext = sourceCode.getTokenAfter(node); - - // Note: don't include the parens of the argument list. - while ( - leftNext && - rightNext && - leftNext.range[0] > leftArgumentListParen.range[0] && - isOpeningParenToken(leftNext) && - isClosingParenToken(rightNext) - ) { - parens.push(leftNext, rightNext); - leftNext = sourceCode.getTokenBefore(leftNext); - rightNext = sourceCode.getTokenAfter(rightNext); - } - - return parens.sort((a, b) => a.range[0] - b.range[0]); + const parens = [ + sourceCode.getFirstToken(node), + sourceCode.getLastToken(node), + ]; + let leftNext = sourceCode.getTokenBefore(node); + let rightNext = sourceCode.getTokenAfter(node); + + // Note: don't include the parens of the argument list. + while ( + leftNext && + rightNext && + leftNext.range[0] > leftArgumentListParen.range[0] && + isOpeningParenToken(leftNext) && + isClosingParenToken(rightNext) + ) { + parens.push(leftNext, rightNext); + leftNext = sourceCode.getTokenBefore(leftNext); + rightNext = sourceCode.getTokenAfter(rightNext); + } + + return parens.sort((a, b) => a.range[0] - b.range[0]); } /** @@ -131,24 +135,26 @@ function getParenTokens(node, leftArgumentListParen, sourceCode) { * @returns {number} The end of the range of the token and around whitespaces. */ function getStartWithSpaces(token, sourceCode) { - const text = sourceCode.text; - let start = token.range[0]; - - // If the previous token is a line comment then skip this step to avoid commenting this token out. - { - const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }); - - if (prevToken && prevToken.type === "Line") { - return start; - } - } - - // Detect spaces before the token. - while (ANY_SPACE.test(text[start - 1] || "")) { - start -= 1; - } - - return start; + const text = sourceCode.text; + let start = token.range[0]; + + // If the previous token is a line comment then skip this step to avoid commenting this token out. + { + const prevToken = sourceCode.getTokenBefore(token, { + includeComments: true, + }); + + if (prevToken && prevToken.type === "Line") { + return start; + } + } + + // Detect spaces before the token. + while (ANY_SPACE.test(text[start - 1] || "")) { + start -= 1; + } + + return start; } /** @@ -158,15 +164,15 @@ function getStartWithSpaces(token, sourceCode) { * @returns {number} The start of the range of the token and around whitespaces. */ function getEndWithSpaces(token, sourceCode) { - const text = sourceCode.text; - let end = token.range[1]; + const text = sourceCode.text; + let end = token.range[1]; - // Detect spaces after the token. - while (ANY_SPACE.test(text[end] || "")) { - end += 1; - } + // Detect spaces after the token. + while (ANY_SPACE.test(text[end] || "")) { + end += 1; + } - return end; + return end; } /** @@ -176,124 +182,138 @@ function getEndWithSpaces(token, sourceCode) { * @returns {Function} autofixer - replaces the Object.assign with a spread object. */ function defineFixer(node, sourceCode) { - return function *(fixer) { - const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken); - const rightParen = sourceCode.getLastToken(node); - - // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren. - yield fixer.removeRange([node.range[0], leftParen.range[0]]); - - // Replace the parens of argument list to braces. - if (needsParens(node, sourceCode)) { - yield fixer.replaceText(leftParen, "({"); - yield fixer.replaceText(rightParen, "})"); - } else { - yield fixer.replaceText(leftParen, "{"); - yield fixer.replaceText(rightParen, "}"); - } - - // Process arguments. - for (const argNode of node.arguments) { - const innerParens = getParenTokens(argNode, leftParen, sourceCode); - const left = innerParens.shift(); - const right = innerParens.pop(); - - if (argNode.type === "ObjectExpression") { - const maybeTrailingComma = sourceCode.getLastToken(argNode, 1); - const maybeArgumentComma = sourceCode.getTokenAfter(right); - - /* - * Make bare this object literal. - * And remove spaces inside of the braces for better formatting. - */ - for (const innerParen of innerParens) { - yield fixer.remove(innerParen); - } - const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)]; - const rightRange = [ - Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap - right.range[1] - ]; - - yield fixer.removeRange(leftRange); - yield fixer.removeRange(rightRange); - - // Remove the comma of this argument if it's duplication. - if ( - (argNode.properties.length === 0 || isCommaToken(maybeTrailingComma)) && - isCommaToken(maybeArgumentComma) - ) { - yield fixer.remove(maybeArgumentComma); - } - } else { - - // Make spread. - if (argNeedsParens(argNode, sourceCode)) { - yield fixer.insertTextBefore(left, "...("); - yield fixer.insertTextAfter(right, ")"); - } else { - yield fixer.insertTextBefore(left, "..."); - } - } - } - }; + return function* (fixer) { + const leftParen = sourceCode.getTokenAfter( + node.callee, + isOpeningParenToken, + ); + const rightParen = sourceCode.getLastToken(node); + + // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren. + yield fixer.removeRange([node.range[0], leftParen.range[0]]); + + // Replace the parens of argument list to braces. + if (needsParens(node, sourceCode)) { + yield fixer.replaceText(leftParen, "({"); + yield fixer.replaceText(rightParen, "})"); + } else { + yield fixer.replaceText(leftParen, "{"); + yield fixer.replaceText(rightParen, "}"); + } + + // Process arguments. + for (const argNode of node.arguments) { + const innerParens = getParenTokens(argNode, leftParen, sourceCode); + const left = innerParens.shift(); + const right = innerParens.pop(); + + if (argNode.type === "ObjectExpression") { + const maybeTrailingComma = sourceCode.getLastToken(argNode, 1); + const maybeArgumentComma = sourceCode.getTokenAfter(right); + + /* + * Make bare this object literal. + * And remove spaces inside of the braces for better formatting. + */ + for (const innerParen of innerParens) { + yield fixer.remove(innerParen); + } + const leftRange = [ + left.range[0], + getEndWithSpaces(left, sourceCode), + ]; + const rightRange = [ + Math.max( + getStartWithSpaces(right, sourceCode), + leftRange[1], + ), // Ensure ranges don't overlap + right.range[1], + ]; + + yield fixer.removeRange(leftRange); + yield fixer.removeRange(rightRange); + + // Remove the comma of this argument if it's duplication. + if ( + (argNode.properties.length === 0 || + isCommaToken(maybeTrailingComma)) && + isCommaToken(maybeArgumentComma) + ) { + yield fixer.remove(maybeArgumentComma); + } + } else { + // Make spread. + if (argNeedsParens(argNode, sourceCode)) { + yield fixer.insertTextBefore(left, "...("); + yield fixer.insertTextAfter(right, ")"); + } else { + yield fixer.insertTextBefore(left, "..."); + } + } + } + }; } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - 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" - }, - - schema: [], - fixable: "code", - - messages: { - useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.", - useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - return { - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const trackMap = { - Object: { - assign: { [CALL]: true } - } - }; - - // Iterate all calls of `Object.assign` (only of the global variable `Object`). - for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { - if ( - refNode.arguments.length >= 1 && - refNode.arguments[0].type === "ObjectExpression" && - !hasArraySpread(refNode) && - !( - refNode.arguments.length > 1 && - hasArgumentsWithAccessors(refNode) - ) - ) { - const messageId = refNode.arguments.length === 1 - ? "useLiteralMessage" - : "useSpreadMessage"; - const fix = defineFixer(refNode, sourceCode); - - context.report({ node: refNode, messageId, fix }); - } - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + 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", + }, + + schema: [], + fixable: "code", + + messages: { + useSpreadMessage: + "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.", + useLiteralMessage: + "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + return { + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + const trackMap = { + Object: { + assign: { [CALL]: true }, + }, + }; + + // Iterate all calls of `Object.assign` (only of the global variable `Object`). + for (const { node: refNode } of tracker.iterateGlobalReferences( + trackMap, + )) { + if ( + refNode.arguments.length >= 1 && + refNode.arguments[0].type === "ObjectExpression" && + !hasArraySpread(refNode) && + !( + refNode.arguments.length > 1 && + hasArgumentsWithAccessors(refNode) + ) + ) { + const messageId = + refNode.arguments.length === 1 + ? "useLiteralMessage" + : "useSpreadMessage"; + const fix = defineFixer(refNode, sourceCode); + + context.report({ node: refNode, messageId, fix }); + } + } + }, + }; + }, }; diff --git a/lib/rules/prefer-promise-reject-errors.js b/lib/rules/prefer-promise-reject-errors.js index 276b4222769e..a10dabf2c446 100644 --- a/lib/rules/prefer-promise-reject-errors.js +++ b/lib/rules/prefer-promise-reject-errors.js @@ -12,125 +12,143 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - allowEmptyReject: false - }], - - docs: { - description: "Require using Error objects as Promise rejection reasons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors" - }, - - fixable: null, - - schema: [ - { - type: "object", - properties: { - allowEmptyReject: { type: "boolean" } - }, - additionalProperties: false - } - ], - - messages: { - rejectAnError: "Expected the Promise rejection reason to be an Error." - } - }, - - create(context) { - - const [{ allowEmptyReject }] = context.options; - const sourceCode = context.sourceCode; - - //---------------------------------------------------------------------- - // Helpers - //---------------------------------------------------------------------- - - /** - * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error - * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise - * @returns {void} - */ - function checkRejectCall(callExpression) { - if (!callExpression.arguments.length && allowEmptyReject) { - return; - } - if ( - !callExpression.arguments.length || - !astUtils.couldBeError(callExpression.arguments[0]) || - callExpression.arguments[0].type === "Identifier" && callExpression.arguments[0].name === "undefined" - ) { - context.report({ - node: callExpression, - messageId: "rejectAnError" - }); - } - } - - /** - * Determines whether a function call is a Promise.reject() call - * @param {ASTNode} node A CallExpression node - * @returns {boolean} `true` if the call is a Promise.reject() call - */ - function isPromiseRejectCall(node) { - return astUtils.isSpecificMemberAccess(node.callee, "Promise", "reject"); - } - - //---------------------------------------------------------------------- - // Public - //---------------------------------------------------------------------- - - return { - - // Check `Promise.reject(value)` calls. - CallExpression(node) { - if (isPromiseRejectCall(node)) { - checkRejectCall(node); - } - }, - - /* - * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls. - * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that - * the nodes in the expression already have the `parent` property. - */ - "NewExpression:exit"(node) { - if ( - node.callee.type === "Identifier" && node.callee.name === "Promise" && - node.arguments.length && astUtils.isFunction(node.arguments[0]) && - node.arguments[0].params.length > 1 && node.arguments[0].params[1].type === "Identifier" - ) { - sourceCode.getDeclaredVariables(node.arguments[0]) - - /* - * Find the first variable that matches the second parameter's name. - * If the first parameter has the same name as the second parameter, then the variable will actually - * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten - * by the second parameter. It's not possible for an expression with the variable to be evaluated before - * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or - * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for - * this case. - */ - .find(variable => variable.name === node.arguments[0].params[1].name) - - // Get the references to that variable. - .references - - // Only check the references that read the parameter's value. - .filter(ref => ref.isRead()) - - // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`. - .filter(ref => ref.identifier.parent.type === "CallExpression" && ref.identifier === ref.identifier.parent.callee) - - // Check the argument of the function call to determine whether it's an Error. - .forEach(ref => checkRejectCall(ref.identifier.parent)); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + allowEmptyReject: false, + }, + ], + + docs: { + description: + "Require using Error objects as Promise rejection reasons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/prefer-promise-reject-errors", + }, + + fixable: null, + + schema: [ + { + type: "object", + properties: { + allowEmptyReject: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + + messages: { + rejectAnError: + "Expected the Promise rejection reason to be an Error.", + }, + }, + + create(context) { + const [{ allowEmptyReject }] = context.options; + const sourceCode = context.sourceCode; + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + /** + * Checks the argument of a reject() or Promise.reject() CallExpression, and reports it if it can't be an Error + * @param {ASTNode} callExpression A CallExpression node which is used to reject a Promise + * @returns {void} + */ + function checkRejectCall(callExpression) { + if (!callExpression.arguments.length && allowEmptyReject) { + return; + } + if ( + !callExpression.arguments.length || + !astUtils.couldBeError(callExpression.arguments[0]) || + (callExpression.arguments[0].type === "Identifier" && + callExpression.arguments[0].name === "undefined") + ) { + context.report({ + node: callExpression, + messageId: "rejectAnError", + }); + } + } + + /** + * Determines whether a function call is a Promise.reject() call + * @param {ASTNode} node A CallExpression node + * @returns {boolean} `true` if the call is a Promise.reject() call + */ + function isPromiseRejectCall(node) { + return astUtils.isSpecificMemberAccess( + node.callee, + "Promise", + "reject", + ); + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + // Check `Promise.reject(value)` calls. + CallExpression(node) { + if (isPromiseRejectCall(node)) { + checkRejectCall(node); + } + }, + + /* + * Check for `new Promise((resolve, reject) => {})`, and check for reject() calls. + * This function is run on "NewExpression:exit" instead of "NewExpression" to ensure that + * the nodes in the expression already have the `parent` property. + */ + "NewExpression:exit"(node) { + if ( + node.callee.type === "Identifier" && + node.callee.name === "Promise" && + node.arguments.length && + astUtils.isFunction(node.arguments[0]) && + node.arguments[0].params.length > 1 && + node.arguments[0].params[1].type === "Identifier" + ) { + sourceCode + .getDeclaredVariables(node.arguments[0]) + + /* + * Find the first variable that matches the second parameter's name. + * If the first parameter has the same name as the second parameter, then the variable will actually + * be "declared" when the first parameter is evaluated, but then it will be immediately overwritten + * by the second parameter. It's not possible for an expression with the variable to be evaluated before + * the variable is overwritten, because functions with duplicate parameters cannot have destructuring or + * default assignments in their parameter lists. Therefore, it's not necessary to explicitly account for + * this case. + */ + .find( + variable => + variable.name === + node.arguments[0].params[1].name, + ) + + // Get the references to that variable. + .references // Only check the references that read the parameter's value. + .filter(ref => ref.isRead()) + + // Only check the references that are used as the callee in a function call, e.g. `reject(foo)`. + .filter( + ref => + ref.identifier.parent.type === + "CallExpression" && + ref.identifier === ref.identifier.parent.callee, + ) + + // Check the argument of the function call to determine whether it's an Error. + .forEach(ref => checkRejectCall(ref.identifier.parent)); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-reflect.js b/lib/rules/prefer-reflect.js index a6f69bd365ac..6de4290b1f71 100644 --- a/lib/rules/prefer-reflect.js +++ b/lib/rules/prefer-reflect.js @@ -11,121 +11,141 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Require `Reflect` methods where applicable", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-reflect" - }, + docs: { + description: "Require `Reflect` methods where applicable", + recommended: false, + url: "https://eslint.org/docs/latest/rules/prefer-reflect", + }, - deprecated: { - message: "The original intention of this rule was misguided.", - url: "https://eslint.org/docs/latest/rules/prefer-reflect", - deprecatedSince: "3.9.0", - availableUntil: null, - replacedBy: [] - }, + deprecated: { + message: "The original intention of this rule was misguided.", + url: "https://eslint.org/docs/latest/rules/prefer-reflect", + deprecatedSince: "3.9.0", + availableUntil: null, + replacedBy: [], + }, - schema: [ - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - enum: [ - "apply", - "call", - "delete", - "defineProperty", - "getOwnPropertyDescriptor", - "getPrototypeOf", - "setPrototypeOf", - "isExtensible", - "getOwnPropertyNames", - "preventExtensions" - ] - }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], + schema: [ + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: [ + "apply", + "call", + "delete", + "defineProperty", + "getOwnPropertyDescriptor", + "getPrototypeOf", + "setPrototypeOf", + "isExtensible", + "getOwnPropertyNames", + "preventExtensions", + ], + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], - messages: { - preferReflect: "Avoid using {{existing}}, instead use {{substitute}}." - } - }, + messages: { + preferReflect: + "Avoid using {{existing}}, instead use {{substitute}}.", + }, + }, - create(context) { - const existingNames = { - apply: "Function.prototype.apply", - call: "Function.prototype.call", - defineProperty: "Object.defineProperty", - getOwnPropertyDescriptor: "Object.getOwnPropertyDescriptor", - getPrototypeOf: "Object.getPrototypeOf", - setPrototypeOf: "Object.setPrototypeOf", - isExtensible: "Object.isExtensible", - getOwnPropertyNames: "Object.getOwnPropertyNames", - preventExtensions: "Object.preventExtensions" - }; + create(context) { + const existingNames = { + apply: "Function.prototype.apply", + call: "Function.prototype.call", + defineProperty: "Object.defineProperty", + getOwnPropertyDescriptor: "Object.getOwnPropertyDescriptor", + getPrototypeOf: "Object.getPrototypeOf", + setPrototypeOf: "Object.setPrototypeOf", + isExtensible: "Object.isExtensible", + getOwnPropertyNames: "Object.getOwnPropertyNames", + preventExtensions: "Object.preventExtensions", + }; - const reflectSubstitutes = { - apply: "Reflect.apply", - call: "Reflect.apply", - defineProperty: "Reflect.defineProperty", - getOwnPropertyDescriptor: "Reflect.getOwnPropertyDescriptor", - getPrototypeOf: "Reflect.getPrototypeOf", - setPrototypeOf: "Reflect.setPrototypeOf", - isExtensible: "Reflect.isExtensible", - getOwnPropertyNames: "Reflect.getOwnPropertyNames", - preventExtensions: "Reflect.preventExtensions" - }; + const reflectSubstitutes = { + apply: "Reflect.apply", + call: "Reflect.apply", + defineProperty: "Reflect.defineProperty", + getOwnPropertyDescriptor: "Reflect.getOwnPropertyDescriptor", + getPrototypeOf: "Reflect.getPrototypeOf", + setPrototypeOf: "Reflect.setPrototypeOf", + isExtensible: "Reflect.isExtensible", + getOwnPropertyNames: "Reflect.getOwnPropertyNames", + preventExtensions: "Reflect.preventExtensions", + }; - const exceptions = (context.options[0] || {}).exceptions || []; + const exceptions = (context.options[0] || {}).exceptions || []; - /** - * Reports the Reflect violation based on the `existing` and `substitute` - * @param {Object} node The node that violates the rule. - * @param {string} existing The existing method name that has been used. - * @param {string} substitute The Reflect substitute that should be used. - * @returns {void} - */ - function report(node, existing, substitute) { - context.report({ - node, - messageId: "preferReflect", - data: { - existing, - substitute - } - }); - } + /** + * Reports the Reflect violation based on the `existing` and `substitute` + * @param {Object} node The node that violates the rule. + * @param {string} existing The existing method name that has been used. + * @param {string} substitute The Reflect substitute that should be used. + * @returns {void} + */ + function report(node, existing, substitute) { + context.report({ + node, + messageId: "preferReflect", + data: { + existing, + substitute, + }, + }); + } - return { - CallExpression(node) { - const methodName = (node.callee.property || {}).name; - const isReflectCall = (node.callee.object || {}).name === "Reflect"; - const hasReflectSubstitute = Object.hasOwn(reflectSubstitutes, methodName); - const userConfiguredException = exceptions.includes(methodName); + return { + CallExpression(node) { + const methodName = (node.callee.property || {}).name; + const isReflectCall = + (node.callee.object || {}).name === "Reflect"; + const hasReflectSubstitute = Object.hasOwn( + reflectSubstitutes, + methodName, + ); + const userConfiguredException = exceptions.includes(methodName); - if (hasReflectSubstitute && !isReflectCall && !userConfiguredException) { - report(node, existingNames[methodName], reflectSubstitutes[methodName]); - } - }, - UnaryExpression(node) { - const isDeleteOperator = node.operator === "delete"; - const targetsIdentifier = node.argument.type === "Identifier"; - const userConfiguredException = exceptions.includes("delete"); + if ( + hasReflectSubstitute && + !isReflectCall && + !userConfiguredException + ) { + report( + node, + existingNames[methodName], + reflectSubstitutes[methodName], + ); + } + }, + UnaryExpression(node) { + const isDeleteOperator = node.operator === "delete"; + const targetsIdentifier = node.argument.type === "Identifier"; + const userConfiguredException = exceptions.includes("delete"); - if (isDeleteOperator && !targetsIdentifier && !userConfiguredException) { - report(node, "the delete keyword", "Reflect.deleteProperty"); - } - } - }; - - } + if ( + isDeleteOperator && + !targetsIdentifier && + !userConfiguredException + ) { + report( + node, + "the delete keyword", + "Reflect.deleteProperty", + ); + } + }, + }; + }, }; diff --git a/lib/rules/prefer-regex-literals.js b/lib/rules/prefer-regex-literals.js index f760102fb341..952ee722673d 100644 --- a/lib/rules/prefer-regex-literals.js +++ b/lib/rules/prefer-regex-literals.js @@ -10,8 +10,17 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"); -const { CALL, CONSTRUCT, ReferenceTracker, findVariable } = require("@eslint-community/eslint-utils"); -const { RegExpValidator, visitRegExpAST, RegExpParser } = require("@eslint-community/regexpp"); +const { + CALL, + CONSTRUCT, + ReferenceTracker, + findVariable, +} = require("@eslint-community/eslint-utils"); +const { + RegExpValidator, + visitRegExpAST, + RegExpParser, +} = require("@eslint-community/regexpp"); const { canTokensBeAdjacent } = require("./utils/ast-utils"); const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); @@ -25,7 +34,7 @@ const { REGEXPP_LATEST_ECMA_VERSION } = require("./utils/regular-expressions"); * @returns {boolean} True if the node is a string literal. */ function isStringLiteral(node) { - return node.type === "Literal" && typeof node.value === "string"; + return node.type === "Literal" && typeof node.value === "string"; } /** @@ -34,477 +43,580 @@ function isStringLiteral(node) { * @returns {boolean} True if the node is a regex literal. */ function isRegexLiteral(node) { - return node.type === "Literal" && Object.hasOwn(node, "regex"); + return node.type === "Literal" && Object.hasOwn(node, "regex"); } const validPrecedingTokens = new Set([ - "(", - ";", - "[", - ",", - "=", - "+", - "*", - "-", - "?", - "~", - "%", - "**", - "!", - "typeof", - "instanceof", - "&&", - "||", - "??", - "return", - "...", - "delete", - "void", - "in", - "<", - ">", - "<=", - ">=", - "==", - "===", - "!=", - "!==", - "<<", - ">>", - ">>>", - "&", - "|", - "^", - ":", - "{", - "=>", - "*=", - "<<=", - ">>=", - ">>>=", - "^=", - "|=", - "&=", - "??=", - "||=", - "&&=", - "**=", - "+=", - "-=", - "/=", - "%=", - "/", - "do", - "break", - "continue", - "debugger", - "case", - "throw" + "(", + ";", + "[", + ",", + "=", + "+", + "*", + "-", + "?", + "~", + "%", + "**", + "!", + "typeof", + "instanceof", + "&&", + "||", + "??", + "return", + "...", + "delete", + "void", + "in", + "<", + ">", + "<=", + ">=", + "==", + "===", + "!=", + "!==", + "<<", + ">>", + ">>>", + "&", + "|", + "^", + ":", + "{", + "=>", + "*=", + "<<=", + ">>=", + ">>>=", + "^=", + "|=", + "&=", + "??=", + "||=", + "&&=", + "**=", + "+=", + "-=", + "/=", + "%=", + "/", + "do", + "break", + "continue", + "debugger", + "case", + "throw", ]); - //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - disallowRedundantWrapping: false - }], - - docs: { - description: "Disallow use of the `RegExp` constructor in favor of regular expression literals", - recommended: false, - url: "https://eslint.org/docs/latest/rules/prefer-regex-literals" - }, - - hasSuggestions: true, - - schema: [ - { - type: "object", - properties: { - disallowRedundantWrapping: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedRegExp: "Use a regular expression literal instead of the 'RegExp' constructor.", - replaceWithLiteral: "Replace with an equivalent regular expression literal.", - replaceWithLiteralAndFlags: "Replace with an equivalent regular expression literal with flags '{{ flags }}'.", - replaceWithIntendedLiteralAndFlags: "Replace with a regular expression literal with flags '{{ flags }}'.", - unexpectedRedundantRegExp: "Regular expression literal is unnecessarily wrapped within a 'RegExp' constructor.", - unexpectedRedundantRegExpWithFlags: "Use regular expression literal with flags instead of the 'RegExp' constructor." - } - }, - - create(context) { - const [{ disallowRedundantWrapping }] = context.options; - const sourceCode = context.sourceCode; - - /** - * Determines whether the given identifier node is a reference to a global variable. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} True if the identifier is a reference to a global variable. - */ - function isGlobalReference(node) { - const scope = sourceCode.getScope(node); - const variable = findVariable(scope, node); - - return variable !== null && variable.scope.type === "global" && variable.defs.length === 0; - } - - /** - * Determines whether the given node is a String.raw`` tagged template expression - * with a static template literal. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is String.raw`` with a static template. - */ - function isStringRawTaggedStaticTemplateLiteral(node) { - return node.type === "TaggedTemplateExpression" && - astUtils.isSpecificMemberAccess(node.tag, "String", "raw") && - isGlobalReference(astUtils.skipChainExpression(node.tag).object) && - astUtils.isStaticTemplateLiteral(node.quasi); - } - - /** - * Gets the value of a string - * @param {ASTNode} node The node to get the string of. - * @returns {string|null} The value of the node. - */ - function getStringValue(node) { - if (isStringLiteral(node)) { - return node.value; - } - - if (astUtils.isStaticTemplateLiteral(node)) { - return node.quasis[0].value.cooked; - } - - if (isStringRawTaggedStaticTemplateLiteral(node)) { - return node.quasi.quasis[0].value.raw; - } - - return null; - } - - /** - * Determines whether the given node is considered to be a static string by the logic of this rule. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is a static string. - */ - function isStaticString(node) { - return isStringLiteral(node) || - astUtils.isStaticTemplateLiteral(node) || - isStringRawTaggedStaticTemplateLiteral(node); - } - - /** - * Determines whether the relevant arguments of the given are all static string literals. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if all arguments are static strings. - */ - function hasOnlyStaticStringArguments(node) { - const args = node.arguments; - - if ((args.length === 1 || args.length === 2) && args.every(isStaticString)) { - return true; - } - - return false; - } - - /** - * Determines whether the arguments of the given node indicate that a regex literal is unnecessarily wrapped. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node already contains a regex literal argument. - */ - function isUnnecessarilyWrappedRegexLiteral(node) { - const args = node.arguments; - - if (args.length === 1 && isRegexLiteral(args[0])) { - return true; - } - - if (args.length === 2 && isRegexLiteral(args[0]) && isStaticString(args[1])) { - return true; - } - - return false; - } - - /** - * Returns a ecmaVersion compatible for regexpp. - * @param {number} ecmaVersion The ecmaVersion to convert. - * @returns {import("@eslint-community/regexpp/ecma-versions").EcmaVersion} The resulting ecmaVersion compatible for regexpp. - */ - function getRegexppEcmaVersion(ecmaVersion) { - if (ecmaVersion <= 5) { - return 5; - } - return Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION); - } - - const regexppEcmaVersion = getRegexppEcmaVersion(context.languageOptions.ecmaVersion); - - /** - * Makes a character escaped or else returns null. - * @param {string} character The character to escape. - * @returns {string} The resulting escaped character. - */ - function resolveEscapes(character) { - switch (character) { - case "\n": - case "\\\n": - return "\\n"; - - case "\r": - case "\\\r": - return "\\r"; - - case "\t": - case "\\\t": - return "\\t"; - - case "\v": - case "\\\v": - return "\\v"; - - case "\f": - case "\\\f": - return "\\f"; - - case "/": - return "\\/"; - - default: - return null; - } - } - - /** - * Checks whether the given regex and flags are valid for the ecma version or not. - * @param {string} pattern The regex pattern to check. - * @param {string | undefined} flags The regex flags to check. - * @returns {boolean} True if the given regex pattern and flags are valid for the ecma version. - */ - function isValidRegexForEcmaVersion(pattern, flags) { - const validator = new RegExpValidator({ ecmaVersion: regexppEcmaVersion }); - - try { - validator.validatePattern(pattern, 0, pattern.length, { - unicode: flags ? flags.includes("u") : false, - unicodeSets: flags ? flags.includes("v") : false - }); - if (flags) { - validator.validateFlags(flags); - } - return true; - } catch { - return false; - } - } - - /** - * Checks whether two given regex flags contain the same flags or not. - * @param {string} flagsA The regex flags. - * @param {string} flagsB The regex flags. - * @returns {boolean} True if two regex flags contain same flags. - */ - function areFlagsEqual(flagsA, flagsB) { - return [...flagsA].sort().join("") === [...flagsB].sort().join(""); - } - - - /** - * Merges two regex flags. - * @param {string} flagsA The regex flags. - * @param {string} flagsB The regex flags. - * @returns {string} The merged regex flags. - */ - function mergeRegexFlags(flagsA, flagsB) { - const flagsSet = new Set([ - ...flagsA, - ...flagsB - ]); - - return [...flagsSet].join(""); - } - - /** - * Checks whether a give node can be fixed to the given regex pattern and flags. - * @param {ASTNode} node The node to check. - * @param {string} pattern The regex pattern to check. - * @param {string} flags The regex flags - * @returns {boolean} True if a node can be fixed to the given regex pattern and flags. - */ - function canFixTo(node, pattern, flags) { - const tokenBefore = sourceCode.getTokenBefore(node); - - return sourceCode.getCommentsInside(node).length === 0 && - (!tokenBefore || validPrecedingTokens.has(tokenBefore.value)) && - isValidRegexForEcmaVersion(pattern, flags); - } - - /** - * Returns a safe output code considering the before and after tokens. - * @param {ASTNode} node The regex node. - * @param {string} newRegExpValue The new regex expression value. - * @returns {string} The output code. - */ - function getSafeOutput(node, newRegExpValue) { - const tokenBefore = sourceCode.getTokenBefore(node); - const tokenAfter = sourceCode.getTokenAfter(node); - - return (tokenBefore && !canTokensBeAdjacent(tokenBefore, newRegExpValue) && tokenBefore.range[1] === node.range[0] ? " " : "") + - newRegExpValue + - (tokenAfter && !canTokensBeAdjacent(newRegExpValue, tokenAfter) && node.range[1] === tokenAfter.range[0] ? " " : ""); - - } - - return { - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const traceMap = { - RegExp: { - [CALL]: true, - [CONSTRUCT]: true - } - }; - - for (const { node: refNode } of tracker.iterateGlobalReferences(traceMap)) { - if (disallowRedundantWrapping && isUnnecessarilyWrappedRegexLiteral(refNode)) { - const regexNode = refNode.arguments[0]; - - if (refNode.arguments.length === 2) { - const suggests = []; - - const argFlags = getStringValue(refNode.arguments[1]) || ""; - - if (canFixTo(refNode, regexNode.regex.pattern, argFlags)) { - suggests.push({ - messageId: "replaceWithLiteralAndFlags", - pattern: regexNode.regex.pattern, - flags: argFlags - }); - } - - const literalFlags = regexNode.regex.flags || ""; - const mergedFlags = mergeRegexFlags(literalFlags, argFlags); - - if ( - !areFlagsEqual(mergedFlags, argFlags) && - canFixTo(refNode, regexNode.regex.pattern, mergedFlags) - ) { - suggests.push({ - messageId: "replaceWithIntendedLiteralAndFlags", - pattern: regexNode.regex.pattern, - flags: mergedFlags - }); - } - - context.report({ - node: refNode, - messageId: "unexpectedRedundantRegExpWithFlags", - suggest: suggests.map(({ flags, pattern, messageId }) => ({ - messageId, - data: { - flags - }, - fix(fixer) { - return fixer.replaceText(refNode, getSafeOutput(refNode, `/${pattern}/${flags}`)); - } - })) - }); - } else { - const outputs = []; - - if (canFixTo(refNode, regexNode.regex.pattern, regexNode.regex.flags)) { - outputs.push(sourceCode.getText(regexNode)); - } - - - context.report({ - node: refNode, - messageId: "unexpectedRedundantRegExp", - suggest: outputs.map(output => ({ - messageId: "replaceWithLiteral", - fix(fixer) { - return fixer.replaceText( - refNode, - getSafeOutput(refNode, output) - ); - } - })) - }); - } - } else if (hasOnlyStaticStringArguments(refNode)) { - let regexContent = getStringValue(refNode.arguments[0]); - let noFix = false; - let flags; - - if (refNode.arguments[1]) { - flags = getStringValue(refNode.arguments[1]); - } - - if (!canFixTo(refNode, regexContent, flags)) { - noFix = true; - } - - if (!/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.> ({ + messageId, + data: { + flags, + }, + fix(fixer) { + return fixer.replaceText( + refNode, + getSafeOutput( + refNode, + `/${pattern}/${flags}`, + ), + ); + }, + }), + ), + }); + } else { + const outputs = []; + + if ( + canFixTo( + refNode, + regexNode.regex.pattern, + regexNode.regex.flags, + ) + ) { + outputs.push(sourceCode.getText(regexNode)); + } + + context.report({ + node: refNode, + messageId: "unexpectedRedundantRegExp", + suggest: outputs.map(output => ({ + messageId: "replaceWithLiteral", + fix(fixer) { + return fixer.replaceText( + refNode, + getSafeOutput(refNode, output), + ); + }, + })), + }); + } + } else if (hasOnlyStaticStringArguments(refNode)) { + let regexContent = getStringValue(refNode.arguments[0]); + let noFix = false; + let flags; + + if (refNode.arguments[1]) { + flags = getStringValue(refNode.arguments[1]); + } + + if (!canFixTo(refNode, regexContent, flags)) { + noFix = true; + } + + if ( + !/^[-a-zA-Z0-9\\[\](){} \t\r\n\v\f!@#$%^&*+^_=/~`.> accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), ""); - } - - /** - * Returns a template literal form of the given node. - * @param {ASTNode} currentNode A node that should be converted to a template literal - * @param {string} textBeforeNode Text that should appear before the node - * @param {string} textAfterNode Text that should appear after the node - * @returns {string} A string form of this node, represented as a template literal - */ - function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) { - if (currentNode.type === "Literal" && typeof currentNode.value === "string") { - - /* - * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted - * as a template placeholder. However, if the code already contains a backslash before the ${ or ` - * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause - * an actual backslash character to appear before the dollar sign). - */ - return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => { - if (matched.lastIndexOf("\\") % 2) { - return `\\${matched}`; - } - return matched; - - // Unescape any quotes that appear in the original Literal that no longer need to be escaped. - }).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), currentNode.raw[0])}\``; - } - - if (currentNode.type === "TemplateLiteral") { - return sourceCode.getText(currentNode); - } - - if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) { - const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+"); - const textBeforePlus = getTextBetween(currentNode.left, plusSign); - const textAfterPlus = getTextBetween(plusSign, currentNode.right); - const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left); - const rightStartsWithCurly = startsWithTemplateCurly(currentNode.right); - - if (leftEndsWithCurly) { - - // If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket. - // `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */ }${baz}` - return getTemplateLiteral(currentNode.left, textBeforeNode, textBeforePlus + textAfterPlus).slice(0, -1) + - getTemplateLiteral(currentNode.right, null, textAfterNode).slice(1); - } - if (rightStartsWithCurly) { - - // Otherwise, if the right side of the expression starts with a template curly, add the text there. - // 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */ bar}baz` - return getTemplateLiteral(currentNode.left, textBeforeNode, null).slice(0, -1) + - getTemplateLiteral(currentNode.right, textBeforePlus + textAfterPlus, textAfterNode).slice(1); - } - - /* - * Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put - * the text between them. - */ - return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`; - } - - return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``; - } - - /** - * Returns a fixer object that converts a non-string binary expression to a template literal - * @param {SourceCodeFixer} fixer The fixer object - * @param {ASTNode} node A node that should be converted to a template literal - * @returns {Object} A fix for this binary expression - */ - function fixNonStringBinaryExpression(fixer, node) { - const topBinaryExpr = getTopConcatBinaryExpression(node.parent); - - if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) { - return null; - } - - return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null)); - } - - /** - * Reports if a given node is string concatenation with non string literals. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkForStringConcat(node) { - if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) { - return; - } - - const topBinaryExpr = getTopConcatBinaryExpression(node.parent); - - // Checks whether or not this node had been checked already. - if (done[topBinaryExpr.range[0]]) { - return; - } - done[topBinaryExpr.range[0]] = true; - - if (hasNonStringLiteral(topBinaryExpr)) { - context.report({ - node: topBinaryExpr, - messageId: "unexpectedStringConcatenation", - fix: fixer => fixNonStringBinaryExpression(fixer, node) - }); - } - } - - return { - Program() { - done = Object.create(null); - }, - - Literal: checkForStringConcat, - TemplateLiteral: checkForStringConcat - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Require template literals instead of string concatenation", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/prefer-template", + }, + + schema: [], + fixable: "code", + + messages: { + unexpectedStringConcatenation: "Unexpected string concatenation.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + let done = Object.create(null); + + /** + * Gets the non-token text between two nodes, ignoring any other tokens that appear between the two tokens. + * @param {ASTNode} node1 The first node + * @param {ASTNode} node2 The second node + * @returns {string} The text between the nodes, excluding other tokens + */ + function getTextBetween(node1, node2) { + const allTokens = [node1] + .concat(sourceCode.getTokensBetween(node1, node2)) + .concat(node2); + const sourceText = sourceCode.getText(); + + return allTokens + .slice(0, -1) + .reduce( + (accumulator, token, index) => + accumulator + + sourceText.slice( + token.range[1], + allTokens[index + 1].range[0], + ), + "", + ); + } + + /** + * Returns a template literal form of the given node. + * @param {ASTNode} currentNode A node that should be converted to a template literal + * @param {string} textBeforeNode Text that should appear before the node + * @param {string} textAfterNode Text that should appear after the node + * @returns {string} A string form of this node, represented as a template literal + */ + function getTemplateLiteral( + currentNode, + textBeforeNode, + textAfterNode, + ) { + if ( + currentNode.type === "Literal" && + typeof currentNode.value === "string" + ) { + /* + * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted + * as a template placeholder. However, if the code already contains a backslash before the ${ or ` + * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause + * an actual backslash character to appear before the dollar sign). + */ + return `\`${currentNode.raw + .slice(1, -1) + .replace(/\\*(\$\{|`)/gu, matched => { + if (matched.lastIndexOf("\\") % 2) { + return `\\${matched}`; + } + return matched; + + // Unescape any quotes that appear in the original Literal that no longer need to be escaped. + }) + .replace( + new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), + currentNode.raw[0], + )}\``; + } + + if (currentNode.type === "TemplateLiteral") { + return sourceCode.getText(currentNode); + } + + if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) { + const plusSign = sourceCode.getFirstTokenBetween( + currentNode.left, + currentNode.right, + token => token.value === "+", + ); + const textBeforePlus = getTextBetween( + currentNode.left, + plusSign, + ); + const textAfterPlus = getTextBetween( + plusSign, + currentNode.right, + ); + const leftEndsWithCurly = endsWithTemplateCurly( + currentNode.left, + ); + const rightStartsWithCurly = startsWithTemplateCurly( + currentNode.right, + ); + + if (leftEndsWithCurly) { + // If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket. + // `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */ }${baz}` + return ( + getTemplateLiteral( + currentNode.left, + textBeforeNode, + textBeforePlus + textAfterPlus, + ).slice(0, -1) + + getTemplateLiteral( + currentNode.right, + null, + textAfterNode, + ).slice(1) + ); + } + if (rightStartsWithCurly) { + // Otherwise, if the right side of the expression starts with a template curly, add the text there. + // 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */ bar}baz` + return ( + getTemplateLiteral( + currentNode.left, + textBeforeNode, + null, + ).slice(0, -1) + + getTemplateLiteral( + currentNode.right, + textBeforePlus + textAfterPlus, + textAfterNode, + ).slice(1) + ); + } + + /* + * Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put + * the text between them. + */ + return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`; + } + + return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``; + } + + /** + * Returns a fixer object that converts a non-string binary expression to a template literal + * @param {SourceCodeFixer} fixer The fixer object + * @param {ASTNode} node A node that should be converted to a template literal + * @returns {Object} A fix for this binary expression + */ + function fixNonStringBinaryExpression(fixer, node) { + const topBinaryExpr = getTopConcatBinaryExpression(node.parent); + + if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) { + return null; + } + + return fixer.replaceText( + topBinaryExpr, + getTemplateLiteral(topBinaryExpr, null, null), + ); + } + + /** + * Reports if a given node is string concatenation with non string literals. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkForStringConcat(node) { + if ( + !astUtils.isStringLiteral(node) || + !isConcatenation(node.parent) + ) { + return; + } + + const topBinaryExpr = getTopConcatBinaryExpression(node.parent); + + // Checks whether or not this node had been checked already. + if (done[topBinaryExpr.range[0]]) { + return; + } + done[topBinaryExpr.range[0]] = true; + + if (hasNonStringLiteral(topBinaryExpr)) { + context.report({ + node: topBinaryExpr, + messageId: "unexpectedStringConcatenation", + fix: fixer => fixNonStringBinaryExpression(fixer, node), + }); + } + } + + return { + Program() { + done = Object.create(null); + }, + + Literal: checkForStringConcat, + TemplateLiteral: checkForStringConcat, + }; + }, }; diff --git a/lib/rules/quote-props.js b/lib/rules/quote-props.js index cd439883f85d..ee602dab7a3d 100644 --- a/lib/rules/quote-props.js +++ b/lib/rules/quote-props.js @@ -19,310 +19,376 @@ const keywords = require("./utils/keywords"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "quote-props", - url: "https://eslint.style/rules/js/quote-props" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Require quotes around object literal property names", - recommended: false, - url: "https://eslint.org/docs/latest/rules/quote-props" - }, - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["always", "as-needed", "consistent", "consistent-as-needed"] - } - ], - minItems: 0, - maxItems: 1 - }, - { - type: "array", - items: [ - { - enum: ["always", "as-needed", "consistent", "consistent-as-needed"] - }, - { - type: "object", - properties: { - keywords: { - type: "boolean" - }, - unnecessary: { - type: "boolean" - }, - numbers: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - fixable: "code", - messages: { - requireQuotesDueToReservedWord: "Properties should be quoted as '{{property}}' is a reserved word.", - inconsistentlyQuotedProperty: "Inconsistently quoted property '{{key}}' found.", - unnecessarilyQuotedProperty: "Unnecessarily quoted property '{{property}}' found.", - unquotedReservedProperty: "Unquoted reserved word '{{property}}' used as key.", - unquotedNumericProperty: "Unquoted number literal '{{property}}' used as key.", - unquotedPropertyFound: "Unquoted property '{{property}}' found.", - redundantQuoting: "Properties shouldn't be quoted as all quotes are redundant." - } - }, - - create(context) { - - const MODE = context.options[0], - KEYWORDS = context.options[1] && context.options[1].keywords, - CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false, - NUMBERS = context.options[1] && context.options[1].numbers, - - sourceCode = context.sourceCode; - - - /** - * Checks whether a certain string constitutes an ES3 token - * @param {string} tokenStr The string to be checked. - * @returns {boolean} `true` if it is an ES3 token. - */ - function isKeyword(tokenStr) { - return keywords.includes(tokenStr); - } - - /** - * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) - * @param {string} rawKey The raw key value from the source - * @param {espreeTokens} tokens The espree-tokenized node key - * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked - * @returns {boolean} Whether or not a key has redundant quotes. - * @private - */ - function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) { - return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length && - (["Identifier", "Keyword", "Null", "Boolean"].includes(tokens[0].type) || - (tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value)); - } - - /** - * Returns a string representation of a property node with quotes removed - * @param {ASTNode} key Key AST Node, which may or may not be quoted - * @returns {string} A replacement string for this property - */ - function getUnquotedKey(key) { - return key.type === "Identifier" ? key.name : key.value; - } - - /** - * Returns a string representation of a property node with quotes added - * @param {ASTNode} key Key AST Node, which may or may not be quoted - * @returns {string} A replacement string for this property - */ - function getQuotedKey(key) { - if (key.type === "Literal" && typeof key.value === "string") { - - // If the key is already a string literal, don't replace the quotes with double quotes. - return sourceCode.getText(key); - } - - // Otherwise, the key is either an identifier or a number literal. - return `"${key.type === "Identifier" ? key.name : key.value}"`; - } - - /** - * Ensures that a property's key is quoted only when necessary - * @param {ASTNode} node Property AST node - * @returns {void} - */ - function checkUnnecessaryQuotes(node) { - const key = node.key; - - if (node.method || node.computed || node.shorthand) { - return; - } - - if (key.type === "Literal" && typeof key.value === "string") { - let tokens; - - try { - tokens = espree.tokenize(key.value); - } catch { - return; - } - - if (tokens.length !== 1) { - return; - } - - const isKeywordToken = isKeyword(tokens[0].value); - - if (isKeywordToken && KEYWORDS) { - return; - } - - if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) { - context.report({ - node, - messageId: "unnecessarilyQuotedProperty", - data: { property: key.value }, - fix: fixer => fixer.replaceText(key, getUnquotedKey(key)) - }); - } - } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) { - context.report({ - node, - messageId: "unquotedReservedProperty", - data: { property: key.name }, - fix: fixer => fixer.replaceText(key, getQuotedKey(key)) - }); - } else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) { - context.report({ - node, - messageId: "unquotedNumericProperty", - data: { property: key.value }, - fix: fixer => fixer.replaceText(key, getQuotedKey(key)) - }); - } - } - - /** - * Ensures that a property's key is quoted - * @param {ASTNode} node Property AST node - * @returns {void} - */ - function checkOmittedQuotes(node) { - const key = node.key; - - if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) { - context.report({ - node, - messageId: "unquotedPropertyFound", - data: { property: key.name || key.value }, - fix: fixer => fixer.replaceText(key, getQuotedKey(key)) - }); - } - } - - /** - * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes - * @param {ASTNode} node Property AST node - * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy - * @returns {void} - */ - function checkConsistency(node, checkQuotesRedundancy) { - const quotedProps = [], - unquotedProps = []; - let keywordKeyName = null, - necessaryQuotes = false; - - node.properties.forEach(property => { - const key = property.key; - - if (!key || property.method || property.computed || property.shorthand) { - return; - } - - if (key.type === "Literal" && typeof key.value === "string") { - - quotedProps.push(property); - - if (checkQuotesRedundancy) { - let tokens; - - try { - tokens = espree.tokenize(key.value); - } catch { - necessaryQuotes = true; - return; - } - - necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value); - } - } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) { - unquotedProps.push(property); - necessaryQuotes = true; - keywordKeyName = key.name; - } else { - unquotedProps.push(property); - } - }); - - if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) { - quotedProps.forEach(property => { - context.report({ - node: property, - messageId: "redundantQuoting", - fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key)) - }); - }); - } else if (unquotedProps.length && keywordKeyName) { - unquotedProps.forEach(property => { - context.report({ - node: property, - messageId: "requireQuotesDueToReservedWord", - data: { property: keywordKeyName }, - fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key)) - }); - }); - } else if (quotedProps.length && unquotedProps.length) { - unquotedProps.forEach(property => { - context.report({ - node: property, - messageId: "inconsistentlyQuotedProperty", - data: { key: property.key.name || property.key.value }, - fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key)) - }); - }); - } - } - - return { - Property(node) { - if (MODE === "always" || !MODE) { - checkOmittedQuotes(node); - } - if (MODE === "as-needed") { - checkUnnecessaryQuotes(node); - } - }, - ObjectExpression(node) { - if (MODE === "consistent") { - checkConsistency(node, false); - } - if (MODE === "consistent-as-needed") { - checkConsistency(node, true); - } - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "quote-props", + url: "https://eslint.style/rules/js/quote-props", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: "Require quotes around object literal property names", + recommended: false, + url: "https://eslint.org/docs/latest/rules/quote-props", + }, + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: [ + "always", + "as-needed", + "consistent", + "consistent-as-needed", + ], + }, + ], + minItems: 0, + maxItems: 1, + }, + { + type: "array", + items: [ + { + enum: [ + "always", + "as-needed", + "consistent", + "consistent-as-needed", + ], + }, + { + type: "object", + properties: { + keywords: { + type: "boolean", + }, + unnecessary: { + type: "boolean", + }, + numbers: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + fixable: "code", + messages: { + requireQuotesDueToReservedWord: + "Properties should be quoted as '{{property}}' is a reserved word.", + inconsistentlyQuotedProperty: + "Inconsistently quoted property '{{key}}' found.", + unnecessarilyQuotedProperty: + "Unnecessarily quoted property '{{property}}' found.", + unquotedReservedProperty: + "Unquoted reserved word '{{property}}' used as key.", + unquotedNumericProperty: + "Unquoted number literal '{{property}}' used as key.", + unquotedPropertyFound: "Unquoted property '{{property}}' found.", + redundantQuoting: + "Properties shouldn't be quoted as all quotes are redundant.", + }, + }, + + create(context) { + const MODE = context.options[0], + KEYWORDS = context.options[1] && context.options[1].keywords, + CHECK_UNNECESSARY = + !context.options[1] || context.options[1].unnecessary !== false, + NUMBERS = context.options[1] && context.options[1].numbers, + sourceCode = context.sourceCode; + + /** + * Checks whether a certain string constitutes an ES3 token + * @param {string} tokenStr The string to be checked. + * @returns {boolean} `true` if it is an ES3 token. + */ + function isKeyword(tokenStr) { + return keywords.includes(tokenStr); + } + + /** + * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary) + * @param {string} rawKey The raw key value from the source + * @param {espreeTokens} tokens The espree-tokenized node key + * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked + * @returns {boolean} Whether or not a key has redundant quotes. + * @private + */ + function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) { + return ( + tokens.length === 1 && + tokens[0].start === 0 && + tokens[0].end === rawKey.length && + (["Identifier", "Keyword", "Null", "Boolean"].includes( + tokens[0].type, + ) || + (tokens[0].type === "Numeric" && + !skipNumberLiterals && + String(+tokens[0].value) === tokens[0].value)) + ); + } + + /** + * Returns a string representation of a property node with quotes removed + * @param {ASTNode} key Key AST Node, which may or may not be quoted + * @returns {string} A replacement string for this property + */ + function getUnquotedKey(key) { + return key.type === "Identifier" ? key.name : key.value; + } + + /** + * Returns a string representation of a property node with quotes added + * @param {ASTNode} key Key AST Node, which may or may not be quoted + * @returns {string} A replacement string for this property + */ + function getQuotedKey(key) { + if (key.type === "Literal" && typeof key.value === "string") { + // If the key is already a string literal, don't replace the quotes with double quotes. + return sourceCode.getText(key); + } + + // Otherwise, the key is either an identifier or a number literal. + return `"${key.type === "Identifier" ? key.name : key.value}"`; + } + + /** + * Ensures that a property's key is quoted only when necessary + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkUnnecessaryQuotes(node) { + const key = node.key; + + if (node.method || node.computed || node.shorthand) { + return; + } + + if (key.type === "Literal" && typeof key.value === "string") { + let tokens; + + try { + tokens = espree.tokenize(key.value); + } catch { + return; + } + + if (tokens.length !== 1) { + return; + } + + const isKeywordToken = isKeyword(tokens[0].value); + + if (isKeywordToken && KEYWORDS) { + return; + } + + if ( + CHECK_UNNECESSARY && + areQuotesRedundant(key.value, tokens, NUMBERS) + ) { + context.report({ + node, + messageId: "unnecessarilyQuotedProperty", + data: { property: key.value }, + fix: fixer => + fixer.replaceText(key, getUnquotedKey(key)), + }); + } + } else if ( + KEYWORDS && + key.type === "Identifier" && + isKeyword(key.name) + ) { + context.report({ + node, + messageId: "unquotedReservedProperty", + data: { property: key.name }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)), + }); + } else if ( + NUMBERS && + key.type === "Literal" && + astUtils.isNumericLiteral(key) + ) { + context.report({ + node, + messageId: "unquotedNumericProperty", + data: { property: key.value }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)), + }); + } + } + + /** + * Ensures that a property's key is quoted + * @param {ASTNode} node Property AST node + * @returns {void} + */ + function checkOmittedQuotes(node) { + const key = node.key; + + if ( + !node.method && + !node.computed && + !node.shorthand && + !(key.type === "Literal" && typeof key.value === "string") + ) { + context.report({ + node, + messageId: "unquotedPropertyFound", + data: { property: key.name || key.value }, + fix: fixer => fixer.replaceText(key, getQuotedKey(key)), + }); + } + } + + /** + * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes + * @param {ASTNode} node Property AST node + * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy + * @returns {void} + */ + function checkConsistency(node, checkQuotesRedundancy) { + const quotedProps = [], + unquotedProps = []; + let keywordKeyName = null, + necessaryQuotes = false; + + node.properties.forEach(property => { + const key = property.key; + + if ( + !key || + property.method || + property.computed || + property.shorthand + ) { + return; + } + + if (key.type === "Literal" && typeof key.value === "string") { + quotedProps.push(property); + + if (checkQuotesRedundancy) { + let tokens; + + try { + tokens = espree.tokenize(key.value); + } catch { + necessaryQuotes = true; + return; + } + + necessaryQuotes = + necessaryQuotes || + !areQuotesRedundant(key.value, tokens) || + (KEYWORDS && isKeyword(tokens[0].value)); + } + } else if ( + KEYWORDS && + checkQuotesRedundancy && + key.type === "Identifier" && + isKeyword(key.name) + ) { + unquotedProps.push(property); + necessaryQuotes = true; + keywordKeyName = key.name; + } else { + unquotedProps.push(property); + } + }); + + if ( + checkQuotesRedundancy && + quotedProps.length && + !necessaryQuotes + ) { + quotedProps.forEach(property => { + context.report({ + node: property, + messageId: "redundantQuoting", + fix: fixer => + fixer.replaceText( + property.key, + getUnquotedKey(property.key), + ), + }); + }); + } else if (unquotedProps.length && keywordKeyName) { + unquotedProps.forEach(property => { + context.report({ + node: property, + messageId: "requireQuotesDueToReservedWord", + data: { property: keywordKeyName }, + fix: fixer => + fixer.replaceText( + property.key, + getQuotedKey(property.key), + ), + }); + }); + } else if (quotedProps.length && unquotedProps.length) { + unquotedProps.forEach(property => { + context.report({ + node: property, + messageId: "inconsistentlyQuotedProperty", + data: { key: property.key.name || property.key.value }, + fix: fixer => + fixer.replaceText( + property.key, + getQuotedKey(property.key), + ), + }); + }); + } + } + + return { + Property(node) { + if (MODE === "always" || !MODE) { + checkOmittedQuotes(node); + } + if (MODE === "as-needed") { + checkUnnecessaryQuotes(node); + } + }, + ObjectExpression(node) { + if (MODE === "consistent") { + checkConsistency(node, false); + } + if (MODE === "consistent-as-needed") { + checkConsistency(node, true); + } + }, + }; + }, }; diff --git a/lib/rules/quotes.js b/lib/rules/quotes.js index fd754c2f5aaa..3ddf50623860 100644 --- a/lib/rules/quotes.js +++ b/lib/rules/quotes.js @@ -17,25 +17,28 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const QUOTE_SETTINGS = { - double: { - quote: "\"", - alternateQuote: "'", - description: "doublequote" - }, - single: { - quote: "'", - alternateQuote: "\"", - description: "singlequote" - }, - backtick: { - quote: "`", - alternateQuote: "\"", - description: "backtick" - } + double: { + quote: '"', + alternateQuote: "'", + description: "doublequote", + }, + single: { + quote: "'", + alternateQuote: '"', + description: "singlequote", + }, + backtick: { + quote: "`", + alternateQuote: '"', + description: "backtick", + }, }; // An unescaped newline is a newline preceded by an even number of backslashes. -const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, "u"); +const UNESCAPED_LINEBREAK_PATTERN = new RegExp( + String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, + "u", +); /** * Switches quoting of javascript string between ' " and ` @@ -47,27 +50,43 @@ const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Arr * @private */ QUOTE_SETTINGS.double.convert = -QUOTE_SETTINGS.single.convert = -QUOTE_SETTINGS.backtick.convert = function(str) { - const newQuote = this.quote; - const oldQuote = str[0]; - - if (newQuote === oldQuote) { - return str; - } - return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => { - if (escaped === oldQuote || oldQuote === "`" && escaped === "${") { - return escaped; // unescape - } - if (match === newQuote || newQuote === "`" && match === "${") { - return `\\${match}`; // escape - } - if (newline && oldQuote === "`") { - return "\\n"; // escape newlines - } - return match; - }) + newQuote; -}; + QUOTE_SETTINGS.single.convert = + QUOTE_SETTINGS.backtick.convert = + function (str) { + const newQuote = this.quote; + const oldQuote = str[0]; + + if (newQuote === oldQuote) { + return str; + } + return ( + newQuote + + str + .slice(1, -1) + .replace( + /\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, + (match, escaped, newline) => { + if ( + escaped === oldQuote || + (oldQuote === "`" && escaped === "${") + ) { + return escaped; // unescape + } + if ( + match === newQuote || + (newQuote === "`" && match === "${") + ) { + return `\\${match}`; // escape + } + if (newline && oldQuote === "`") { + return "\\n"; // escape newlines + } + return match; + }, + ) + + newQuote + ); + }; const AVOID_ESCAPE = "avoid-escape"; @@ -77,292 +96,321 @@ const AVOID_ESCAPE = "avoid-escape"; /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "quotes", - url: "https://eslint.style/rules/js/quotes" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce the consistent use of either backticks, double, or single quotes", - recommended: false, - url: "https://eslint.org/docs/latest/rules/quotes" - }, - - fixable: "code", - - schema: [ - { - enum: ["single", "double", "backtick"] - }, - { - anyOf: [ - { - enum: ["avoid-escape"] - }, - { - type: "object", - properties: { - avoidEscape: { - type: "boolean" - }, - allowTemplateLiterals: { - type: "boolean" - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - wrongQuotes: "Strings must use {{description}}." - } - }, - - create(context) { - - const quoteOption = context.options[0], - settings = QUOTE_SETTINGS[quoteOption || "double"], - options = context.options[1], - allowTemplateLiterals = options && options.allowTemplateLiterals === true, - sourceCode = context.sourceCode; - let avoidEscape = options && options.avoidEscape === true; - - // deprecated - if (options === AVOID_ESCAPE) { - avoidEscape = true; - } - - /** - * Determines if a given node is part of JSX syntax. - * - * This function returns `true` in the following cases: - * - * - `
` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`. - * - `
foo
` ... If the literal is a text content, the parent of the literal is `JSXElement`. - * - `<>foo` ... If the literal is a text content, the parent of the literal is `JSXFragment`. - * - * In particular, this function returns `false` in the following cases: - * - * - `
` - * - `
{"foo"}
` - * - * In both cases, inside of the braces is handled as normal JavaScript. - * The braces are `JSXExpressionContainer` nodes. - * @param {ASTNode} node The Literal node to check. - * @returns {boolean} True if the node is a part of JSX, false if not. - * @private - */ - function isJSXLiteral(node) { - return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment"; - } - - /** - * Checks whether or not a given node is a directive. - * The directive is a `ExpressionStatement` which has only a string literal not surrounded by - * parentheses. - * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is a directive. - * @private - */ - function isDirective(node) { - return ( - node.type === "ExpressionStatement" && - node.expression.type === "Literal" && - typeof node.expression.value === "string" && - !astUtils.isParenthesised(sourceCode, node.expression) - ); - } - - /** - * Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. - * @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive} - * @param {ASTNode} node A node to check. - * @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. - * @private - */ - function isExpressionInOrJustAfterDirectivePrologue(node) { - if (!astUtils.isTopLevelExpressionStatement(node.parent)) { - return false; - } - const block = node.parent.parent; - - // Check the node is at a prologue. - for (let i = 0; i < block.body.length; ++i) { - const statement = block.body[i]; - - if (statement === node.parent) { - return true; - } - if (!isDirective(statement)) { - break; - } - } - - return false; - } - - /** - * Checks whether or not a given node is allowed as non backtick. - * @param {ASTNode} node A node to check. - * @returns {boolean} Whether or not the node is allowed as non backtick. - * @private - */ - function isAllowedAsNonBacktick(node) { - const parent = node.parent; - - switch (parent.type) { - - // Directive Prologues. - case "ExpressionStatement": - return !astUtils.isParenthesised(sourceCode, node) && isExpressionInOrJustAfterDirectivePrologue(node); - - // LiteralPropertyName. - case "Property": - case "PropertyDefinition": - case "MethodDefinition": - return parent.key === node && !parent.computed; - - // ModuleSpecifier. - case "ImportDeclaration": - case "ExportNamedDeclaration": - return parent.source === node; - - // ModuleExportName or ModuleSpecifier. - case "ExportAllDeclaration": - return parent.exported === node || parent.source === node; - - // ModuleExportName. - case "ImportSpecifier": - return parent.imported === node; - - // ModuleExportName. - case "ExportSpecifier": - return parent.local === node || parent.exported === node; - - // Others don't allow. - default: - return false; - } - } - - /** - * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings. - * @param {ASTNode} node A TemplateLiteral node to check. - * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings. - * @private - */ - function isUsingFeatureOfTemplateLiteral(node) { - const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi; - - if (hasTag) { - return true; - } - - const hasStringInterpolation = node.expressions.length > 0; - - if (hasStringInterpolation) { - return true; - } - - const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw); - - if (isMultilineString) { - return true; - } - - return false; - } - - return { - - Literal(node) { - const val = node.value, - rawVal = node.raw; - - if (settings && typeof val === "string") { - let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) || - isJSXLiteral(node) || - astUtils.isSurroundedBy(rawVal, settings.quote); - - if (!isValid && avoidEscape) { - isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.includes(settings.quote); - } - - if (!isValid) { - context.report({ - node, - messageId: "wrongQuotes", - data: { - description: settings.description - }, - fix(fixer) { - if (quoteOption === "backtick" && astUtils.hasOctalOrNonOctalDecimalEscapeSequence(rawVal)) { - - /* - * An octal or non-octal decimal escape sequence in a template literal would - * produce syntax error, even in non-strict mode. - */ - return null; - } - - return fixer.replaceText(node, settings.convert(node.raw)); - } - }); - } - } - }, - - TemplateLiteral(node) { - - // Don't throw an error if backticks are expected or a template literal feature is in use. - if ( - allowTemplateLiterals || - quoteOption === "backtick" || - isUsingFeatureOfTemplateLiteral(node) - ) { - return; - } - - context.report({ - node, - messageId: "wrongQuotes", - data: { - description: settings.description - }, - fix(fixer) { - if (astUtils.isTopLevelExpressionStatement(node.parent) && !astUtils.isParenthesised(sourceCode, node)) { - - /* - * TemplateLiterals aren't actually directives, but fixing them might turn - * them into directives and change the behavior of the code. - */ - return null; - } - return fixer.replaceText(node, settings.convert(sourceCode.getText(node))); - } - }); - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "quotes", + url: "https://eslint.style/rules/js/quotes", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce the consistent use of either backticks, double, or single quotes", + recommended: false, + url: "https://eslint.org/docs/latest/rules/quotes", + }, + + fixable: "code", + + schema: [ + { + enum: ["single", "double", "backtick"], + }, + { + anyOf: [ + { + enum: ["avoid-escape"], + }, + { + type: "object", + properties: { + avoidEscape: { + type: "boolean", + }, + allowTemplateLiterals: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + wrongQuotes: "Strings must use {{description}}.", + }, + }, + + create(context) { + const quoteOption = context.options[0], + settings = QUOTE_SETTINGS[quoteOption || "double"], + options = context.options[1], + allowTemplateLiterals = + options && options.allowTemplateLiterals === true, + sourceCode = context.sourceCode; + let avoidEscape = options && options.avoidEscape === true; + + // deprecated + if (options === AVOID_ESCAPE) { + avoidEscape = true; + } + + /** + * Determines if a given node is part of JSX syntax. + * + * This function returns `true` in the following cases: + * + * - `
` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`. + * - `
foo
` ... If the literal is a text content, the parent of the literal is `JSXElement`. + * - `<>foo` ... If the literal is a text content, the parent of the literal is `JSXFragment`. + * + * In particular, this function returns `false` in the following cases: + * + * - `
` + * - `
{"foo"}
` + * + * In both cases, inside of the braces is handled as normal JavaScript. + * The braces are `JSXExpressionContainer` nodes. + * @param {ASTNode} node The Literal node to check. + * @returns {boolean} True if the node is a part of JSX, false if not. + * @private + */ + function isJSXLiteral(node) { + return ( + node.parent.type === "JSXAttribute" || + node.parent.type === "JSXElement" || + node.parent.type === "JSXFragment" + ); + } + + /** + * Checks whether or not a given node is a directive. + * The directive is a `ExpressionStatement` which has only a string literal not surrounded by + * parentheses. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is a directive. + * @private + */ + function isDirective(node) { + return ( + node.type === "ExpressionStatement" && + node.expression.type === "Literal" && + typeof node.expression.value === "string" && + !astUtils.isParenthesised(sourceCode, node.expression) + ); + } + + /** + * Checks whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. + * @see {@link http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive} + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether a specified node is either part of, or immediately follows a (possibly empty) directive prologue. + * @private + */ + function isExpressionInOrJustAfterDirectivePrologue(node) { + if (!astUtils.isTopLevelExpressionStatement(node.parent)) { + return false; + } + const block = node.parent.parent; + + // Check the node is at a prologue. + for (let i = 0; i < block.body.length; ++i) { + const statement = block.body[i]; + + if (statement === node.parent) { + return true; + } + if (!isDirective(statement)) { + break; + } + } + + return false; + } + + /** + * Checks whether or not a given node is allowed as non backtick. + * @param {ASTNode} node A node to check. + * @returns {boolean} Whether or not the node is allowed as non backtick. + * @private + */ + function isAllowedAsNonBacktick(node) { + const parent = node.parent; + + switch (parent.type) { + // Directive Prologues. + case "ExpressionStatement": + return ( + !astUtils.isParenthesised(sourceCode, node) && + isExpressionInOrJustAfterDirectivePrologue(node) + ); + + // LiteralPropertyName. + case "Property": + case "PropertyDefinition": + case "MethodDefinition": + return parent.key === node && !parent.computed; + + // ModuleSpecifier. + case "ImportDeclaration": + case "ExportNamedDeclaration": + return parent.source === node; + + // ModuleExportName or ModuleSpecifier. + case "ExportAllDeclaration": + return parent.exported === node || parent.source === node; + + // ModuleExportName. + case "ImportSpecifier": + return parent.imported === node; + + // ModuleExportName. + case "ExportSpecifier": + return parent.local === node || parent.exported === node; + + // Others don't allow. + default: + return false; + } + } + + /** + * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings. + * @param {ASTNode} node A TemplateLiteral node to check. + * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings. + * @private + */ + function isUsingFeatureOfTemplateLiteral(node) { + const hasTag = + node.parent.type === "TaggedTemplateExpression" && + node === node.parent.quasi; + + if (hasTag) { + return true; + } + + const hasStringInterpolation = node.expressions.length > 0; + + if (hasStringInterpolation) { + return true; + } + + const isMultilineString = + node.quasis.length >= 1 && + UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw); + + if (isMultilineString) { + return true; + } + + return false; + } + + return { + Literal(node) { + const val = node.value, + rawVal = node.raw; + + if (settings && typeof val === "string") { + let isValid = + (quoteOption === "backtick" && + isAllowedAsNonBacktick(node)) || + isJSXLiteral(node) || + astUtils.isSurroundedBy(rawVal, settings.quote); + + if (!isValid && avoidEscape) { + isValid = + astUtils.isSurroundedBy( + rawVal, + settings.alternateQuote, + ) && rawVal.includes(settings.quote); + } + + if (!isValid) { + context.report({ + node, + messageId: "wrongQuotes", + data: { + description: settings.description, + }, + fix(fixer) { + if ( + quoteOption === "backtick" && + astUtils.hasOctalOrNonOctalDecimalEscapeSequence( + rawVal, + ) + ) { + /* + * An octal or non-octal decimal escape sequence in a template literal would + * produce syntax error, even in non-strict mode. + */ + return null; + } + + return fixer.replaceText( + node, + settings.convert(node.raw), + ); + }, + }); + } + } + }, + + TemplateLiteral(node) { + // Don't throw an error if backticks are expected or a template literal feature is in use. + if ( + allowTemplateLiterals || + quoteOption === "backtick" || + isUsingFeatureOfTemplateLiteral(node) + ) { + return; + } + + context.report({ + node, + messageId: "wrongQuotes", + data: { + description: settings.description, + }, + fix(fixer) { + if ( + astUtils.isTopLevelExpressionStatement( + node.parent, + ) && + !astUtils.isParenthesised(sourceCode, node) + ) { + /* + * TemplateLiterals aren't actually directives, but fixing them might turn + * them into directives and change the behavior of the code. + */ + return null; + } + return fixer.replaceText( + node, + settings.convert(sourceCode.getText(node)), + ); + }, + }); + }, + }; + }, }; diff --git a/lib/rules/radix.js b/lib/rules/radix.js index 6d37d18df761..cc7cbbc6be02 100644 --- a/lib/rules/radix.js +++ b/lib/rules/radix.js @@ -16,9 +16,11 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const MODE_ALWAYS = "always", - MODE_AS_NEEDED = "as-needed"; + MODE_AS_NEEDED = "as-needed"; -const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => index + 2)); +const validRadixValues = new Set( + Array.from({ length: 37 - 2 }, (_, index) => index + 2), +); /** * Checks whether a given variable is shadowed or not. @@ -26,7 +28,7 @@ const validRadixValues = new Set(Array.from({ length: 37 - 2 }, (_, index) => in * @returns {boolean} `true` if the variable is shadowed. */ function isShadowed(variable) { - return variable.defs.length >= 1; + return variable.defs.length >= 1; } /** @@ -36,12 +38,12 @@ function isShadowed(variable) { * method. */ function isParseIntMethod(node) { - return ( - node.type === "MemberExpression" && - !node.computed && - node.property.type === "Identifier" && - node.property.name === "parseInt" - ); + return ( + node.type === "MemberExpression" && + !node.computed && + node.property.type === "Identifier" && + node.property.name === "parseInt" + ); } /** @@ -55,10 +57,10 @@ function isParseIntMethod(node) { * @returns {boolean} `true` if the node is valid. */ function isValidRadix(radix) { - return !( - (radix.type === "Literal" && !validRadixValues.has(radix.value)) || - (radix.type === "Identifier" && radix.name === "undefined") - ); + return !( + (radix.type === "Literal" && !validRadixValues.has(radix.value)) || + (radix.type === "Identifier" && radix.name === "undefined") + ); } /** @@ -67,7 +69,7 @@ function isValidRadix(radix) { * @returns {boolean} `true` if the node is the literal node of `10`. */ function isDefaultRadix(radix) { - return radix.type === "Literal" && radix.value === 10; + return radix.type === "Literal" && radix.value === 10; } //------------------------------------------------------------------------------ @@ -76,125 +78,139 @@ function isDefaultRadix(radix) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [MODE_ALWAYS], - - docs: { - description: "Enforce the consistent use of the radix argument when using `parseInt()`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/radix" - }, - - hasSuggestions: true, - - schema: [ - { - enum: ["always", "as-needed"] - } - ], - - messages: { - missingParameters: "Missing parameters.", - redundantRadix: "Redundant radix parameter.", - missingRadix: "Missing radix parameter.", - invalidRadix: "Invalid radix parameter, must be an integer between 2 and 36.", - addRadixParameter10: "Add radix parameter `10` for parsing decimal numbers." - } - }, - - create(context) { - const [mode] = context.options; - const sourceCode = context.sourceCode; - - /** - * Checks the arguments of a given CallExpression node and reports it if it - * offends this rule. - * @param {ASTNode} node A CallExpression node to check. - * @returns {void} - */ - function checkArguments(node) { - const args = node.arguments; - - switch (args.length) { - case 0: - context.report({ - node, - messageId: "missingParameters" - }); - break; - - case 1: - if (mode === MODE_ALWAYS) { - context.report({ - node, - messageId: "missingRadix", - suggest: [ - { - messageId: "addRadixParameter10", - fix(fixer) { - const tokens = sourceCode.getTokens(node); - const lastToken = tokens.at(-1); // Parenthesis. - const secondToLastToken = tokens.at(-2); // May or may not be a comma. - const hasTrailingComma = secondToLastToken.type === "Punctuator" && secondToLastToken.value === ","; - - return fixer.insertTextBefore(lastToken, hasTrailingComma ? " 10," : ", 10"); - } - } - ] - }); - } - break; - - default: - if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) { - context.report({ - node, - messageId: "redundantRadix" - }); - } else if (!isValidRadix(args[1])) { - context.report({ - node, - messageId: "invalidRadix" - }); - } - break; - } - } - - return { - "Program:exit"(node) { - const scope = sourceCode.getScope(node); - let variable; - - // Check `parseInt()` - variable = astUtils.getVariableByName(scope, "parseInt"); - if (variable && !isShadowed(variable)) { - variable.references.forEach(reference => { - const idNode = reference.identifier; - - if (astUtils.isCallee(idNode)) { - checkArguments(idNode.parent); - } - }); - } - - // Check `Number.parseInt()` - variable = astUtils.getVariableByName(scope, "Number"); - if (variable && !isShadowed(variable)) { - variable.references.forEach(reference => { - const parentNode = reference.identifier.parent; - const maybeCallee = parentNode.parent.type === "ChainExpression" - ? parentNode.parent - : parentNode; - - if (isParseIntMethod(parentNode) && astUtils.isCallee(maybeCallee)) { - checkArguments(maybeCallee.parent); - } - }); - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [MODE_ALWAYS], + + docs: { + description: + "Enforce the consistent use of the radix argument when using `parseInt()`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/radix", + }, + + hasSuggestions: true, + + schema: [ + { + enum: ["always", "as-needed"], + }, + ], + + messages: { + missingParameters: "Missing parameters.", + redundantRadix: "Redundant radix parameter.", + missingRadix: "Missing radix parameter.", + invalidRadix: + "Invalid radix parameter, must be an integer between 2 and 36.", + addRadixParameter10: + "Add radix parameter `10` for parsing decimal numbers.", + }, + }, + + create(context) { + const [mode] = context.options; + const sourceCode = context.sourceCode; + + /** + * Checks the arguments of a given CallExpression node and reports it if it + * offends this rule. + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + function checkArguments(node) { + const args = node.arguments; + + switch (args.length) { + case 0: + context.report({ + node, + messageId: "missingParameters", + }); + break; + + case 1: + if (mode === MODE_ALWAYS) { + context.report({ + node, + messageId: "missingRadix", + suggest: [ + { + messageId: "addRadixParameter10", + fix(fixer) { + const tokens = + sourceCode.getTokens(node); + const lastToken = tokens.at(-1); // Parenthesis. + const secondToLastToken = tokens.at(-2); // May or may not be a comma. + const hasTrailingComma = + secondToLastToken.type === + "Punctuator" && + secondToLastToken.value === ","; + + return fixer.insertTextBefore( + lastToken, + hasTrailingComma ? " 10," : ", 10", + ); + }, + }, + ], + }); + } + break; + + default: + if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) { + context.report({ + node, + messageId: "redundantRadix", + }); + } else if (!isValidRadix(args[1])) { + context.report({ + node, + messageId: "invalidRadix", + }); + } + break; + } + } + + return { + "Program:exit"(node) { + const scope = sourceCode.getScope(node); + let variable; + + // Check `parseInt()` + variable = astUtils.getVariableByName(scope, "parseInt"); + if (variable && !isShadowed(variable)) { + variable.references.forEach(reference => { + const idNode = reference.identifier; + + if (astUtils.isCallee(idNode)) { + checkArguments(idNode.parent); + } + }); + } + + // Check `Number.parseInt()` + variable = astUtils.getVariableByName(scope, "Number"); + if (variable && !isShadowed(variable)) { + variable.references.forEach(reference => { + const parentNode = reference.identifier.parent; + const maybeCallee = + parentNode.parent.type === "ChainExpression" + ? parentNode.parent + : parentNode; + + if ( + isParseIntMethod(parentNode) && + astUtils.isCallee(maybeCallee) + ) { + checkArguments(maybeCallee.parent); + } + }); + } + }, + }; + }, }; diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js index 5de58f7ea0d8..6c18b3a9d4bb 100644 --- a/lib/rules/require-atomic-updates.js +++ b/lib/rules/require-atomic-updates.js @@ -12,20 +12,20 @@ * @returns {Map} `referenceMap`. */ function createReferenceMap(scope, outReferenceMap = new Map()) { - for (const reference of scope.references) { - if (reference.resolved === null) { - continue; - } - - outReferenceMap.set(reference.identifier, reference); - } - for (const childScope of scope.childScopes) { - if (childScope.type !== "function") { - createReferenceMap(childScope, outReferenceMap); - } - } - - return outReferenceMap; + for (const reference of scope.references) { + if (reference.resolved === null) { + continue; + } + + outReferenceMap.set(reference.identifier, reference); + } + for (const childScope of scope.childScopes) { + if (childScope.type !== "function") { + createReferenceMap(childScope, outReferenceMap); + } + } + + return outReferenceMap; } /** @@ -35,26 +35,26 @@ function createReferenceMap(scope, outReferenceMap = new Map()) { * @returns {Expression|null} The `reference.writeExpr`. */ function getWriteExpr(reference) { - if (reference.writeExpr) { - return reference.writeExpr; - } - let node = reference.identifier; - - while (node) { - const t = node.parent.type; - - if (t === "AssignmentExpression" && node.parent.left === node) { - return node.parent.right; - } - if (t === "MemberExpression" && node.parent.object === node) { - node = node.parent; - continue; - } - - break; - } - - return null; + if (reference.writeExpr) { + return reference.writeExpr; + } + let node = reference.identifier; + + while (node) { + const t = node.parent.type; + + if (t === "AssignmentExpression" && node.parent.left === node) { + return node.parent.right; + } + if (t === "MemberExpression" && node.parent.object === node) { + node = node.parent; + continue; + } + + break; + } + + return null; } /** @@ -64,101 +64,111 @@ function getWriteExpr(reference) { * @returns {boolean} `true` if the variable is local to the given function, and is never referenced in a closure. */ function isLocalVariableWithoutEscape(variable, isMemberAccess) { - if (!variable) { - return false; // A global variable which was not defined. - } + if (!variable) { + return false; // A global variable which was not defined. + } - // If the reference is a property access and the variable is a parameter, it handles the variable is not local. - if (isMemberAccess && variable.defs.some(d => d.type === "Parameter")) { - return false; - } + // If the reference is a property access and the variable is a parameter, it handles the variable is not local. + if (isMemberAccess && variable.defs.some(d => d.type === "Parameter")) { + return false; + } - const functionScope = variable.scope.variableScope; + const functionScope = variable.scope.variableScope; - return variable.references.every(reference => - reference.from.variableScope === functionScope); + return variable.references.every( + reference => reference.from.variableScope === functionScope, + ); } /** * Represents segment information. */ class SegmentInfo { - constructor() { - this.info = new WeakMap(); - } - - /** - * Initialize the segment information. - * @param {PathSegment} segment The segment to initialize. - * @returns {void} - */ - initialize(segment) { - const outdatedReadVariables = new Set(); - const freshReadVariables = new Set(); - - for (const prevSegment of segment.prevSegments) { - const info = this.info.get(prevSegment); - - if (info) { - info.outdatedReadVariables.forEach(Set.prototype.add, outdatedReadVariables); - info.freshReadVariables.forEach(Set.prototype.add, freshReadVariables); - } - } - - this.info.set(segment, { outdatedReadVariables, freshReadVariables }); - } - - /** - * Mark a given variable as read on given segments. - * @param {PathSegment[]} segments The segments that it read the variable on. - * @param {Variable} variable The variable to be read. - * @returns {void} - */ - markAsRead(segments, variable) { - for (const segment of segments) { - const info = this.info.get(segment); - - if (info) { - info.freshReadVariables.add(variable); - - // If a variable is freshly read again, then it's no more out-dated. - info.outdatedReadVariables.delete(variable); - } - } - } - - /** - * Move `freshReadVariables` to `outdatedReadVariables`. - * @param {PathSegment[]} segments The segments to process. - * @returns {void} - */ - makeOutdated(segments) { - for (const segment of segments) { - const info = this.info.get(segment); - - if (info) { - info.freshReadVariables.forEach(Set.prototype.add, info.outdatedReadVariables); - info.freshReadVariables.clear(); - } - } - } - - /** - * Check if a given variable is outdated on the current segments. - * @param {PathSegment[]} segments The current segments. - * @param {Variable} variable The variable to check. - * @returns {boolean} `true` if the variable is outdated on the segments. - */ - isOutdated(segments, variable) { - for (const segment of segments) { - const info = this.info.get(segment); - - if (info && info.outdatedReadVariables.has(variable)) { - return true; - } - } - return false; - } + constructor() { + this.info = new WeakMap(); + } + + /** + * Initialize the segment information. + * @param {PathSegment} segment The segment to initialize. + * @returns {void} + */ + initialize(segment) { + const outdatedReadVariables = new Set(); + const freshReadVariables = new Set(); + + for (const prevSegment of segment.prevSegments) { + const info = this.info.get(prevSegment); + + if (info) { + info.outdatedReadVariables.forEach( + Set.prototype.add, + outdatedReadVariables, + ); + info.freshReadVariables.forEach( + Set.prototype.add, + freshReadVariables, + ); + } + } + + this.info.set(segment, { outdatedReadVariables, freshReadVariables }); + } + + /** + * Mark a given variable as read on given segments. + * @param {PathSegment[]} segments The segments that it read the variable on. + * @param {Variable} variable The variable to be read. + * @returns {void} + */ + markAsRead(segments, variable) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info) { + info.freshReadVariables.add(variable); + + // If a variable is freshly read again, then it's no more out-dated. + info.outdatedReadVariables.delete(variable); + } + } + } + + /** + * Move `freshReadVariables` to `outdatedReadVariables`. + * @param {PathSegment[]} segments The segments to process. + * @returns {void} + */ + makeOutdated(segments) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info) { + info.freshReadVariables.forEach( + Set.prototype.add, + info.outdatedReadVariables, + ); + info.freshReadVariables.clear(); + } + } + } + + /** + * Check if a given variable is outdated on the current segments. + * @param {PathSegment[]} segments The current segments. + * @param {Variable} variable The variable to check. + * @returns {boolean} `true` if the variable is outdated on the segments. + */ + isOutdated(segments, variable) { + for (const segment of segments) { + const info = this.info.get(segment); + + if (info && info.outdatedReadVariables.has(variable)) { + return true; + } + } + return false; + } } //------------------------------------------------------------------------------ @@ -167,168 +177,189 @@ class SegmentInfo { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - allowProperties: false - }], - - docs: { - description: "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", - recommended: false, - url: "https://eslint.org/docs/latest/rules/require-atomic-updates" - }, - - fixable: null, - - schema: [{ - type: "object", - properties: { - allowProperties: { - type: "boolean" - } - }, - additionalProperties: false - }], - - messages: { - nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.", - nonAtomicObjectUpdate: "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`." - } - }, - - create(context) { - const [{ allowProperties }] = context.options; - - const sourceCode = context.sourceCode; - const assignmentReferences = new Map(); - const segmentInfo = new SegmentInfo(); - let stack = null; - - return { - onCodePathStart(codePath, node) { - const scope = sourceCode.getScope(node); - const shouldVerify = - scope.type === "function" && - (scope.block.async || scope.block.generator); - - stack = { - upper: stack, - codePath, - referenceMap: shouldVerify ? createReferenceMap(scope) : null, - currentSegments: new Set() - }; - }, - onCodePathEnd() { - stack = stack.upper; - }, - - // Initialize the segment information. - onCodePathSegmentStart(segment) { - segmentInfo.initialize(segment); - stack.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentStart(segment) { - stack.currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - stack.currentSegments.delete(segment); - }, - - onCodePathSegmentEnd(segment) { - stack.currentSegments.delete(segment); - }, - - - // Handle references to prepare verification. - Identifier(node) { - const { referenceMap } = stack; - const reference = referenceMap && referenceMap.get(node); - - // Ignore if this is not a valid variable reference. - if (!reference) { - return; - } - const variable = reference.resolved; - const writeExpr = getWriteExpr(reference); - const isMemberAccess = reference.identifier.parent.type === "MemberExpression"; - - // Add a fresh read variable. - if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) { - segmentInfo.markAsRead(stack.currentSegments, variable); - } - - /* - * Register the variable to verify after ESLint traversed the `writeExpr` node - * if this reference is an assignment to a variable which is referred from other closure. - */ - if (writeExpr && - writeExpr.parent.right === writeExpr && // ← exclude variable declarations. - !isLocalVariableWithoutEscape(variable, isMemberAccess) - ) { - let refs = assignmentReferences.get(writeExpr); - - if (!refs) { - refs = []; - assignmentReferences.set(writeExpr, refs); - } - - refs.push(reference); - } - }, - - /* - * Verify assignments. - * If the reference exists in `outdatedReadVariables` list, report it. - */ - ":expression:exit"(node) { - - // referenceMap exists if this is in a resumable function scope. - if (!stack.referenceMap) { - return; - } - - // Mark the read variables on this code path as outdated. - if (node.type === "AwaitExpression" || node.type === "YieldExpression") { - segmentInfo.makeOutdated(stack.currentSegments); - } - - // Verify. - const references = assignmentReferences.get(node); - - if (references) { - assignmentReferences.delete(node); - - for (const reference of references) { - const variable = reference.resolved; - - if (segmentInfo.isOutdated(stack.currentSegments, variable)) { - if (node.parent.left === reference.identifier) { - context.report({ - node: node.parent, - messageId: "nonAtomicUpdate", - data: { - value: variable.name - } - }); - } else if (!allowProperties) { - context.report({ - node: node.parent, - messageId: "nonAtomicObjectUpdate", - data: { - value: sourceCode.getText(node.parent.left), - object: variable.name - } - }); - } - - } - } - } - } - }; - } + meta: { + type: "problem", + + defaultOptions: [ + { + allowProperties: false, + }, + ], + + docs: { + description: + "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", + recommended: false, + url: "https://eslint.org/docs/latest/rules/require-atomic-updates", + }, + + fixable: null, + + schema: [ + { + type: "object", + properties: { + allowProperties: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + nonAtomicUpdate: + "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`.", + nonAtomicObjectUpdate: + "Possible race condition: `{{value}}` might be assigned based on an outdated state of `{{object}}`.", + }, + }, + + create(context) { + const [{ allowProperties }] = context.options; + + const sourceCode = context.sourceCode; + const assignmentReferences = new Map(); + const segmentInfo = new SegmentInfo(); + let stack = null; + + return { + onCodePathStart(codePath, node) { + const scope = sourceCode.getScope(node); + const shouldVerify = + scope.type === "function" && + (scope.block.async || scope.block.generator); + + stack = { + upper: stack, + codePath, + referenceMap: shouldVerify + ? createReferenceMap(scope) + : null, + currentSegments: new Set(), + }; + }, + onCodePathEnd() { + stack = stack.upper; + }, + + // Initialize the segment information. + onCodePathSegmentStart(segment) { + segmentInfo.initialize(segment); + stack.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentStart(segment) { + stack.currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + stack.currentSegments.delete(segment); + }, + + onCodePathSegmentEnd(segment) { + stack.currentSegments.delete(segment); + }, + + // Handle references to prepare verification. + Identifier(node) { + const { referenceMap } = stack; + const reference = referenceMap && referenceMap.get(node); + + // Ignore if this is not a valid variable reference. + if (!reference) { + return; + } + const variable = reference.resolved; + const writeExpr = getWriteExpr(reference); + const isMemberAccess = + reference.identifier.parent.type === "MemberExpression"; + + // Add a fresh read variable. + if ( + reference.isRead() && + !(writeExpr && writeExpr.parent.operator === "=") + ) { + segmentInfo.markAsRead(stack.currentSegments, variable); + } + + /* + * Register the variable to verify after ESLint traversed the `writeExpr` node + * if this reference is an assignment to a variable which is referred from other closure. + */ + if ( + writeExpr && + writeExpr.parent.right === writeExpr && // ← exclude variable declarations. + !isLocalVariableWithoutEscape(variable, isMemberAccess) + ) { + let refs = assignmentReferences.get(writeExpr); + + if (!refs) { + refs = []; + assignmentReferences.set(writeExpr, refs); + } + + refs.push(reference); + } + }, + + /* + * Verify assignments. + * If the reference exists in `outdatedReadVariables` list, report it. + */ + ":expression:exit"(node) { + // referenceMap exists if this is in a resumable function scope. + if (!stack.referenceMap) { + return; + } + + // Mark the read variables on this code path as outdated. + if ( + node.type === "AwaitExpression" || + node.type === "YieldExpression" + ) { + segmentInfo.makeOutdated(stack.currentSegments); + } + + // Verify. + const references = assignmentReferences.get(node); + + if (references) { + assignmentReferences.delete(node); + + for (const reference of references) { + const variable = reference.resolved; + + if ( + segmentInfo.isOutdated( + stack.currentSegments, + variable, + ) + ) { + if (node.parent.left === reference.identifier) { + context.report({ + node: node.parent, + messageId: "nonAtomicUpdate", + data: { + value: variable.name, + }, + }); + } else if (!allowProperties) { + context.report({ + node: node.parent, + messageId: "nonAtomicObjectUpdate", + data: { + value: sourceCode.getText( + node.parent.left, + ), + object: variable.name, + }, + }); + } + } + } + } + }, + }; + }, }; diff --git a/lib/rules/require-await.js b/lib/rules/require-await.js index 2a5159d722d4..6fd6af745c4d 100644 --- a/lib/rules/require-await.js +++ b/lib/rules/require-await.js @@ -21,7 +21,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {string} The text that the 1st letter was capitalized. */ function capitalizeFirstLetter(text) { - return text[0].toUpperCase() + text.slice(1); + return text[0].toUpperCase() + text.slice(1); } //------------------------------------------------------------------------------ @@ -30,118 +30,146 @@ function capitalizeFirstLetter(text) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Disallow async functions which have no `await` expression", - recommended: false, - url: "https://eslint.org/docs/latest/rules/require-await" - }, - - schema: [], - - messages: { - missingAwait: "{{name}} has no 'await' expression.", - removeAsync: "Remove 'async'." - }, - - hasSuggestions: true - }, - - create(context) { - const sourceCode = context.sourceCode; - let scopeInfo = null; - - /** - * Push the scope info object to the stack. - * @returns {void} - */ - function enterFunction() { - scopeInfo = { - upper: scopeInfo, - hasAwait: false - }; - } - - /** - * Pop the top scope info object from the stack. - * Also, it reports the function if needed. - * @param {ASTNode} node The node to report. - * @returns {void} - */ - function exitFunction(node) { - if (!node.generator && node.async && !scopeInfo.hasAwait && !astUtils.isEmptyFunction(node)) { - - /* - * If the function belongs to a method definition or - * property, then the function's range may not include the - * `async` keyword and we should look at the parent instead. - */ - const nodeWithAsyncKeyword = - (node.parent.type === "MethodDefinition" && node.parent.value === node) || - (node.parent.type === "Property" && node.parent.method && node.parent.value === node) - ? node.parent - : node; - - const asyncToken = sourceCode.getFirstToken(nodeWithAsyncKeyword, token => token.value === "async"); - const asyncRange = [asyncToken.range[0], sourceCode.getTokenAfter(asyncToken, { includeComments: true }).range[0]]; - - /* - * Removing the `async` keyword can cause parsing errors if the current - * statement is relying on automatic semicolon insertion. If ASI is currently - * being used, then we should replace the `async` keyword with a semicolon. - */ - const nextToken = sourceCode.getTokenAfter(asyncToken); - const addSemiColon = - nextToken.type === "Punctuator" && - (nextToken.value === "[" || nextToken.value === "(") && - (nodeWithAsyncKeyword.type === "MethodDefinition" || astUtils.isStartOfExpressionStatement(nodeWithAsyncKeyword)) && - astUtils.needsPrecedingSemicolon(sourceCode, nodeWithAsyncKeyword); - - context.report({ - node, - loc: astUtils.getFunctionHeadLoc(node, sourceCode), - messageId: "missingAwait", - data: { - name: capitalizeFirstLetter( - astUtils.getFunctionNameWithKind(node) - ) - }, - suggest: [{ - messageId: "removeAsync", - fix: fixer => fixer.replaceTextRange(asyncRange, addSemiColon ? ";" : "") - }] - }); - } - - scopeInfo = scopeInfo.upper; - } - - return { - FunctionDeclaration: enterFunction, - FunctionExpression: enterFunction, - ArrowFunctionExpression: enterFunction, - "FunctionDeclaration:exit": exitFunction, - "FunctionExpression:exit": exitFunction, - "ArrowFunctionExpression:exit": exitFunction, - - AwaitExpression() { - if (!scopeInfo) { - return; - } - - scopeInfo.hasAwait = true; - }, - ForOfStatement(node) { - if (!scopeInfo) { - return; - } - - if (node.await) { - scopeInfo.hasAwait = true; - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Disallow async functions which have no `await` expression", + recommended: false, + url: "https://eslint.org/docs/latest/rules/require-await", + }, + + schema: [], + + messages: { + missingAwait: "{{name}} has no 'await' expression.", + removeAsync: "Remove 'async'.", + }, + + hasSuggestions: true, + }, + + create(context) { + const sourceCode = context.sourceCode; + let scopeInfo = null; + + /** + * Push the scope info object to the stack. + * @returns {void} + */ + function enterFunction() { + scopeInfo = { + upper: scopeInfo, + hasAwait: false, + }; + } + + /** + * Pop the top scope info object from the stack. + * Also, it reports the function if needed. + * @param {ASTNode} node The node to report. + * @returns {void} + */ + function exitFunction(node) { + if ( + !node.generator && + node.async && + !scopeInfo.hasAwait && + !astUtils.isEmptyFunction(node) + ) { + /* + * If the function belongs to a method definition or + * property, then the function's range may not include the + * `async` keyword and we should look at the parent instead. + */ + const nodeWithAsyncKeyword = + (node.parent.type === "MethodDefinition" && + node.parent.value === node) || + (node.parent.type === "Property" && + node.parent.method && + node.parent.value === node) + ? node.parent + : node; + + const asyncToken = sourceCode.getFirstToken( + nodeWithAsyncKeyword, + token => token.value === "async", + ); + const asyncRange = [ + asyncToken.range[0], + sourceCode.getTokenAfter(asyncToken, { + includeComments: true, + }).range[0], + ]; + + /* + * Removing the `async` keyword can cause parsing errors if the current + * statement is relying on automatic semicolon insertion. If ASI is currently + * being used, then we should replace the `async` keyword with a semicolon. + */ + const nextToken = sourceCode.getTokenAfter(asyncToken); + const addSemiColon = + nextToken.type === "Punctuator" && + (nextToken.value === "[" || nextToken.value === "(") && + (nodeWithAsyncKeyword.type === "MethodDefinition" || + astUtils.isStartOfExpressionStatement( + nodeWithAsyncKeyword, + )) && + astUtils.needsPrecedingSemicolon( + sourceCode, + nodeWithAsyncKeyword, + ); + + context.report({ + node, + loc: astUtils.getFunctionHeadLoc(node, sourceCode), + messageId: "missingAwait", + data: { + name: capitalizeFirstLetter( + astUtils.getFunctionNameWithKind(node), + ), + }, + suggest: [ + { + messageId: "removeAsync", + fix: fixer => + fixer.replaceTextRange( + asyncRange, + addSemiColon ? ";" : "", + ), + }, + ], + }); + } + + scopeInfo = scopeInfo.upper; + } + + return { + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction, + "FunctionDeclaration:exit": exitFunction, + "FunctionExpression:exit": exitFunction, + "ArrowFunctionExpression:exit": exitFunction, + + AwaitExpression() { + if (!scopeInfo) { + return; + } + + scopeInfo.hasAwait = true; + }, + ForOfStatement(node) { + if (!scopeInfo) { + return; + } + + if (node.await) { + scopeInfo.hasAwait = true; + } + }, + }; + }, }; diff --git a/lib/rules/require-unicode-regexp.js b/lib/rules/require-unicode-regexp.js index 04edc90ab233..7a9bc805c610 100644 --- a/lib/rules/require-unicode-regexp.js +++ b/lib/rules/require-unicode-regexp.js @@ -10,10 +10,10 @@ //------------------------------------------------------------------------------ const { - CALL, - CONSTRUCT, - ReferenceTracker, - getStringIfConstant + CALL, + CONSTRUCT, + ReferenceTracker, + getStringIfConstant, } = require("@eslint-community/eslint-utils"); const astUtils = require("./utils/ast-utils.js"); const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); @@ -25,17 +25,17 @@ const { isValidWithUnicodeFlag } = require("./utils/regular-expressions"); * @returns {boolean} Whether the flag configuration results in a missing flag. */ function checkFlags(requireFlag, flags) { - let missingFlag; + let missingFlag; - if (requireFlag === "v") { - missingFlag = !flags.includes("v"); - } else if (requireFlag === "u") { - missingFlag = !flags.includes("u"); - } else { - missingFlag = !flags.includes("u") && !flags.includes("v"); - } + if (requireFlag === "v") { + missingFlag = !flags.includes("v"); + } else if (requireFlag === "u") { + missingFlag = !flags.includes("u"); + } else { + missingFlag = !flags.includes("u") && !flags.includes("v"); + } - return missingFlag; + return missingFlag; } //------------------------------------------------------------------------------ @@ -44,167 +44,272 @@ function checkFlags(requireFlag, flags) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Enforce the use of `u` or `v` flag on regular expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/require-unicode-regexp" - }, - - hasSuggestions: true, - - messages: { - addUFlag: "Add the 'u' flag.", - addVFlag: "Add the 'v' flag.", - requireUFlag: "Use the 'u' flag.", - requireVFlag: "Use the 'v' flag." - }, - - schema: [ - { - type: "object", - properties: { - requireFlag: { - enum: ["u", "v"] - } - }, - additionalProperties: false - } - ] - }, - - create(context) { - - const sourceCode = context.sourceCode; - - const { - requireFlag - } = context.options[0] ?? {}; - - return { - "Literal[regex]"(node) { - const flags = node.regex.flags || ""; - - const missingFlag = checkFlags(requireFlag, flags); - - if (missingFlag) { - context.report({ - messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag", - node, - suggest: isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, node.regex.pattern, requireFlag) - ? [ - { - fix(fixer) { - const replaceFlag = requireFlag ?? "u"; - const regex = sourceCode.getText(node); - const slashPos = regex.lastIndexOf("/"); - - if (requireFlag) { - const flag = requireFlag === "u" ? "v" : "u"; - - if (regex.includes(flag, slashPos)) { - return fixer.replaceText( - node, - regex.slice(0, slashPos) + - regex.slice(slashPos).replace(flag, requireFlag) - ); - } - } - - return fixer.insertTextAfter(node, replaceFlag); - }, - messageId: requireFlag === "v" ? "addVFlag" : "addUFlag" - } - ] - : null - }); - } - }, - - Program(node) { - const scope = sourceCode.getScope(node); - const tracker = new ReferenceTracker(scope); - const trackMap = { - RegExp: { [CALL]: true, [CONSTRUCT]: true } - }; - - for (const { node: refNode } of tracker.iterateGlobalReferences(trackMap)) { - const [patternNode, flagsNode] = refNode.arguments; - - if (patternNode && patternNode.type === "SpreadElement") { - continue; - } - const pattern = getStringIfConstant(patternNode, scope); - const flags = getStringIfConstant(flagsNode, scope); - - let missingFlag = !flagsNode; - - if (typeof flags === "string") { - missingFlag = checkFlags(requireFlag, flags); - } - - if (missingFlag) { - context.report({ - messageId: requireFlag === "v" ? "requireVFlag" : "requireUFlag", - node: refNode, - suggest: typeof pattern === "string" && isValidWithUnicodeFlag(context.languageOptions.ecmaVersion, pattern, requireFlag) - ? [ - { - fix(fixer) { - const replaceFlag = requireFlag ?? "u"; - - if (flagsNode) { - if ((flagsNode.type === "Literal" && typeof flagsNode.value === "string") || flagsNode.type === "TemplateLiteral") { - const flagsNodeText = sourceCode.getText(flagsNode); - const flag = requireFlag === "u" ? "v" : "u"; - - if (flags.includes(flag)) { - - // Avoid replacing "u" in escapes like `\uXXXX` - if (flagsNode.type === "Literal" && flagsNode.raw.includes("\\")) { - return null; - } - - // Avoid replacing "u" in expressions like "`${regularFlags}g`" - if (flagsNode.type === "TemplateLiteral" && ( - flagsNode.expressions.length || - flagsNode.quasis.some(({ value: { raw } }) => raw.includes("\\")) - )) { - return null; - } - - return fixer.replaceText(flagsNode, flagsNodeText.replace(flag, replaceFlag)); - } - - return fixer.replaceText(flagsNode, [ - flagsNodeText.slice(0, flagsNodeText.length - 1), - flagsNodeText.slice(flagsNodeText.length - 1) - ].join(replaceFlag)); - } - - // We intentionally don't suggest concatenating + "u" to non-literals - return null; - } - - const penultimateToken = sourceCode.getLastToken(refNode, { skip: 1 }); // skip closing parenthesis - - return fixer.insertTextAfter( - penultimateToken, - astUtils.isCommaToken(penultimateToken) - ? ` "${replaceFlag}",` - : `, "${replaceFlag}"` - ); - }, - messageId: requireFlag === "v" ? "addVFlag" : "addUFlag" - } - ] - : null - }); - } - } - } - }; - } + meta: { + type: "suggestion", + + docs: { + description: + "Enforce the use of `u` or `v` flag on regular expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/require-unicode-regexp", + }, + + hasSuggestions: true, + + messages: { + addUFlag: "Add the 'u' flag.", + addVFlag: "Add the 'v' flag.", + requireUFlag: "Use the 'u' flag.", + requireVFlag: "Use the 'v' flag.", + }, + + schema: [ + { + type: "object", + properties: { + requireFlag: { + enum: ["u", "v"], + }, + }, + additionalProperties: false, + }, + ], + }, + + create(context) { + const sourceCode = context.sourceCode; + + const { requireFlag } = context.options[0] ?? {}; + + return { + "Literal[regex]"(node) { + const flags = node.regex.flags || ""; + + const missingFlag = checkFlags(requireFlag, flags); + + if (missingFlag) { + context.report({ + messageId: + requireFlag === "v" + ? "requireVFlag" + : "requireUFlag", + node, + suggest: isValidWithUnicodeFlag( + context.languageOptions.ecmaVersion, + node.regex.pattern, + requireFlag, + ) + ? [ + { + fix(fixer) { + const replaceFlag = + requireFlag ?? "u"; + const regex = + sourceCode.getText(node); + const slashPos = + regex.lastIndexOf("/"); + + if (requireFlag) { + const flag = + requireFlag === "u" + ? "v" + : "u"; + + if ( + regex.includes( + flag, + slashPos, + ) + ) { + return fixer.replaceText( + node, + regex.slice( + 0, + slashPos, + ) + + regex + .slice(slashPos) + .replace( + flag, + requireFlag, + ), + ); + } + } + + return fixer.insertTextAfter( + node, + replaceFlag, + ); + }, + messageId: + requireFlag === "v" + ? "addVFlag" + : "addUFlag", + }, + ] + : null, + }); + } + }, + + Program(node) { + const scope = sourceCode.getScope(node); + const tracker = new ReferenceTracker(scope); + const trackMap = { + RegExp: { [CALL]: true, [CONSTRUCT]: true }, + }; + + for (const { node: refNode } of tracker.iterateGlobalReferences( + trackMap, + )) { + const [patternNode, flagsNode] = refNode.arguments; + + if (patternNode && patternNode.type === "SpreadElement") { + continue; + } + const pattern = getStringIfConstant(patternNode, scope); + const flags = getStringIfConstant(flagsNode, scope); + + let missingFlag = !flagsNode; + + if (typeof flags === "string") { + missingFlag = checkFlags(requireFlag, flags); + } + + if (missingFlag) { + context.report({ + messageId: + requireFlag === "v" + ? "requireVFlag" + : "requireUFlag", + node: refNode, + suggest: + typeof pattern === "string" && + isValidWithUnicodeFlag( + context.languageOptions.ecmaVersion, + pattern, + requireFlag, + ) + ? [ + { + fix(fixer) { + const replaceFlag = + requireFlag ?? "u"; + + if (flagsNode) { + if ( + (flagsNode.type === + "Literal" && + typeof flagsNode.value === + "string") || + flagsNode.type === + "TemplateLiteral" + ) { + const flagsNodeText = + sourceCode.getText( + flagsNode, + ); + const flag = + requireFlag === + "u" + ? "v" + : "u"; + + if ( + flags.includes( + flag, + ) + ) { + // Avoid replacing "u" in escapes like `\uXXXX` + if ( + flagsNode.type === + "Literal" && + flagsNode.raw.includes( + "\\", + ) + ) { + return null; + } + + // Avoid replacing "u" in expressions like "`${regularFlags}g`" + if ( + flagsNode.type === + "TemplateLiteral" && + (flagsNode + .expressions + .length || + flagsNode.quasis.some( + ({ + value: { + raw, + }, + }) => + raw.includes( + "\\", + ), + )) + ) { + return null; + } + + return fixer.replaceText( + flagsNode, + flagsNodeText.replace( + flag, + replaceFlag, + ), + ); + } + + return fixer.replaceText( + flagsNode, + [ + flagsNodeText.slice( + 0, + flagsNodeText.length - + 1, + ), + flagsNodeText.slice( + flagsNodeText.length - + 1, + ), + ].join( + replaceFlag, + ), + ); + } + + // We intentionally don't suggest concatenating + "u" to non-literals + return null; + } + + const penultimateToken = + sourceCode.getLastToken( + refNode, + { skip: 1 }, + ); // skip closing parenthesis + + return fixer.insertTextAfter( + penultimateToken, + astUtils.isCommaToken( + penultimateToken, + ) + ? ` "${replaceFlag}",` + : `, "${replaceFlag}"`, + ); + }, + messageId: + requireFlag === "v" + ? "addVFlag" + : "addUFlag", + }, + ] + : null, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/require-yield.js b/lib/rules/require-yield.js index f801af0ab504..419b6660cb84 100644 --- a/lib/rules/require-yield.js +++ b/lib/rules/require-yield.js @@ -11,67 +11,66 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", + meta: { + type: "suggestion", - docs: { - description: "Require generator functions to contain `yield`", - recommended: true, - url: "https://eslint.org/docs/latest/rules/require-yield" - }, + docs: { + description: "Require generator functions to contain `yield`", + recommended: true, + url: "https://eslint.org/docs/latest/rules/require-yield", + }, - schema: [], + schema: [], - messages: { - missingYield: "This generator function does not have 'yield'." - } - }, + messages: { + missingYield: "This generator function does not have 'yield'.", + }, + }, - create(context) { - const stack = []; + create(context) { + const stack = []; - /** - * If the node is a generator function, start counting `yield` keywords. - * @param {Node} node A function node to check. - * @returns {void} - */ - function beginChecking(node) { - if (node.generator) { - stack.push(0); - } - } + /** + * If the node is a generator function, start counting `yield` keywords. + * @param {Node} node A function node to check. + * @returns {void} + */ + function beginChecking(node) { + if (node.generator) { + stack.push(0); + } + } - /** - * If the node is a generator function, end counting `yield` keywords, then - * reports result. - * @param {Node} node A function node to check. - * @returns {void} - */ - function endChecking(node) { - if (!node.generator) { - return; - } + /** + * If the node is a generator function, end counting `yield` keywords, then + * reports result. + * @param {Node} node A function node to check. + * @returns {void} + */ + function endChecking(node) { + if (!node.generator) { + return; + } - const countYield = stack.pop(); + const countYield = stack.pop(); - if (countYield === 0 && node.body.body.length > 0) { - context.report({ node, messageId: "missingYield" }); - } - } + if (countYield === 0 && node.body.body.length > 0) { + context.report({ node, messageId: "missingYield" }); + } + } - return { - FunctionDeclaration: beginChecking, - "FunctionDeclaration:exit": endChecking, - FunctionExpression: beginChecking, - "FunctionExpression:exit": endChecking, + return { + FunctionDeclaration: beginChecking, + "FunctionDeclaration:exit": endChecking, + FunctionExpression: beginChecking, + "FunctionExpression:exit": endChecking, - // Increases the count of `yield` keyword. - YieldExpression() { - - if (stack.length > 0) { - stack[stack.length - 1] += 1; - } - } - }; - } + // Increases the count of `yield` keyword. + YieldExpression() { + if (stack.length > 0) { + stack[stack.length - 1] += 1; + } + }, + }; + }, }; diff --git a/lib/rules/rest-spread-spacing.js b/lib/rules/rest-spread-spacing.js index e6ba3ad60ae9..9cec75673fff 100644 --- a/lib/rules/rest-spread-spacing.js +++ b/lib/rules/rest-spread-spacing.js @@ -12,130 +12,142 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "rest-spread-spacing", - url: "https://eslint.style/rules/js/rest-spread-spacing" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "rest-spread-spacing", + url: "https://eslint.style/rules/js/rest-spread-spacing", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce spacing between rest and spread operators and their expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/rest-spread-spacing" - }, + docs: { + description: + "Enforce spacing between rest and spread operators and their expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/rest-spread-spacing", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - enum: ["always", "never"] - } - ], + schema: [ + { + enum: ["always", "never"], + }, + ], - messages: { - unexpectedWhitespace: "Unexpected whitespace after {{type}} operator.", - expectedWhitespace: "Expected whitespace after {{type}} operator." - } - }, + messages: { + unexpectedWhitespace: + "Unexpected whitespace after {{type}} operator.", + expectedWhitespace: "Expected whitespace after {{type}} operator.", + }, + }, - create(context) { - const sourceCode = context.sourceCode, - alwaysSpace = context.options[0] === "always"; + create(context) { + const sourceCode = context.sourceCode, + alwaysSpace = context.options[0] === "always"; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Checks whitespace between rest/spread operators and their expressions - * @param {ASTNode} node The node to check - * @returns {void} - */ - function checkWhiteSpace(node) { - const operator = sourceCode.getFirstToken(node), - nextToken = sourceCode.getTokenAfter(operator), - hasWhitespace = sourceCode.isSpaceBetweenTokens(operator, nextToken); - let type; + /** + * Checks whitespace between rest/spread operators and their expressions + * @param {ASTNode} node The node to check + * @returns {void} + */ + function checkWhiteSpace(node) { + const operator = sourceCode.getFirstToken(node), + nextToken = sourceCode.getTokenAfter(operator), + hasWhitespace = sourceCode.isSpaceBetweenTokens( + operator, + nextToken, + ); + let type; - switch (node.type) { - case "SpreadElement": - type = "spread"; - if (node.parent.type === "ObjectExpression") { - type += " property"; - } - break; - case "RestElement": - type = "rest"; - if (node.parent.type === "ObjectPattern") { - type += " property"; - } - break; - case "ExperimentalSpreadProperty": - type = "spread property"; - break; - case "ExperimentalRestProperty": - type = "rest property"; - break; - default: - return; - } + switch (node.type) { + case "SpreadElement": + type = "spread"; + if (node.parent.type === "ObjectExpression") { + type += " property"; + } + break; + case "RestElement": + type = "rest"; + if (node.parent.type === "ObjectPattern") { + type += " property"; + } + break; + case "ExperimentalSpreadProperty": + type = "spread property"; + break; + case "ExperimentalRestProperty": + type = "rest property"; + break; + default: + return; + } - if (alwaysSpace && !hasWhitespace) { - context.report({ - node, - loc: operator.loc, - messageId: "expectedWhitespace", - data: { - type - }, - fix(fixer) { - return fixer.replaceTextRange([operator.range[1], nextToken.range[0]], " "); - } - }); - } else if (!alwaysSpace && hasWhitespace) { - context.report({ - node, - loc: { - start: operator.loc.end, - end: nextToken.loc.start - }, - messageId: "unexpectedWhitespace", - data: { - type - }, - fix(fixer) { - return fixer.removeRange([operator.range[1], nextToken.range[0]]); - } - }); - } - } + if (alwaysSpace && !hasWhitespace) { + context.report({ + node, + loc: operator.loc, + messageId: "expectedWhitespace", + data: { + type, + }, + fix(fixer) { + return fixer.replaceTextRange( + [operator.range[1], nextToken.range[0]], + " ", + ); + }, + }); + } else if (!alwaysSpace && hasWhitespace) { + context.report({ + node, + loc: { + start: operator.loc.end, + end: nextToken.loc.start, + }, + messageId: "unexpectedWhitespace", + data: { + type, + }, + fix(fixer) { + return fixer.removeRange([ + operator.range[1], + nextToken.range[0], + ]); + }, + }); + } + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - SpreadElement: checkWhiteSpace, - RestElement: checkWhiteSpace, - ExperimentalSpreadProperty: checkWhiteSpace, - ExperimentalRestProperty: checkWhiteSpace - }; - } + return { + SpreadElement: checkWhiteSpace, + RestElement: checkWhiteSpace, + ExperimentalSpreadProperty: checkWhiteSpace, + ExperimentalRestProperty: checkWhiteSpace, + }; + }, }; diff --git a/lib/rules/semi-spacing.js b/lib/rules/semi-spacing.js index d47bc1a96e14..7c65f44578f6 100644 --- a/lib/rules/semi-spacing.js +++ b/lib/rules/semi-spacing.js @@ -14,253 +14,284 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "semi-spacing", - url: "https://eslint.style/rules/js/semi-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before and after semicolons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/semi-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - before: { - type: "boolean", - default: false - }, - after: { - type: "boolean", - default: true - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedWhitespaceBefore: "Unexpected whitespace before semicolon.", - unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.", - missingWhitespaceBefore: "Missing whitespace before semicolon.", - missingWhitespaceAfter: "Missing whitespace after semicolon." - } - }, - - create(context) { - - const config = context.options[0], - sourceCode = context.sourceCode; - let requireSpaceBefore = false, - requireSpaceAfter = true; - - if (typeof config === "object") { - requireSpaceBefore = config.before; - requireSpaceAfter = config.after; - } - - /** - * Checks if a given token has leading whitespace. - * @param {Object} token The token to check. - * @returns {boolean} True if the given token has leading space, false if not. - */ - function hasLeadingSpace(token) { - const tokenBefore = sourceCode.getTokenBefore(token); - - return tokenBefore && astUtils.isTokenOnSameLine(tokenBefore, token) && sourceCode.isSpaceBetweenTokens(tokenBefore, token); - } - - /** - * Checks if a given token has trailing whitespace. - * @param {Object} token The token to check. - * @returns {boolean} True if the given token has trailing space, false if not. - */ - function hasTrailingSpace(token) { - const tokenAfter = sourceCode.getTokenAfter(token); - - return tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) && sourceCode.isSpaceBetweenTokens(token, tokenAfter); - } - - /** - * Checks if the given token is the last token in its line. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is the last in its line. - */ - function isLastTokenInCurrentLine(token) { - const tokenAfter = sourceCode.getTokenAfter(token); - - return !(tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter)); - } - - /** - * Checks if the given token is the first token in its line - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the token is the first in its line. - */ - function isFirstTokenInCurrentLine(token) { - const tokenBefore = sourceCode.getTokenBefore(token); - - return !(tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore)); - } - - /** - * Checks if the next token of a given token is a closing parenthesis. - * @param {Token} token The token to check. - * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. - */ - function isBeforeClosingParen(token) { - const nextToken = sourceCode.getTokenAfter(token); - - return (nextToken && astUtils.isClosingBraceToken(nextToken) || astUtils.isClosingParenToken(nextToken)); - } - - /** - * Report location example : - * - * for unexpected space `before` - * - * var a = 'b' ; - * ^^^ - * - * for unexpected space `after` - * - * var a = 'b'; c = 10; - * ^^ - * - * Reports if the given token has invalid spacing. - * @param {Token} token The semicolon token to check. - * @param {ASTNode} node The corresponding node of the token. - * @returns {void} - */ - function checkSemicolonSpacing(token, node) { - if (astUtils.isSemicolonToken(token)) { - if (hasLeadingSpace(token)) { - if (!requireSpaceBefore) { - const tokenBefore = sourceCode.getTokenBefore(token); - const loc = { - start: tokenBefore.loc.end, - end: token.loc.start - }; - - context.report({ - node, - loc, - messageId: "unexpectedWhitespaceBefore", - fix(fixer) { - - return fixer.removeRange([tokenBefore.range[1], token.range[0]]); - } - }); - } - } else { - if (requireSpaceBefore) { - const loc = token.loc; - - context.report({ - node, - loc, - messageId: "missingWhitespaceBefore", - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - } - - if (!isFirstTokenInCurrentLine(token) && !isLastTokenInCurrentLine(token) && !isBeforeClosingParen(token)) { - if (hasTrailingSpace(token)) { - if (!requireSpaceAfter) { - const tokenAfter = sourceCode.getTokenAfter(token); - const loc = { - start: token.loc.end, - end: tokenAfter.loc.start - }; - - context.report({ - node, - loc, - messageId: "unexpectedWhitespaceAfter", - fix(fixer) { - - return fixer.removeRange([token.range[1], tokenAfter.range[0]]); - } - }); - } - } else { - if (requireSpaceAfter) { - const loc = token.loc; - - context.report({ - node, - loc, - messageId: "missingWhitespaceAfter", - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } - } - } - } - } - - /** - * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkNode(node) { - const token = sourceCode.getLastToken(node); - - checkSemicolonSpacing(token, node); - } - - return { - VariableDeclaration: checkNode, - ExpressionStatement: checkNode, - BreakStatement: checkNode, - ContinueStatement: checkNode, - DebuggerStatement: checkNode, - DoWhileStatement: checkNode, - ReturnStatement: checkNode, - ThrowStatement: checkNode, - ImportDeclaration: checkNode, - ExportNamedDeclaration: checkNode, - ExportAllDeclaration: checkNode, - ExportDefaultDeclaration: checkNode, - ForStatement(node) { - if (node.init) { - checkSemicolonSpacing(sourceCode.getTokenAfter(node.init), node); - } - - if (node.test) { - checkSemicolonSpacing(sourceCode.getTokenAfter(node.test), node); - } - }, - PropertyDefinition: checkNode - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "semi-spacing", + url: "https://eslint.style/rules/js/semi-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing before and after semicolons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/semi-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + before: { + type: "boolean", + default: false, + }, + after: { + type: "boolean", + default: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedWhitespaceBefore: + "Unexpected whitespace before semicolon.", + unexpectedWhitespaceAfter: "Unexpected whitespace after semicolon.", + missingWhitespaceBefore: "Missing whitespace before semicolon.", + missingWhitespaceAfter: "Missing whitespace after semicolon.", + }, + }, + + create(context) { + const config = context.options[0], + sourceCode = context.sourceCode; + let requireSpaceBefore = false, + requireSpaceAfter = true; + + if (typeof config === "object") { + requireSpaceBefore = config.before; + requireSpaceAfter = config.after; + } + + /** + * Checks if a given token has leading whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has leading space, false if not. + */ + function hasLeadingSpace(token) { + const tokenBefore = sourceCode.getTokenBefore(token); + + return ( + tokenBefore && + astUtils.isTokenOnSameLine(tokenBefore, token) && + sourceCode.isSpaceBetweenTokens(tokenBefore, token) + ); + } + + /** + * Checks if a given token has trailing whitespace. + * @param {Object} token The token to check. + * @returns {boolean} True if the given token has trailing space, false if not. + */ + function hasTrailingSpace(token) { + const tokenAfter = sourceCode.getTokenAfter(token); + + return ( + tokenAfter && + astUtils.isTokenOnSameLine(token, tokenAfter) && + sourceCode.isSpaceBetweenTokens(token, tokenAfter) + ); + } + + /** + * Checks if the given token is the last token in its line. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the last in its line. + */ + function isLastTokenInCurrentLine(token) { + const tokenAfter = sourceCode.getTokenAfter(token); + + return !( + tokenAfter && astUtils.isTokenOnSameLine(token, tokenAfter) + ); + } + + /** + * Checks if the given token is the first token in its line + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the token is the first in its line. + */ + function isFirstTokenInCurrentLine(token) { + const tokenBefore = sourceCode.getTokenBefore(token); + + return !( + tokenBefore && astUtils.isTokenOnSameLine(token, tokenBefore) + ); + } + + /** + * Checks if the next token of a given token is a closing parenthesis. + * @param {Token} token The token to check. + * @returns {boolean} Whether or not the next token of a given token is a closing parenthesis. + */ + function isBeforeClosingParen(token) { + const nextToken = sourceCode.getTokenAfter(token); + + return ( + (nextToken && astUtils.isClosingBraceToken(nextToken)) || + astUtils.isClosingParenToken(nextToken) + ); + } + + /** + * Report location example : + * + * for unexpected space `before` + * + * var a = 'b' ; + * ^^^ + * + * for unexpected space `after` + * + * var a = 'b'; c = 10; + * ^^ + * + * Reports if the given token has invalid spacing. + * @param {Token} token The semicolon token to check. + * @param {ASTNode} node The corresponding node of the token. + * @returns {void} + */ + function checkSemicolonSpacing(token, node) { + if (astUtils.isSemicolonToken(token)) { + if (hasLeadingSpace(token)) { + if (!requireSpaceBefore) { + const tokenBefore = sourceCode.getTokenBefore(token); + const loc = { + start: tokenBefore.loc.end, + end: token.loc.start, + }; + + context.report({ + node, + loc, + messageId: "unexpectedWhitespaceBefore", + fix(fixer) { + return fixer.removeRange([ + tokenBefore.range[1], + token.range[0], + ]); + }, + }); + } + } else { + if (requireSpaceBefore) { + const loc = token.loc; + + context.report({ + node, + loc, + messageId: "missingWhitespaceBefore", + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } + } + + if ( + !isFirstTokenInCurrentLine(token) && + !isLastTokenInCurrentLine(token) && + !isBeforeClosingParen(token) + ) { + if (hasTrailingSpace(token)) { + if (!requireSpaceAfter) { + const tokenAfter = sourceCode.getTokenAfter(token); + const loc = { + start: token.loc.end, + end: tokenAfter.loc.start, + }; + + context.report({ + node, + loc, + messageId: "unexpectedWhitespaceAfter", + fix(fixer) { + return fixer.removeRange([ + token.range[1], + tokenAfter.range[0], + ]); + }, + }); + } + } else { + if (requireSpaceAfter) { + const loc = token.loc; + + context.report({ + node, + loc, + messageId: "missingWhitespaceAfter", + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } + } + } + } + } + + /** + * Checks the spacing of the semicolon with the assumption that the last token is the semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkNode(node) { + const token = sourceCode.getLastToken(node); + + checkSemicolonSpacing(token, node); + } + + return { + VariableDeclaration: checkNode, + ExpressionStatement: checkNode, + BreakStatement: checkNode, + ContinueStatement: checkNode, + DebuggerStatement: checkNode, + DoWhileStatement: checkNode, + ReturnStatement: checkNode, + ThrowStatement: checkNode, + ImportDeclaration: checkNode, + ExportNamedDeclaration: checkNode, + ExportAllDeclaration: checkNode, + ExportDefaultDeclaration: checkNode, + ForStatement(node) { + if (node.init) { + checkSemicolonSpacing( + sourceCode.getTokenAfter(node.init), + node, + ); + } + + if (node.test) { + checkSemicolonSpacing( + sourceCode.getTokenAfter(node.test), + node, + ); + } + }, + PropertyDefinition: checkNode, + }; + }, }; diff --git a/lib/rules/semi-style.js b/lib/rules/semi-style.js index 682619812801..67dfe5c60c03 100644 --- a/lib/rules/semi-style.js +++ b/lib/rules/semi-style.js @@ -17,11 +17,19 @@ const astUtils = require("./utils/ast-utils"); //------------------------------------------------------------------------------ const SELECTOR = [ - "BreakStatement", "ContinueStatement", "DebuggerStatement", - "DoWhileStatement", "ExportAllDeclaration", - "ExportDefaultDeclaration", "ExportNamedDeclaration", - "ExpressionStatement", "ImportDeclaration", "ReturnStatement", - "ThrowStatement", "VariableDeclaration", "PropertyDefinition" + "BreakStatement", + "ContinueStatement", + "DebuggerStatement", + "DoWhileStatement", + "ExportAllDeclaration", + "ExportDefaultDeclaration", + "ExportNamedDeclaration", + "ExpressionStatement", + "ImportDeclaration", + "ReturnStatement", + "ThrowStatement", + "VariableDeclaration", + "PropertyDefinition", ].join(","); /** @@ -33,20 +41,20 @@ const SELECTOR = [ * @returns {Node[]|null} The child node list. */ function getChildren(node) { - const t = node.type; - - if ( - t === "BlockStatement" || - t === "StaticBlock" || - t === "Program" || - t === "ClassBody" - ) { - return node.body; - } - if (t === "SwitchCase") { - return node.consequent; - } - return null; + const t = node.type; + + if ( + t === "BlockStatement" || + t === "StaticBlock" || + t === "Program" || + t === "ClassBody" + ) { + return node.body; + } + if (t === "SwitchCase") { + return node.consequent; + } + return null; } /** @@ -55,122 +63,156 @@ function getChildren(node) { * @returns {boolean} `true` if the node is the last statement in the parent block. */ function isLastChild(node) { - const t = node.parent.type; - - if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword. - return true; - } - if (t === "DoWhileStatement") { // before `while` keyword. - return true; - } - const nodeList = getChildren(node.parent); - - return nodeList !== null && nodeList.at(-1) === node; // before `}` or etc. + const t = node.parent.type; + + if ( + t === "IfStatement" && + node.parent.consequent === node && + node.parent.alternate + ) { + // before `else` keyword. + return true; + } + if (t === "DoWhileStatement") { + // before `while` keyword. + return true; + } + const nodeList = getChildren(node.parent); + + return nodeList !== null && nodeList.at(-1) === node; // before `}` or etc. } /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "semi-style", - url: "https://eslint.style/rules/js/semi-style" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce location of semicolons", - recommended: false, - url: "https://eslint.org/docs/latest/rules/semi-style" - }, - - schema: [{ enum: ["last", "first"] }], - fixable: "whitespace", - - messages: { - expectedSemiColon: "Expected this semicolon to be at {{pos}}." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const option = context.options[0] || "last"; - - /** - * Check the given semicolon token. - * @param {Token} semiToken The semicolon token to check. - * @param {"first"|"last"} expected The expected location to check. - * @returns {void} - */ - function check(semiToken, expected) { - const prevToken = sourceCode.getTokenBefore(semiToken); - const nextToken = sourceCode.getTokenAfter(semiToken); - const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken); - const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken); - - if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) { - context.report({ - loc: semiToken.loc, - messageId: "expectedSemiColon", - data: { - pos: (expected === "last") - ? "the end of the previous line" - : "the beginning of the next line" - }, - fix(fixer) { - if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) { - return null; - } - - const start = prevToken ? prevToken.range[1] : semiToken.range[0]; - const end = nextToken ? nextToken.range[0] : semiToken.range[1]; - const text = (expected === "last") ? ";\n" : "\n;"; - - return fixer.replaceTextRange([start, end], text); - } - }); - } - } - - return { - [SELECTOR](node) { - if (option === "first" && isLastChild(node)) { - return; - } - - const lastToken = sourceCode.getLastToken(node); - - if (astUtils.isSemicolonToken(lastToken)) { - check(lastToken, option); - } - }, - - ForStatement(node) { - const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken); - const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken); - - if (firstSemi) { - check(firstSemi, "last"); - } - if (secondSemi) { - check(secondSemi, "last"); - } - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "semi-style", + url: "https://eslint.style/rules/js/semi-style", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce location of semicolons", + recommended: false, + url: "https://eslint.org/docs/latest/rules/semi-style", + }, + + schema: [{ enum: ["last", "first"] }], + fixable: "whitespace", + + messages: { + expectedSemiColon: "Expected this semicolon to be at {{pos}}.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const option = context.options[0] || "last"; + + /** + * Check the given semicolon token. + * @param {Token} semiToken The semicolon token to check. + * @param {"first"|"last"} expected The expected location to check. + * @returns {void} + */ + function check(semiToken, expected) { + const prevToken = sourceCode.getTokenBefore(semiToken); + const nextToken = sourceCode.getTokenAfter(semiToken); + const prevIsSameLine = + !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken); + const nextIsSameLine = + !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken); + + if ( + (expected === "last" && !prevIsSameLine) || + (expected === "first" && !nextIsSameLine) + ) { + context.report({ + loc: semiToken.loc, + messageId: "expectedSemiColon", + data: { + pos: + expected === "last" + ? "the end of the previous line" + : "the beginning of the next line", + }, + fix(fixer) { + if ( + prevToken && + nextToken && + sourceCode.commentsExistBetween( + prevToken, + nextToken, + ) + ) { + return null; + } + + const start = prevToken + ? prevToken.range[1] + : semiToken.range[0]; + const end = nextToken + ? nextToken.range[0] + : semiToken.range[1]; + const text = expected === "last" ? ";\n" : "\n;"; + + return fixer.replaceTextRange([start, end], text); + }, + }); + } + } + + return { + [SELECTOR](node) { + if (option === "first" && isLastChild(node)) { + return; + } + + const lastToken = sourceCode.getLastToken(node); + + if (astUtils.isSemicolonToken(lastToken)) { + check(lastToken, option); + } + }, + + ForStatement(node) { + const firstSemi = + node.init && + sourceCode.getTokenAfter( + node.init, + astUtils.isSemicolonToken, + ); + const secondSemi = + node.test && + sourceCode.getTokenAfter( + node.test, + astUtils.isSemicolonToken, + ); + + if (firstSemi) { + check(firstSemi, "last"); + } + if (secondSemi) { + check(secondSemi, "last"); + } + }, + }; + }, }; diff --git a/lib/rules/semi.js b/lib/rules/semi.js index db04b99a8c13..7965e039bbd3 100644 --- a/lib/rules/semi.js +++ b/lib/rules/semi.js @@ -18,439 +18,459 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "semi", - url: "https://eslint.style/rules/js/semi" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow semicolons instead of ASI", - recommended: false, - url: "https://eslint.org/docs/latest/rules/semi" - }, - - fixable: "code", - - schema: { - anyOf: [ - { - type: "array", - items: [ - { - enum: ["never"] - }, - { - type: "object", - properties: { - beforeStatementContinuationChars: { - enum: ["always", "any", "never"] - } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - }, - { - type: "array", - items: [ - { - enum: ["always"] - }, - { - type: "object", - properties: { - omitLastInOneLineBlock: { type: "boolean" }, - omitLastInOneLineClassBody: { type: "boolean" } - }, - additionalProperties: false - } - ], - minItems: 0, - maxItems: 2 - } - ] - }, - - messages: { - missingSemi: "Missing semicolon.", - extraSemi: "Extra semicolon." - } - }, - - create(context) { - - const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-` - const unsafeClassFieldNames = new Set(["get", "set", "static"]); - const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]); - const options = context.options[1]; - const never = context.options[0] === "never"; - const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock); - const exceptOneLineClassBody = Boolean(options && options.omitLastInOneLineClassBody); - const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any"; - const sourceCode = context.sourceCode; - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Reports a semicolon error with appropriate location and message. - * @param {ASTNode} node The node with an extra or missing semicolon. - * @param {boolean} missing True if the semicolon is missing. - * @returns {void} - */ - function report(node, missing) { - const lastToken = sourceCode.getLastToken(node); - let messageId, - fix, - loc; - - if (!missing) { - messageId = "missingSemi"; - loc = { - start: lastToken.loc.end, - end: astUtils.getNextLocation(sourceCode, lastToken.loc.end) - }; - fix = function(fixer) { - return fixer.insertTextAfter(lastToken, ";"); - }; - } else { - messageId = "extraSemi"; - loc = lastToken.loc; - fix = function(fixer) { - - /* - * Expand the replacement range to include the surrounding - * tokens to avoid conflicting with no-extra-semi. - * https://github.com/eslint/eslint/issues/7928 - */ - return new FixTracker(fixer, sourceCode) - .retainSurroundingTokens(lastToken) - .remove(lastToken); - }; - } - - context.report({ - node, - loc, - messageId, - fix - }); - - } - - /** - * Check whether a given semicolon token is redundant. - * @param {Token} semiToken A semicolon token to check. - * @returns {boolean} `true` if the next token is `;` or `}`. - */ - function isRedundantSemi(semiToken) { - const nextToken = sourceCode.getTokenAfter(semiToken); - - return ( - !nextToken || - astUtils.isClosingBraceToken(nextToken) || - astUtils.isSemicolonToken(nextToken) - ); - } - - /** - * Check whether a given token is the closing brace of an arrow function. - * @param {Token} lastToken A token to check. - * @returns {boolean} `true` if the token is the closing brace of an arrow function. - */ - function isEndOfArrowBlock(lastToken) { - if (!astUtils.isClosingBraceToken(lastToken)) { - return false; - } - const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]); - - return ( - node.type === "BlockStatement" && - node.parent.type === "ArrowFunctionExpression" - ); - } - - /** - * Checks if a given PropertyDefinition node followed by a semicolon - * can safely remove that semicolon. It is not to safe to remove if - * the class field name is "get", "set", or "static", or if - * followed by a generator method. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if the node cannot have the semicolon - * removed. - */ - function maybeClassFieldAsiHazard(node) { - - if (node.type !== "PropertyDefinition") { - return false; - } - - /* - * Computed property names and non-identifiers are always safe - * as they can be distinguished from keywords easily. - */ - const needsNameCheck = !node.computed && node.key.type === "Identifier"; - - /* - * Certain names are problematic unless they also have a - * a way to distinguish between keywords and property - * names. - */ - if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) { - - /* - * Special case: If the field name is `static`, - * it is only valid if the field is marked as static, - * so "static static" is okay but "static" is not. - */ - const isStaticStatic = node.static && node.key.name === "static"; - - /* - * For other unsafe names, we only care if there is no - * initializer. No initializer = hazard. - */ - if (!isStaticStatic && !node.value) { - return true; - } - } - - const followingToken = sourceCode.getTokenAfter(node); - - return unsafeClassFieldFollowers.has(followingToken.value); - } - - /** - * Check whether a given node is on the same line with the next token. - * @param {Node} node A statement node to check. - * @returns {boolean} `true` if the node is on the same line with the next token. - */ - function isOnSameLineWithNextToken(node) { - const prevToken = sourceCode.getLastToken(node, 1); - const nextToken = sourceCode.getTokenAfter(node); - - return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken); - } - - /** - * Check whether a given node can connect the next line if the next line is unreliable. - * @param {Node} node A statement node to check. - * @returns {boolean} `true` if the node can connect the next line. - */ - function maybeAsiHazardAfter(node) { - const t = node.type; - - if (t === "DoWhileStatement" || - t === "BreakStatement" || - t === "ContinueStatement" || - t === "DebuggerStatement" || - t === "ImportDeclaration" || - t === "ExportAllDeclaration" - ) { - return false; - } - if (t === "ReturnStatement") { - return Boolean(node.argument); - } - if (t === "ExportNamedDeclaration") { - return Boolean(node.declaration); - } - if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) { - return false; - } - - return true; - } - - /** - * Check whether a given token can connect the previous statement. - * @param {Token} token A token to check. - * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`. - */ - function maybeAsiHazardBefore(token) { - return ( - Boolean(token) && - OPT_OUT_PATTERN.test(token.value) && - token.value !== "++" && - token.value !== "--" - ); - } - - /** - * Check if the semicolon of a given node is unnecessary, only true if: - * - next token is a valid statement divider (`;` or `}`). - * - next token is on a new line and the node is not connectable to the new line. - * @param {Node} node A statement node to check. - * @returns {boolean} whether the semicolon is unnecessary. - */ - function canRemoveSemicolon(node) { - if (isRedundantSemi(sourceCode.getLastToken(node))) { - return true; // `;;` or `;}` - } - if (maybeClassFieldAsiHazard(node)) { - return false; - } - if (isOnSameLineWithNextToken(node)) { - return false; // One liner. - } - - // continuation characters should not apply to class fields - if ( - node.type !== "PropertyDefinition" && - beforeStatementContinuationChars === "never" && - !maybeAsiHazardAfter(node) - ) { - return true; // ASI works. This statement doesn't connect to the next. - } - if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { - return true; // ASI works. The next token doesn't connect to this statement. - } - - return false; - } - - /** - * Checks a node to see if it's the last item in a one-liner block. - * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its - * braces (and consequently everything between them) are on the same line. - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is the last item in a one-liner block. - */ - function isLastInOneLinerBlock(node) { - const parent = node.parent; - const nextToken = sourceCode.getTokenAfter(node); - - if (!nextToken || nextToken.value !== "}") { - return false; - } - - if (parent.type === "BlockStatement") { - return parent.loc.start.line === parent.loc.end.line; - } - - if (parent.type === "StaticBlock") { - const openingBrace = sourceCode.getFirstToken(parent, { skip: 1 }); // skip the `static` token - - return openingBrace.loc.start.line === parent.loc.end.line; - } - - return false; - } - - /** - * Checks a node to see if it's the last item in a one-liner `ClassBody` node. - * ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line. - * @param {ASTNode} node The node to check. - * @returns {boolean} whether the node is the last item in a one-liner ClassBody. - */ - function isLastInOneLinerClassBody(node) { - const parent = node.parent; - const nextToken = sourceCode.getTokenAfter(node); - - if (!nextToken || nextToken.value !== "}") { - return false; - } - - if (parent.type === "ClassBody") { - return parent.loc.start.line === parent.loc.end.line; - } - - return false; - } - - /** - * Checks a node to see if it's followed by a semicolon. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkForSemicolon(node) { - const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node)); - - if (never) { - if (isSemi && canRemoveSemicolon(node)) { - report(node, true); - } else if ( - !isSemi && beforeStatementContinuationChars === "always" && - node.type !== "PropertyDefinition" && - maybeAsiHazardBefore(sourceCode.getTokenAfter(node)) - ) { - report(node); - } - } else { - const oneLinerBlock = (exceptOneLine && isLastInOneLinerBlock(node)); - const oneLinerClassBody = (exceptOneLineClassBody && isLastInOneLinerClassBody(node)); - const oneLinerBlockOrClassBody = oneLinerBlock || oneLinerClassBody; - - if (isSemi && oneLinerBlockOrClassBody) { - report(node, true); - } else if (!isSemi && !oneLinerBlockOrClassBody) { - report(node); - } - } - } - - /** - * Checks to see if there's a semicolon after a variable declaration. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkForSemicolonForVariableDeclaration(node) { - const parent = node.parent; - - if ((parent.type !== "ForStatement" || parent.init !== node) && - (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node) - ) { - checkForSemicolon(node); - } - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - VariableDeclaration: checkForSemicolonForVariableDeclaration, - ExpressionStatement: checkForSemicolon, - ReturnStatement: checkForSemicolon, - ThrowStatement: checkForSemicolon, - DoWhileStatement: checkForSemicolon, - DebuggerStatement: checkForSemicolon, - BreakStatement: checkForSemicolon, - ContinueStatement: checkForSemicolon, - ImportDeclaration: checkForSemicolon, - ExportAllDeclaration: checkForSemicolon, - ExportNamedDeclaration(node) { - if (!node.declaration) { - checkForSemicolon(node); - } - }, - ExportDefaultDeclaration(node) { - if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) { - checkForSemicolon(node); - } - }, - PropertyDefinition: checkForSemicolon - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "semi", + url: "https://eslint.style/rules/js/semi", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require or disallow semicolons instead of ASI", + recommended: false, + url: "https://eslint.org/docs/latest/rules/semi", + }, + + fixable: "code", + + schema: { + anyOf: [ + { + type: "array", + items: [ + { + enum: ["never"], + }, + { + type: "object", + properties: { + beforeStatementContinuationChars: { + enum: ["always", "any", "never"], + }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + { + type: "array", + items: [ + { + enum: ["always"], + }, + { + type: "object", + properties: { + omitLastInOneLineBlock: { type: "boolean" }, + omitLastInOneLineClassBody: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + minItems: 0, + maxItems: 2, + }, + ], + }, + + messages: { + missingSemi: "Missing semicolon.", + extraSemi: "Extra semicolon.", + }, + }, + + create(context) { + const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-` + const unsafeClassFieldNames = new Set(["get", "set", "static"]); + const unsafeClassFieldFollowers = new Set(["*", "in", "instanceof"]); + const options = context.options[1]; + const never = context.options[0] === "never"; + const exceptOneLine = Boolean( + options && options.omitLastInOneLineBlock, + ); + const exceptOneLineClassBody = Boolean( + options && options.omitLastInOneLineClassBody, + ); + const beforeStatementContinuationChars = + (options && options.beforeStatementContinuationChars) || "any"; + const sourceCode = context.sourceCode; + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Reports a semicolon error with appropriate location and message. + * @param {ASTNode} node The node with an extra or missing semicolon. + * @param {boolean} missing True if the semicolon is missing. + * @returns {void} + */ + function report(node, missing) { + const lastToken = sourceCode.getLastToken(node); + let messageId, fix, loc; + + if (!missing) { + messageId = "missingSemi"; + loc = { + start: lastToken.loc.end, + end: astUtils.getNextLocation( + sourceCode, + lastToken.loc.end, + ), + }; + fix = function (fixer) { + return fixer.insertTextAfter(lastToken, ";"); + }; + } else { + messageId = "extraSemi"; + loc = lastToken.loc; + fix = function (fixer) { + /* + * Expand the replacement range to include the surrounding + * tokens to avoid conflicting with no-extra-semi. + * https://github.com/eslint/eslint/issues/7928 + */ + return new FixTracker(fixer, sourceCode) + .retainSurroundingTokens(lastToken) + .remove(lastToken); + }; + } + + context.report({ + node, + loc, + messageId, + fix, + }); + } + + /** + * Check whether a given semicolon token is redundant. + * @param {Token} semiToken A semicolon token to check. + * @returns {boolean} `true` if the next token is `;` or `}`. + */ + function isRedundantSemi(semiToken) { + const nextToken = sourceCode.getTokenAfter(semiToken); + + return ( + !nextToken || + astUtils.isClosingBraceToken(nextToken) || + astUtils.isSemicolonToken(nextToken) + ); + } + + /** + * Check whether a given token is the closing brace of an arrow function. + * @param {Token} lastToken A token to check. + * @returns {boolean} `true` if the token is the closing brace of an arrow function. + */ + function isEndOfArrowBlock(lastToken) { + if (!astUtils.isClosingBraceToken(lastToken)) { + return false; + } + const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]); + + return ( + node.type === "BlockStatement" && + node.parent.type === "ArrowFunctionExpression" + ); + } + + /** + * Checks if a given PropertyDefinition node followed by a semicolon + * can safely remove that semicolon. It is not to safe to remove if + * the class field name is "get", "set", or "static", or if + * followed by a generator method. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node cannot have the semicolon + * removed. + */ + function maybeClassFieldAsiHazard(node) { + if (node.type !== "PropertyDefinition") { + return false; + } + + /* + * Computed property names and non-identifiers are always safe + * as they can be distinguished from keywords easily. + */ + const needsNameCheck = + !node.computed && node.key.type === "Identifier"; + + /* + * Certain names are problematic unless they also have a + * a way to distinguish between keywords and property + * names. + */ + if (needsNameCheck && unsafeClassFieldNames.has(node.key.name)) { + /* + * Special case: If the field name is `static`, + * it is only valid if the field is marked as static, + * so "static static" is okay but "static" is not. + */ + const isStaticStatic = + node.static && node.key.name === "static"; + + /* + * For other unsafe names, we only care if there is no + * initializer. No initializer = hazard. + */ + if (!isStaticStatic && !node.value) { + return true; + } + } + + const followingToken = sourceCode.getTokenAfter(node); + + return unsafeClassFieldFollowers.has(followingToken.value); + } + + /** + * Check whether a given node is on the same line with the next token. + * @param {Node} node A statement node to check. + * @returns {boolean} `true` if the node is on the same line with the next token. + */ + function isOnSameLineWithNextToken(node) { + const prevToken = sourceCode.getLastToken(node, 1); + const nextToken = sourceCode.getTokenAfter(node); + + return ( + !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken) + ); + } + + /** + * Check whether a given node can connect the next line if the next line is unreliable. + * @param {Node} node A statement node to check. + * @returns {boolean} `true` if the node can connect the next line. + */ + function maybeAsiHazardAfter(node) { + const t = node.type; + + if ( + t === "DoWhileStatement" || + t === "BreakStatement" || + t === "ContinueStatement" || + t === "DebuggerStatement" || + t === "ImportDeclaration" || + t === "ExportAllDeclaration" + ) { + return false; + } + if (t === "ReturnStatement") { + return Boolean(node.argument); + } + if (t === "ExportNamedDeclaration") { + return Boolean(node.declaration); + } + if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) { + return false; + } + + return true; + } + + /** + * Check whether a given token can connect the previous statement. + * @param {Token} token A token to check. + * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`. + */ + function maybeAsiHazardBefore(token) { + return ( + Boolean(token) && + OPT_OUT_PATTERN.test(token.value) && + token.value !== "++" && + token.value !== "--" + ); + } + + /** + * Check if the semicolon of a given node is unnecessary, only true if: + * - next token is a valid statement divider (`;` or `}`). + * - next token is on a new line and the node is not connectable to the new line. + * @param {Node} node A statement node to check. + * @returns {boolean} whether the semicolon is unnecessary. + */ + function canRemoveSemicolon(node) { + if (isRedundantSemi(sourceCode.getLastToken(node))) { + return true; // `;;` or `;}` + } + if (maybeClassFieldAsiHazard(node)) { + return false; + } + if (isOnSameLineWithNextToken(node)) { + return false; // One liner. + } + + // continuation characters should not apply to class fields + if ( + node.type !== "PropertyDefinition" && + beforeStatementContinuationChars === "never" && + !maybeAsiHazardAfter(node) + ) { + return true; // ASI works. This statement doesn't connect to the next. + } + if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) { + return true; // ASI works. The next token doesn't connect to this statement. + } + + return false; + } + + /** + * Checks a node to see if it's the last item in a one-liner block. + * Block is any `BlockStatement` or `StaticBlock` node. Block is a one-liner if its + * braces (and consequently everything between them) are on the same line. + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is the last item in a one-liner block. + */ + function isLastInOneLinerBlock(node) { + const parent = node.parent; + const nextToken = sourceCode.getTokenAfter(node); + + if (!nextToken || nextToken.value !== "}") { + return false; + } + + if (parent.type === "BlockStatement") { + return parent.loc.start.line === parent.loc.end.line; + } + + if (parent.type === "StaticBlock") { + const openingBrace = sourceCode.getFirstToken(parent, { + skip: 1, + }); // skip the `static` token + + return openingBrace.loc.start.line === parent.loc.end.line; + } + + return false; + } + + /** + * Checks a node to see if it's the last item in a one-liner `ClassBody` node. + * ClassBody is a one-liner if its braces (and consequently everything between them) are on the same line. + * @param {ASTNode} node The node to check. + * @returns {boolean} whether the node is the last item in a one-liner ClassBody. + */ + function isLastInOneLinerClassBody(node) { + const parent = node.parent; + const nextToken = sourceCode.getTokenAfter(node); + + if (!nextToken || nextToken.value !== "}") { + return false; + } + + if (parent.type === "ClassBody") { + return parent.loc.start.line === parent.loc.end.line; + } + + return false; + } + + /** + * Checks a node to see if it's followed by a semicolon. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolon(node) { + const isSemi = astUtils.isSemicolonToken( + sourceCode.getLastToken(node), + ); + + if (never) { + if (isSemi && canRemoveSemicolon(node)) { + report(node, true); + } else if ( + !isSemi && + beforeStatementContinuationChars === "always" && + node.type !== "PropertyDefinition" && + maybeAsiHazardBefore(sourceCode.getTokenAfter(node)) + ) { + report(node); + } + } else { + const oneLinerBlock = + exceptOneLine && isLastInOneLinerBlock(node); + const oneLinerClassBody = + exceptOneLineClassBody && isLastInOneLinerClassBody(node); + const oneLinerBlockOrClassBody = + oneLinerBlock || oneLinerClassBody; + + if (isSemi && oneLinerBlockOrClassBody) { + report(node, true); + } else if (!isSemi && !oneLinerBlockOrClassBody) { + report(node); + } + } + } + + /** + * Checks to see if there's a semicolon after a variable declaration. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkForSemicolonForVariableDeclaration(node) { + const parent = node.parent; + + if ( + (parent.type !== "ForStatement" || parent.init !== node) && + (!/^For(?:In|Of)Statement/u.test(parent.type) || + parent.left !== node) + ) { + checkForSemicolon(node); + } + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + VariableDeclaration: checkForSemicolonForVariableDeclaration, + ExpressionStatement: checkForSemicolon, + ReturnStatement: checkForSemicolon, + ThrowStatement: checkForSemicolon, + DoWhileStatement: checkForSemicolon, + DebuggerStatement: checkForSemicolon, + BreakStatement: checkForSemicolon, + ContinueStatement: checkForSemicolon, + ImportDeclaration: checkForSemicolon, + ExportAllDeclaration: checkForSemicolon, + ExportNamedDeclaration(node) { + if (!node.declaration) { + checkForSemicolon(node); + } + }, + ExportDefaultDeclaration(node) { + if ( + !/(?:Class|Function)Declaration/u.test( + node.declaration.type, + ) + ) { + checkForSemicolon(node); + } + }, + PropertyDefinition: checkForSemicolon, + }; + }, }; diff --git a/lib/rules/sort-imports.js b/lib/rules/sort-imports.js index b355da982775..da56fe8a68b3 100644 --- a/lib/rules/sort-imports.js +++ b/lib/rules/sort-imports.js @@ -11,236 +11,309 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: [{ - allowSeparatedGroups: false, - ignoreCase: false, - ignoreDeclarationSort: false, - ignoreMemberSort: false, - memberSyntaxSortOrder: ["none", "all", "multiple", "single"] - }], - - docs: { - description: "Enforce sorted `import` declarations within modules", - recommended: false, - frozen: true, - url: "https://eslint.org/docs/latest/rules/sort-imports" - }, - - schema: [ - { - type: "object", - properties: { - ignoreCase: { - type: "boolean" - }, - memberSyntaxSortOrder: { - type: "array", - items: { - enum: ["none", "all", "multiple", "single"] - }, - uniqueItems: true, - minItems: 4, - maxItems: 4 - }, - ignoreDeclarationSort: { - type: "boolean" - }, - ignoreMemberSort: { - type: "boolean" - }, - allowSeparatedGroups: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - sortImportsAlphabetically: "Imports should be sorted alphabetically.", - sortMembersAlphabetically: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", - unexpectedSyntaxOrder: "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax." - } - }, - - create(context) { - const [{ - ignoreCase, - ignoreDeclarationSort, - ignoreMemberSort, - memberSyntaxSortOrder, - allowSeparatedGroups - }] = context.options; - const sourceCode = context.sourceCode; - let previousDeclaration = null; - - /** - * Gets the used member syntax style. - * - * import "my-module.js" --> none - * import * as myModule from "my-module.js" --> all - * import {myMember} from "my-module.js" --> single - * import {foo, bar} from "my-module.js" --> multiple - * @param {ASTNode} node the ImportDeclaration node. - * @returns {string} used member parameter style, ["all", "multiple", "single"] - */ - function usedMemberSyntax(node) { - if (node.specifiers.length === 0) { - return "none"; - } - if (node.specifiers[0].type === "ImportNamespaceSpecifier") { - return "all"; - } - if (node.specifiers.length === 1) { - return "single"; - } - return "multiple"; - - } - - /** - * Gets the group by member parameter index for given declaration. - * @param {ASTNode} node the ImportDeclaration node. - * @returns {number} the declaration group by member index. - */ - function getMemberParameterGroupIndex(node) { - return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); - } - - /** - * Gets the local name of the first imported module. - * @param {ASTNode} node the ImportDeclaration node. - * @returns {?string} the local name of the first imported module. - */ - function getFirstLocalMemberName(node) { - if (node.specifiers[0]) { - return node.specifiers[0].local.name; - } - return null; - - } - - /** - * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before - * the given `right` node in the source code. Lines are counted from the end of the `left` node till the - * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were - * on two consecutive lines. - * @param {ASTNode} left node that appears before the given `right` node. - * @param {ASTNode} right node that appears after the given `left` node. - * @returns {number} number of lines between nodes. - */ - function getNumberOfLinesBetween(left, right) { - return Math.max(right.loc.start.line - left.loc.end.line - 1, 0); - } - - return { - ImportDeclaration(node) { - if (!ignoreDeclarationSort) { - if ( - previousDeclaration && - allowSeparatedGroups && - getNumberOfLinesBetween(previousDeclaration, node) > 0 - ) { - - // reset declaration sort - previousDeclaration = null; - } - - if (previousDeclaration) { - const currentMemberSyntaxGroupIndex = getMemberParameterGroupIndex(node), - previousMemberSyntaxGroupIndex = getMemberParameterGroupIndex(previousDeclaration); - let currentLocalMemberName = getFirstLocalMemberName(node), - previousLocalMemberName = getFirstLocalMemberName(previousDeclaration); - - if (ignoreCase) { - previousLocalMemberName = previousLocalMemberName && previousLocalMemberName.toLowerCase(); - currentLocalMemberName = currentLocalMemberName && currentLocalMemberName.toLowerCase(); - } - - /* - * When the current declaration uses a different member syntax, - * then check if the ordering is correct. - * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. - */ - if (currentMemberSyntaxGroupIndex !== previousMemberSyntaxGroupIndex) { - if (currentMemberSyntaxGroupIndex < previousMemberSyntaxGroupIndex) { - context.report({ - node, - messageId: "unexpectedSyntaxOrder", - data: { - syntaxA: memberSyntaxSortOrder[currentMemberSyntaxGroupIndex], - syntaxB: memberSyntaxSortOrder[previousMemberSyntaxGroupIndex] - } - }); - } - } else { - if (previousLocalMemberName && - currentLocalMemberName && - currentLocalMemberName < previousLocalMemberName - ) { - context.report({ - node, - messageId: "sortImportsAlphabetically" - }); - } - } - } - - previousDeclaration = node; - } - - if (!ignoreMemberSort) { - const importSpecifiers = node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"); - const getSortableName = ignoreCase ? specifier => specifier.local.name.toLowerCase() : specifier => specifier.local.name; - const firstUnsortedIndex = importSpecifiers.map(getSortableName).findIndex((name, index, array) => array[index - 1] > name); - - if (firstUnsortedIndex !== -1) { - context.report({ - node: importSpecifiers[firstUnsortedIndex], - messageId: "sortMembersAlphabetically", - data: { memberName: importSpecifiers[firstUnsortedIndex].local.name }, - fix(fixer) { - if (importSpecifiers.some(specifier => - sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) { - - // If there are comments in the ImportSpecifier list, don't rearrange the specifiers. - return null; - } - - return fixer.replaceTextRange( - [importSpecifiers[0].range[0], importSpecifiers.at(-1).range[1]], - importSpecifiers - - // Clone the importSpecifiers array to avoid mutating it - .slice() - - // Sort the array into the desired order - .sort((specifierA, specifierB) => { - const aName = getSortableName(specifierA); - const bName = getSortableName(specifierB); - - return aName > bName ? 1 : -1; - }) - - // Build a string out of the sorted list of import specifiers and the text between the originals - .reduce((sourceText, specifier, index) => { - const textAfterSpecifier = index === importSpecifiers.length - 1 - ? "" - : sourceCode.getText().slice(importSpecifiers[index].range[1], importSpecifiers[index + 1].range[0]); - - return sourceText + sourceCode.getText(specifier) + textAfterSpecifier; - }, "") - ); - } - }); - } - } - } - }; - } + meta: { + type: "suggestion", + + defaultOptions: [ + { + allowSeparatedGroups: false, + ignoreCase: false, + ignoreDeclarationSort: false, + ignoreMemberSort: false, + memberSyntaxSortOrder: ["none", "all", "multiple", "single"], + }, + ], + + docs: { + description: "Enforce sorted `import` declarations within modules", + recommended: false, + frozen: true, + url: "https://eslint.org/docs/latest/rules/sort-imports", + }, + + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean", + }, + memberSyntaxSortOrder: { + type: "array", + items: { + enum: ["none", "all", "multiple", "single"], + }, + uniqueItems: true, + minItems: 4, + maxItems: 4, + }, + ignoreDeclarationSort: { + type: "boolean", + }, + ignoreMemberSort: { + type: "boolean", + }, + allowSeparatedGroups: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + sortImportsAlphabetically: + "Imports should be sorted alphabetically.", + sortMembersAlphabetically: + "Member '{{memberName}}' of the import declaration should be sorted alphabetically.", + unexpectedSyntaxOrder: + "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.", + }, + }, + + create(context) { + const [ + { + ignoreCase, + ignoreDeclarationSort, + ignoreMemberSort, + memberSyntaxSortOrder, + allowSeparatedGroups, + }, + ] = context.options; + const sourceCode = context.sourceCode; + let previousDeclaration = null; + + /** + * Gets the used member syntax style. + * + * import "my-module.js" --> none + * import * as myModule from "my-module.js" --> all + * import {myMember} from "my-module.js" --> single + * import {foo, bar} from "my-module.js" --> multiple + * @param {ASTNode} node the ImportDeclaration node. + * @returns {string} used member parameter style, ["all", "multiple", "single"] + */ + function usedMemberSyntax(node) { + if (node.specifiers.length === 0) { + return "none"; + } + if (node.specifiers[0].type === "ImportNamespaceSpecifier") { + return "all"; + } + if (node.specifiers.length === 1) { + return "single"; + } + return "multiple"; + } + + /** + * Gets the group by member parameter index for given declaration. + * @param {ASTNode} node the ImportDeclaration node. + * @returns {number} the declaration group by member index. + */ + function getMemberParameterGroupIndex(node) { + return memberSyntaxSortOrder.indexOf(usedMemberSyntax(node)); + } + + /** + * Gets the local name of the first imported module. + * @param {ASTNode} node the ImportDeclaration node. + * @returns {?string} the local name of the first imported module. + */ + function getFirstLocalMemberName(node) { + if (node.specifiers[0]) { + return node.specifiers[0].local.name; + } + return null; + } + + /** + * Calculates number of lines between two nodes. It is assumed that the given `left` node appears before + * the given `right` node in the source code. Lines are counted from the end of the `left` node till the + * start of the `right` node. If the given nodes are on the same line, it returns `0`, same as if they were + * on two consecutive lines. + * @param {ASTNode} left node that appears before the given `right` node. + * @param {ASTNode} right node that appears after the given `left` node. + * @returns {number} number of lines between nodes. + */ + function getNumberOfLinesBetween(left, right) { + return Math.max(right.loc.start.line - left.loc.end.line - 1, 0); + } + + return { + ImportDeclaration(node) { + if (!ignoreDeclarationSort) { + if ( + previousDeclaration && + allowSeparatedGroups && + getNumberOfLinesBetween(previousDeclaration, node) > 0 + ) { + // reset declaration sort + previousDeclaration = null; + } + + if (previousDeclaration) { + const currentMemberSyntaxGroupIndex = + getMemberParameterGroupIndex(node), + previousMemberSyntaxGroupIndex = + getMemberParameterGroupIndex( + previousDeclaration, + ); + let currentLocalMemberName = + getFirstLocalMemberName(node), + previousLocalMemberName = + getFirstLocalMemberName(previousDeclaration); + + if (ignoreCase) { + previousLocalMemberName = + previousLocalMemberName && + previousLocalMemberName.toLowerCase(); + currentLocalMemberName = + currentLocalMemberName && + currentLocalMemberName.toLowerCase(); + } + + /* + * When the current declaration uses a different member syntax, + * then check if the ordering is correct. + * Otherwise, make a default string compare (like rule sort-vars to be consistent) of the first used local member name. + */ + if ( + currentMemberSyntaxGroupIndex !== + previousMemberSyntaxGroupIndex + ) { + if ( + currentMemberSyntaxGroupIndex < + previousMemberSyntaxGroupIndex + ) { + context.report({ + node, + messageId: "unexpectedSyntaxOrder", + data: { + syntaxA: + memberSyntaxSortOrder[ + currentMemberSyntaxGroupIndex + ], + syntaxB: + memberSyntaxSortOrder[ + previousMemberSyntaxGroupIndex + ], + }, + }); + } + } else { + if ( + previousLocalMemberName && + currentLocalMemberName && + currentLocalMemberName < previousLocalMemberName + ) { + context.report({ + node, + messageId: "sortImportsAlphabetically", + }); + } + } + } + + previousDeclaration = node; + } + + if (!ignoreMemberSort) { + const importSpecifiers = node.specifiers.filter( + specifier => specifier.type === "ImportSpecifier", + ); + const getSortableName = ignoreCase + ? specifier => specifier.local.name.toLowerCase() + : specifier => specifier.local.name; + const firstUnsortedIndex = importSpecifiers + .map(getSortableName) + .findIndex( + (name, index, array) => array[index - 1] > name, + ); + + if (firstUnsortedIndex !== -1) { + context.report({ + node: importSpecifiers[firstUnsortedIndex], + messageId: "sortMembersAlphabetically", + data: { + memberName: + importSpecifiers[firstUnsortedIndex].local + .name, + }, + fix(fixer) { + if ( + importSpecifiers.some( + specifier => + sourceCode.getCommentsBefore( + specifier, + ).length || + sourceCode.getCommentsAfter( + specifier, + ).length, + ) + ) { + // If there are comments in the ImportSpecifier list, don't rearrange the specifiers. + return null; + } + + return fixer.replaceTextRange( + [ + importSpecifiers[0].range[0], + importSpecifiers.at(-1).range[1], + ], + importSpecifiers + + // Clone the importSpecifiers array to avoid mutating it + .slice() + + // Sort the array into the desired order + .sort((specifierA, specifierB) => { + const aName = + getSortableName(specifierA); + const bName = + getSortableName(specifierB); + + return aName > bName ? 1 : -1; + }) + + // Build a string out of the sorted list of import specifiers and the text between the originals + .reduce( + (sourceText, specifier, index) => { + const textAfterSpecifier = + index === + importSpecifiers.length - 1 + ? "" + : sourceCode + .getText() + .slice( + importSpecifiers[ + index + ].range[1], + importSpecifiers[ + index + + 1 + ].range[0], + ); + + return ( + sourceText + + sourceCode.getText( + specifier, + ) + + textAfterSpecifier + ); + }, + "", + ), + ); + }, + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js index 47932609a14c..3735b4d7e762 100644 --- a/lib/rules/sort-keys.js +++ b/lib/rules/sort-keys.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const astUtils = require("./utils/ast-utils"), - naturalCompare = require("natural-compare"); + naturalCompare = require("natural-compare"); //------------------------------------------------------------------------------ // Helpers @@ -28,13 +28,13 @@ const astUtils = require("./utils/ast-utils"), * @private */ function getPropertyName(node) { - const staticName = astUtils.getStaticPropertyName(node); + const staticName = astUtils.getStaticPropertyName(node); - if (staticName !== null) { - return staticName; - } + if (staticName !== null) { + return staticName; + } - return node.key.name || null; + return node.key.name || null; } /** @@ -45,30 +45,30 @@ function getPropertyName(node) { * @private */ const isValidOrders = { - asc(a, b) { - return a <= b; - }, - ascI(a, b) { - return a.toLowerCase() <= b.toLowerCase(); - }, - ascN(a, b) { - return naturalCompare(a, b) <= 0; - }, - ascIN(a, b) { - return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0; - }, - desc(a, b) { - return isValidOrders.asc(b, a); - }, - descI(a, b) { - return isValidOrders.ascI(b, a); - }, - descN(a, b) { - return isValidOrders.ascN(b, a); - }, - descIN(a, b) { - return isValidOrders.ascIN(b, a); - } + asc(a, b) { + return a <= b; + }, + ascI(a, b) { + return a.toLowerCase() <= b.toLowerCase(); + }, + ascN(a, b) { + return naturalCompare(a, b) <= 0; + }, + ascIN(a, b) { + return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0; + }, + desc(a, b) { + return isValidOrders.asc(b, a); + }, + descI(a, b) { + return isValidOrders.ascI(b, a); + }, + descN(a, b) { + return isValidOrders.ascN(b, a); + }, + descIN(a, b) { + return isValidOrders.ascIN(b, a); + }, }; //------------------------------------------------------------------------------ @@ -77,161 +77,192 @@ const isValidOrders = { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - enum: ["asc", "desc"] - }, - { - type: "object", - properties: { - caseSensitive: { - type: "boolean" - }, - natural: { - type: "boolean" - }, - minKeys: { - type: "integer", - minimum: 2 - }, - allowLineSeparatedGroups: { - type: "boolean" - }, - ignoreComputedKeys: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - messages: { - sortKeys: "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'." - } - }, - - create(context) { - const [order, { caseSensitive, natural, minKeys, allowLineSeparatedGroups, ignoreComputedKeys }] = context.options; - const insensitive = !caseSensitive; - const isValidOrder = isValidOrders[ - order + (insensitive ? "I" : "") + (natural ? "N" : "") - ]; - - // The stack to save the previous property's name for each object literals. - let stack = null; - const sourceCode = context.sourceCode; - - return { - ObjectExpression(node) { - stack = { - upper: stack, - prevNode: null, - prevBlankLine: false, - prevName: null, - numKeys: node.properties.length - }; - }, - - "ObjectExpression:exit"() { - stack = stack.upper; - }, - - SpreadElement(node) { - if (node.parent.type === "ObjectExpression") { - stack.prevName = null; - } - }, - - Property(node) { - if (node.parent.type === "ObjectPattern") { - return; - } - - if (ignoreComputedKeys && node.computed) { - stack.prevName = null; // reset sort - return; - } - - const prevName = stack.prevName; - const numKeys = stack.numKeys; - const thisName = getPropertyName(node); - - // Get tokens between current node and previous node - const tokens = stack.prevNode && sourceCode - .getTokensBetween(stack.prevNode, node, { includeComments: true }); - - let isBlankLineBetweenNodes = stack.prevBlankLine; - - if (tokens) { - - // check blank line between tokens - tokens.forEach((token, index) => { - const previousToken = tokens[index - 1]; - - if (previousToken && (token.loc.start.line - previousToken.loc.end.line > 1)) { - isBlankLineBetweenNodes = true; - } - }); - - // check blank line between the current node and the last token - if (!isBlankLineBetweenNodes && (node.loc.start.line - tokens.at(-1).loc.end.line > 1)) { - isBlankLineBetweenNodes = true; - } - - // check blank line between the first token and the previous node - if (!isBlankLineBetweenNodes && (tokens[0].loc.start.line - stack.prevNode.loc.end.line > 1)) { - isBlankLineBetweenNodes = true; - } - } - - stack.prevNode = node; - - if (thisName !== null) { - stack.prevName = thisName; - } - - if (allowLineSeparatedGroups && isBlankLineBetweenNodes) { - stack.prevBlankLine = thisName === null; - return; - } - - if (prevName === null || thisName === null || numKeys < minKeys) { - return; - } - - if (!isValidOrder(prevName, thisName)) { - context.report({ - node, - loc: node.key.loc, - messageId: "sortKeys", - data: { - thisName, - prevName, - order, - insensitive: insensitive ? "insensitive " : "", - natural: natural ? "natural " : "" - } - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [ + { + enum: ["asc", "desc"], + }, + { + type: "object", + properties: { + caseSensitive: { + type: "boolean", + }, + natural: { + type: "boolean", + }, + minKeys: { + type: "integer", + minimum: 2, + }, + allowLineSeparatedGroups: { + type: "boolean", + }, + ignoreComputedKeys: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + messages: { + sortKeys: + "Expected object keys to be in {{natural}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.", + }, + }, + + create(context) { + const [ + order, + { + caseSensitive, + natural, + minKeys, + allowLineSeparatedGroups, + ignoreComputedKeys, + }, + ] = context.options; + const insensitive = !caseSensitive; + const isValidOrder = + isValidOrders[ + order + (insensitive ? "I" : "") + (natural ? "N" : "") + ]; + + // The stack to save the previous property's name for each object literals. + let stack = null; + const sourceCode = context.sourceCode; + + return { + ObjectExpression(node) { + stack = { + upper: stack, + prevNode: null, + prevBlankLine: false, + prevName: null, + numKeys: node.properties.length, + }; + }, + + "ObjectExpression:exit"() { + stack = stack.upper; + }, + + SpreadElement(node) { + if (node.parent.type === "ObjectExpression") { + stack.prevName = null; + } + }, + + Property(node) { + if (node.parent.type === "ObjectPattern") { + return; + } + + if (ignoreComputedKeys && node.computed) { + stack.prevName = null; // reset sort + return; + } + + const prevName = stack.prevName; + const numKeys = stack.numKeys; + const thisName = getPropertyName(node); + + // Get tokens between current node and previous node + const tokens = + stack.prevNode && + sourceCode.getTokensBetween(stack.prevNode, node, { + includeComments: true, + }); + + let isBlankLineBetweenNodes = stack.prevBlankLine; + + if (tokens) { + // check blank line between tokens + tokens.forEach((token, index) => { + const previousToken = tokens[index - 1]; + + if ( + previousToken && + token.loc.start.line - previousToken.loc.end.line > + 1 + ) { + isBlankLineBetweenNodes = true; + } + }); + + // check blank line between the current node and the last token + if ( + !isBlankLineBetweenNodes && + node.loc.start.line - tokens.at(-1).loc.end.line > 1 + ) { + isBlankLineBetweenNodes = true; + } + + // check blank line between the first token and the previous node + if ( + !isBlankLineBetweenNodes && + tokens[0].loc.start.line - stack.prevNode.loc.end.line > + 1 + ) { + isBlankLineBetweenNodes = true; + } + } + + stack.prevNode = node; + + if (thisName !== null) { + stack.prevName = thisName; + } + + if (allowLineSeparatedGroups && isBlankLineBetweenNodes) { + stack.prevBlankLine = thisName === null; + return; + } + + if ( + prevName === null || + thisName === null || + numKeys < minKeys + ) { + return; + } + + if (!isValidOrder(prevName, thisName)) { + context.report({ + node, + loc: node.key.loc, + messageId: "sortKeys", + data: { + thisName, + prevName, + order, + insensitive: insensitive ? "insensitive " : "", + natural: natural ? "natural " : "", + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/sort-vars.js b/lib/rules/sort-vars.js index cc22061a2507..5b13ea69fd5c 100644 --- a/lib/rules/sort-vars.js +++ b/lib/rules/sort-vars.js @@ -11,96 +11,130 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - ignoreCase: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - - messages: { - sortVars: "Variables within the same declaration block should be sorted alphabetically." - } - }, - - create(context) { - const [{ ignoreCase }] = context.options; - const sourceCode = context.sourceCode; - - return { - VariableDeclaration(node) { - const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier"); - const getSortableName = ignoreCase ? decl => decl.id.name.toLowerCase() : decl => decl.id.name; - const unfixable = idDeclarations.some(decl => decl.init !== null && decl.init.type !== "Literal"); - let fixed = false; - - idDeclarations.slice(1).reduce((memo, decl) => { - const lastVariableName = getSortableName(memo), - currentVariableName = getSortableName(decl); - - if (currentVariableName < lastVariableName) { - context.report({ - node: decl, - messageId: "sortVars", - fix(fixer) { - if (unfixable || fixed) { - return null; - } - return fixer.replaceTextRange( - [idDeclarations[0].range[0], idDeclarations.at(-1).range[1]], - idDeclarations - - // Clone the idDeclarations array to avoid mutating it - .slice() - - // Sort the array into the desired order - .sort((declA, declB) => { - const aName = getSortableName(declA); - const bName = getSortableName(declB); - - return aName > bName ? 1 : -1; - }) - - // Build a string out of the sorted list of identifier declarations and the text between the originals - .reduce((sourceText, identifier, index) => { - const textAfterIdentifier = index === idDeclarations.length - 1 - ? "" - : sourceCode.getText().slice(idDeclarations[index].range[1], idDeclarations[index + 1].range[0]); - - return sourceText + sourceCode.getText(identifier) + textAfterIdentifier; - }, "") - - ); - } - }); - fixed = true; - return memo; - } - return decl; - - }, idDeclarations[0]); - } - }; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + ignoreCase: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + + messages: { + sortVars: + "Variables within the same declaration block should be sorted alphabetically.", + }, + }, + + create(context) { + const [{ ignoreCase }] = context.options; + const sourceCode = context.sourceCode; + + return { + VariableDeclaration(node) { + const idDeclarations = node.declarations.filter( + decl => decl.id.type === "Identifier", + ); + const getSortableName = ignoreCase + ? decl => decl.id.name.toLowerCase() + : decl => decl.id.name; + const unfixable = idDeclarations.some( + decl => decl.init !== null && decl.init.type !== "Literal", + ); + let fixed = false; + + idDeclarations.slice(1).reduce((memo, decl) => { + const lastVariableName = getSortableName(memo), + currentVariableName = getSortableName(decl); + + if (currentVariableName < lastVariableName) { + context.report({ + node: decl, + messageId: "sortVars", + fix(fixer) { + if (unfixable || fixed) { + return null; + } + return fixer.replaceTextRange( + [ + idDeclarations[0].range[0], + idDeclarations.at(-1).range[1], + ], + idDeclarations + + // Clone the idDeclarations array to avoid mutating it + .slice() + + // Sort the array into the desired order + .sort((declA, declB) => { + const aName = + getSortableName(declA); + const bName = + getSortableName(declB); + + return aName > bName ? 1 : -1; + }) + + // Build a string out of the sorted list of identifier declarations and the text between the originals + .reduce( + (sourceText, identifier, index) => { + const textAfterIdentifier = + index === + idDeclarations.length - 1 + ? "" + : sourceCode + .getText() + .slice( + idDeclarations[ + index + ].range[1], + idDeclarations[ + index + + 1 + ].range[0], + ); + + return ( + sourceText + + sourceCode.getText( + identifier, + ) + + textAfterIdentifier + ); + }, + "", + ), + ); + }, + }); + fixed = true; + return memo; + } + return decl; + }, idDeclarations[0]); + }, + }; + }, }; diff --git a/lib/rules/space-before-blocks.js b/lib/rules/space-before-blocks.js index 0e173ea3341d..366b4ceaf6a8 100644 --- a/lib/rules/space-before-blocks.js +++ b/lib/rules/space-before-blocks.js @@ -22,13 +22,13 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is function body. */ function isFunctionBody(node) { - const parent = node.parent; + const parent = node.parent; - return ( - node.type === "BlockStatement" && - astUtils.isFunction(parent) && - parent.body === node - ); + return ( + node.type === "BlockStatement" && + astUtils.isFunction(parent) && + parent.body === node + ); } //------------------------------------------------------------------------------ @@ -37,186 +37,196 @@ function isFunctionBody(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "space-before-blocks", - url: "https://eslint.style/rules/js/space-before-blocks" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before blocks", - recommended: false, - url: "https://eslint.org/docs/latest/rules/space-before-blocks" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - keywords: { - enum: ["always", "never", "off"] - }, - functions: { - enum: ["always", "never", "off"] - }, - classes: { - enum: ["always", "never", "off"] - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - unexpectedSpace: "Unexpected space before opening brace.", - missingSpace: "Missing space before opening brace." - } - }, - - create(context) { - const config = context.options[0], - sourceCode = context.sourceCode; - let alwaysFunctions = true, - alwaysKeywords = true, - alwaysClasses = true, - neverFunctions = false, - neverKeywords = false, - neverClasses = false; - - if (typeof config === "object") { - alwaysFunctions = config.functions === "always"; - alwaysKeywords = config.keywords === "always"; - alwaysClasses = config.classes === "always"; - neverFunctions = config.functions === "never"; - neverKeywords = config.keywords === "never"; - neverClasses = config.classes === "never"; - } else if (config === "never") { - alwaysFunctions = false; - alwaysKeywords = false; - alwaysClasses = false; - neverFunctions = true; - neverKeywords = true; - neverClasses = true; - } - - /** - * Checks whether the spacing before the given block is already controlled by another rule: - * - `arrow-spacing` checks spaces after `=>`. - * - `keyword-spacing` checks spaces after keywords in certain contexts. - * - `switch-colon-spacing` checks spaces after `:` of switch cases. - * @param {Token} precedingToken first token before the block. - * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node. - * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules. - */ - function isConflicted(precedingToken, node) { - return ( - astUtils.isArrowToken(precedingToken) || - ( - astUtils.isKeywordToken(precedingToken) && - !isFunctionBody(node) - ) || - ( - astUtils.isColonToken(precedingToken) && - node.parent && - node.parent.type === "SwitchCase" && - precedingToken === astUtils.getSwitchCaseColonToken(node.parent, sourceCode) - ) - ); - } - - /** - * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. - * @param {ASTNode|Token} node The AST node of a BlockStatement. - * @returns {void} undefined. - */ - function checkPrecedingSpace(node) { - const precedingToken = sourceCode.getTokenBefore(node); - - if (precedingToken && !isConflicted(precedingToken, node) && astUtils.isTokenOnSameLine(precedingToken, node)) { - const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node); - let requireSpace; - let requireNoSpace; - - if (isFunctionBody(node)) { - requireSpace = alwaysFunctions; - requireNoSpace = neverFunctions; - } else if (node.type === "ClassBody") { - requireSpace = alwaysClasses; - requireNoSpace = neverClasses; - } else { - requireSpace = alwaysKeywords; - requireNoSpace = neverKeywords; - } - - if (requireSpace && !hasSpace) { - context.report({ - node, - messageId: "missingSpace", - fix(fixer) { - return fixer.insertTextBefore(node, " "); - } - }); - } else if (requireNoSpace && hasSpace) { - context.report({ - node, - messageId: "unexpectedSpace", - fix(fixer) { - return fixer.removeRange([precedingToken.range[1], node.range[0]]); - } - }); - } - } - } - - /** - * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. - * @param {ASTNode} node The node of a SwitchStatement. - * @returns {void} undefined. - */ - function checkSpaceBeforeCaseBlock(node) { - const cases = node.cases; - let openingBrace; - - if (cases.length > 0) { - openingBrace = sourceCode.getTokenBefore(cases[0]); - } else { - openingBrace = sourceCode.getLastToken(node, 1); - } - - checkPrecedingSpace(openingBrace); - } - - return { - BlockStatement: checkPrecedingSpace, - ClassBody: checkPrecedingSpace, - SwitchStatement: checkSpaceBeforeCaseBlock - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "space-before-blocks", + url: "https://eslint.style/rules/js/space-before-blocks", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent spacing before blocks", + recommended: false, + url: "https://eslint.org/docs/latest/rules/space-before-blocks", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + keywords: { + enum: ["always", "never", "off"], + }, + functions: { + enum: ["always", "never", "off"], + }, + classes: { + enum: ["always", "never", "off"], + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + unexpectedSpace: "Unexpected space before opening brace.", + missingSpace: "Missing space before opening brace.", + }, + }, + + create(context) { + const config = context.options[0], + sourceCode = context.sourceCode; + let alwaysFunctions = true, + alwaysKeywords = true, + alwaysClasses = true, + neverFunctions = false, + neverKeywords = false, + neverClasses = false; + + if (typeof config === "object") { + alwaysFunctions = config.functions === "always"; + alwaysKeywords = config.keywords === "always"; + alwaysClasses = config.classes === "always"; + neverFunctions = config.functions === "never"; + neverKeywords = config.keywords === "never"; + neverClasses = config.classes === "never"; + } else if (config === "never") { + alwaysFunctions = false; + alwaysKeywords = false; + alwaysClasses = false; + neverFunctions = true; + neverKeywords = true; + neverClasses = true; + } + + /** + * Checks whether the spacing before the given block is already controlled by another rule: + * - `arrow-spacing` checks spaces after `=>`. + * - `keyword-spacing` checks spaces after keywords in certain contexts. + * - `switch-colon-spacing` checks spaces after `:` of switch cases. + * @param {Token} precedingToken first token before the block. + * @param {ASTNode|Token} node `BlockStatement` node or `{` token of a `SwitchStatement` node. + * @returns {boolean} `true` if requiring or disallowing spaces before the given block could produce conflicts with other rules. + */ + function isConflicted(precedingToken, node) { + return ( + astUtils.isArrowToken(precedingToken) || + (astUtils.isKeywordToken(precedingToken) && + !isFunctionBody(node)) || + (astUtils.isColonToken(precedingToken) && + node.parent && + node.parent.type === "SwitchCase" && + precedingToken === + astUtils.getSwitchCaseColonToken( + node.parent, + sourceCode, + )) + ); + } + + /** + * Checks the given BlockStatement node has a preceding space if it doesn’t start on a new line. + * @param {ASTNode|Token} node The AST node of a BlockStatement. + * @returns {void} undefined. + */ + function checkPrecedingSpace(node) { + const precedingToken = sourceCode.getTokenBefore(node); + + if ( + precedingToken && + !isConflicted(precedingToken, node) && + astUtils.isTokenOnSameLine(precedingToken, node) + ) { + const hasSpace = sourceCode.isSpaceBetweenTokens( + precedingToken, + node, + ); + let requireSpace; + let requireNoSpace; + + if (isFunctionBody(node)) { + requireSpace = alwaysFunctions; + requireNoSpace = neverFunctions; + } else if (node.type === "ClassBody") { + requireSpace = alwaysClasses; + requireNoSpace = neverClasses; + } else { + requireSpace = alwaysKeywords; + requireNoSpace = neverKeywords; + } + + if (requireSpace && !hasSpace) { + context.report({ + node, + messageId: "missingSpace", + fix(fixer) { + return fixer.insertTextBefore(node, " "); + }, + }); + } else if (requireNoSpace && hasSpace) { + context.report({ + node, + messageId: "unexpectedSpace", + fix(fixer) { + return fixer.removeRange([ + precedingToken.range[1], + node.range[0], + ]); + }, + }); + } + } + } + + /** + * Checks if the CaseBlock of an given SwitchStatement node has a preceding space. + * @param {ASTNode} node The node of a SwitchStatement. + * @returns {void} undefined. + */ + function checkSpaceBeforeCaseBlock(node) { + const cases = node.cases; + let openingBrace; + + if (cases.length > 0) { + openingBrace = sourceCode.getTokenBefore(cases[0]); + } else { + openingBrace = sourceCode.getLastToken(node, 1); + } + + checkPrecedingSpace(openingBrace); + } + + return { + BlockStatement: checkPrecedingSpace, + ClassBody: checkPrecedingSpace, + SwitchStatement: checkSpaceBeforeCaseBlock, + }; + }, }; diff --git a/lib/rules/space-before-function-paren.js b/lib/rules/space-before-function-paren.js index 0897c3b35e44..5fce08d802c2 100644 --- a/lib/rules/space-before-function-paren.js +++ b/lib/rules/space-before-function-paren.js @@ -17,169 +17,189 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "space-before-function-paren", - url: "https://eslint.style/rules/js/space-before-function-paren" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing before `function` definition opening parenthesis", - recommended: false, - url: "https://eslint.org/docs/latest/rules/space-before-function-paren" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - anonymous: { - enum: ["always", "never", "ignore"] - }, - named: { - enum: ["always", "never", "ignore"] - }, - asyncArrow: { - enum: ["always", "never", "ignore"] - } - }, - additionalProperties: false - } - ] - } - ], - - messages: { - unexpectedSpace: "Unexpected space before function parentheses.", - missingSpace: "Missing space before function parentheses." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const baseConfig = typeof context.options[0] === "string" ? context.options[0] : "always"; - const overrideConfig = typeof context.options[0] === "object" ? context.options[0] : {}; - - /** - * Determines whether a function has a name. - * @param {ASTNode} node The function node. - * @returns {boolean} Whether the function has a name. - */ - function isNamedFunction(node) { - if (node.id) { - return true; - } - - const parent = node.parent; - - return parent.type === "MethodDefinition" || - (parent.type === "Property" && - ( - parent.kind === "get" || - parent.kind === "set" || - parent.method - ) - ); - } - - /** - * Gets the config for a given function - * @param {ASTNode} node The function node - * @returns {string} "always", "never", or "ignore" - */ - function getConfigForFunction(node) { - if (node.type === "ArrowFunctionExpression") { - - // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar - if (node.async && astUtils.isOpeningParenToken(sourceCode.getFirstToken(node, { skip: 1 }))) { - return overrideConfig.asyncArrow || baseConfig; - } - } else if (isNamedFunction(node)) { - return overrideConfig.named || baseConfig; - - // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}` - } else if (!node.generator) { - return overrideConfig.anonymous || baseConfig; - } - - return "ignore"; - } - - /** - * Checks the parens of a function node - * @param {ASTNode} node A function node - * @returns {void} - */ - function checkFunction(node) { - const functionConfig = getConfigForFunction(node); - - if (functionConfig === "ignore") { - return; - } - - const rightToken = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken); - const leftToken = sourceCode.getTokenBefore(rightToken); - const hasSpacing = sourceCode.isSpaceBetweenTokens(leftToken, rightToken); - - if (hasSpacing && functionConfig === "never") { - context.report({ - node, - loc: { - start: leftToken.loc.end, - end: rightToken.loc.start - }, - messageId: "unexpectedSpace", - fix(fixer) { - const comments = sourceCode.getCommentsBefore(rightToken); - - // Don't fix anything if there's a single line comment between the left and the right token - if (comments.some(comment => comment.type === "Line")) { - return null; - } - return fixer.replaceTextRange( - [leftToken.range[1], rightToken.range[0]], - comments.reduce((text, comment) => text + sourceCode.getText(comment), "") - ); - } - }); - } else if (!hasSpacing && functionConfig === "always") { - context.report({ - node, - loc: rightToken.loc, - messageId: "missingSpace", - fix: fixer => fixer.insertTextAfter(leftToken, " ") - }); - } - } - - return { - ArrowFunctionExpression: checkFunction, - FunctionDeclaration: checkFunction, - FunctionExpression: checkFunction - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "space-before-function-paren", + url: "https://eslint.style/rules/js/space-before-function-paren", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Enforce consistent spacing before `function` definition opening parenthesis", + recommended: false, + url: "https://eslint.org/docs/latest/rules/space-before-function-paren", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + anonymous: { + enum: ["always", "never", "ignore"], + }, + named: { + enum: ["always", "never", "ignore"], + }, + asyncArrow: { + enum: ["always", "never", "ignore"], + }, + }, + additionalProperties: false, + }, + ], + }, + ], + + messages: { + unexpectedSpace: "Unexpected space before function parentheses.", + missingSpace: "Missing space before function parentheses.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const baseConfig = + typeof context.options[0] === "string" + ? context.options[0] + : "always"; + const overrideConfig = + typeof context.options[0] === "object" ? context.options[0] : {}; + + /** + * Determines whether a function has a name. + * @param {ASTNode} node The function node. + * @returns {boolean} Whether the function has a name. + */ + function isNamedFunction(node) { + if (node.id) { + return true; + } + + const parent = node.parent; + + return ( + parent.type === "MethodDefinition" || + (parent.type === "Property" && + (parent.kind === "get" || + parent.kind === "set" || + parent.method)) + ); + } + + /** + * Gets the config for a given function + * @param {ASTNode} node The function node + * @returns {string} "always", "never", or "ignore" + */ + function getConfigForFunction(node) { + if (node.type === "ArrowFunctionExpression") { + // Always ignore non-async functions and arrow functions without parens, e.g. async foo => bar + if ( + node.async && + astUtils.isOpeningParenToken( + sourceCode.getFirstToken(node, { skip: 1 }), + ) + ) { + return overrideConfig.asyncArrow || baseConfig; + } + } else if (isNamedFunction(node)) { + return overrideConfig.named || baseConfig; + + // `generator-star-spacing` should warn anonymous generators. E.g. `function* () {}` + } else if (!node.generator) { + return overrideConfig.anonymous || baseConfig; + } + + return "ignore"; + } + + /** + * Checks the parens of a function node + * @param {ASTNode} node A function node + * @returns {void} + */ + function checkFunction(node) { + const functionConfig = getConfigForFunction(node); + + if (functionConfig === "ignore") { + return; + } + + const rightToken = sourceCode.getFirstToken( + node, + astUtils.isOpeningParenToken, + ); + const leftToken = sourceCode.getTokenBefore(rightToken); + const hasSpacing = sourceCode.isSpaceBetweenTokens( + leftToken, + rightToken, + ); + + if (hasSpacing && functionConfig === "never") { + context.report({ + node, + loc: { + start: leftToken.loc.end, + end: rightToken.loc.start, + }, + messageId: "unexpectedSpace", + fix(fixer) { + const comments = + sourceCode.getCommentsBefore(rightToken); + + // Don't fix anything if there's a single line comment between the left and the right token + if (comments.some(comment => comment.type === "Line")) { + return null; + } + return fixer.replaceTextRange( + [leftToken.range[1], rightToken.range[0]], + comments.reduce( + (text, comment) => + text + sourceCode.getText(comment), + "", + ), + ); + }, + }); + } else if (!hasSpacing && functionConfig === "always") { + context.report({ + node, + loc: rightToken.loc, + messageId: "missingSpace", + fix: fixer => fixer.insertTextAfter(leftToken, " "), + }); + } + } + + return { + ArrowFunctionExpression: checkFunction, + FunctionDeclaration: checkFunction, + FunctionExpression: checkFunction, + }; + }, }; diff --git a/lib/rules/space-in-parens.js b/lib/rules/space-in-parens.js index 7cbda1fccbe7..e127a51d3da1 100644 --- a/lib/rules/space-in-parens.js +++ b/lib/rules/space-in-parens.js @@ -13,291 +13,362 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "space-in-parens", - url: "https://eslint.style/rules/js/space-in-parens" - } - } - ] - }, - type: "layout", - - docs: { - description: "Enforce consistent spacing inside parentheses", - recommended: false, - url: "https://eslint.org/docs/latest/rules/space-in-parens" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - enum: ["{}", "[]", "()", "empty"] - }, - uniqueItems: true - } - }, - additionalProperties: false - } - ], - - messages: { - missingOpeningSpace: "There must be a space after this paren.", - missingClosingSpace: "There must be a space before this paren.", - rejectedOpeningSpace: "There should be no space after this paren.", - rejectedClosingSpace: "There should be no space before this paren." - } - }, - - create(context) { - const ALWAYS = context.options[0] === "always", - exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [], - options = {}; - - let exceptions; - - if (exceptionsArrayOptions.length) { - options.braceException = exceptionsArrayOptions.includes("{}"); - options.bracketException = exceptionsArrayOptions.includes("[]"); - options.parenException = exceptionsArrayOptions.includes("()"); - options.empty = exceptionsArrayOptions.includes("empty"); - } - - /** - * Produces an object with the opener and closer exception values - * @returns {Object} `openers` and `closers` exception values - * @private - */ - function getExceptions() { - const openers = [], - closers = []; - - if (options.braceException) { - openers.push("{"); - closers.push("}"); - } - - if (options.bracketException) { - openers.push("["); - closers.push("]"); - } - - if (options.parenException) { - openers.push("("); - closers.push(")"); - } - - if (options.empty) { - openers.push(")"); - closers.push("("); - } - - return { - openers, - closers - }; - } - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - const sourceCode = context.sourceCode; - - /** - * Determines if a token is one of the exceptions for the opener paren - * @param {Object} token The token to check - * @returns {boolean} True if the token is one of the exceptions for the opener paren - */ - function isOpenerException(token) { - return exceptions.openers.includes(token.value); - } - - /** - * Determines if a token is one of the exceptions for the closer paren - * @param {Object} token The token to check - * @returns {boolean} True if the token is one of the exceptions for the closer paren - */ - function isCloserException(token) { - return exceptions.closers.includes(token.value); - } - - /** - * Determines if an opening paren is immediately followed by a required space - * @param {Object} openingParenToken The paren token - * @param {Object} tokenAfterOpeningParen The token after it - * @returns {boolean} True if the opening paren is missing a required space - */ - function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) { - if (sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) { - return false; - } - - if (!options.empty && astUtils.isClosingParenToken(tokenAfterOpeningParen)) { - return false; - } - - if (ALWAYS) { - return !isOpenerException(tokenAfterOpeningParen); - } - return isOpenerException(tokenAfterOpeningParen); - } - - /** - * Determines if an opening paren is immediately followed by a disallowed space - * @param {Object} openingParenToken The paren token - * @param {Object} tokenAfterOpeningParen The token after it - * @returns {boolean} True if the opening paren has a disallowed space - */ - function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) { - if (!astUtils.isTokenOnSameLine(openingParenToken, tokenAfterOpeningParen)) { - return false; - } - - if (tokenAfterOpeningParen.type === "Line") { - return false; - } - - if (!sourceCode.isSpaceBetweenTokens(openingParenToken, tokenAfterOpeningParen)) { - return false; - } - - if (ALWAYS) { - return isOpenerException(tokenAfterOpeningParen); - } - return !isOpenerException(tokenAfterOpeningParen); - } - - /** - * Determines if a closing paren is immediately preceded by a required space - * @param {Object} tokenBeforeClosingParen The token before the paren - * @param {Object} closingParenToken The paren token - * @returns {boolean} True if the closing paren is missing a required space - */ - function closerMissingSpace(tokenBeforeClosingParen, closingParenToken) { - if (sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) { - return false; - } - - if (!options.empty && astUtils.isOpeningParenToken(tokenBeforeClosingParen)) { - return false; - } - - if (ALWAYS) { - return !isCloserException(tokenBeforeClosingParen); - } - return isCloserException(tokenBeforeClosingParen); - } - - /** - * Determines if a closer paren is immediately preceded by a disallowed space - * @param {Object} tokenBeforeClosingParen The token before the paren - * @param {Object} closingParenToken The paren token - * @returns {boolean} True if the closing paren has a disallowed space - */ - function closerRejectsSpace(tokenBeforeClosingParen, closingParenToken) { - if (!astUtils.isTokenOnSameLine(tokenBeforeClosingParen, closingParenToken)) { - return false; - } - - if (!sourceCode.isSpaceBetweenTokens(tokenBeforeClosingParen, closingParenToken)) { - return false; - } - - if (ALWAYS) { - return isCloserException(tokenBeforeClosingParen); - } - return !isCloserException(tokenBeforeClosingParen); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - Program: function checkParenSpaces(node) { - exceptions = getExceptions(); - const tokens = sourceCode.tokensAndComments; - - tokens.forEach((token, i) => { - const prevToken = tokens[i - 1]; - const nextToken = tokens[i + 1]; - - // if token is not an opening or closing paren token, do nothing - if (!astUtils.isOpeningParenToken(token) && !astUtils.isClosingParenToken(token)) { - return; - } - - // if token is an opening paren and is not followed by a required space - if (token.value === "(" && openerMissingSpace(token, nextToken)) { - context.report({ - node, - loc: token.loc, - messageId: "missingOpeningSpace", - fix(fixer) { - return fixer.insertTextAfter(token, " "); - } - }); - } - - // if token is an opening paren and is followed by a disallowed space - if (token.value === "(" && openerRejectsSpace(token, nextToken)) { - context.report({ - node, - loc: { start: token.loc.end, end: nextToken.loc.start }, - messageId: "rejectedOpeningSpace", - fix(fixer) { - return fixer.removeRange([token.range[1], nextToken.range[0]]); - } - }); - } - - // if token is a closing paren and is not preceded by a required space - if (token.value === ")" && closerMissingSpace(prevToken, token)) { - context.report({ - node, - loc: token.loc, - messageId: "missingClosingSpace", - fix(fixer) { - return fixer.insertTextBefore(token, " "); - } - }); - } - - // if token is a closing paren and is preceded by a disallowed space - if (token.value === ")" && closerRejectsSpace(prevToken, token)) { - context.report({ - node, - loc: { start: prevToken.loc.end, end: token.loc.start }, - messageId: "rejectedClosingSpace", - fix(fixer) { - return fixer.removeRange([prevToken.range[1], token.range[0]]); - } - }); - } - }); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "space-in-parens", + url: "https://eslint.style/rules/js/space-in-parens", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Enforce consistent spacing inside parentheses", + recommended: false, + url: "https://eslint.org/docs/latest/rules/space-in-parens", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + enum: ["{}", "[]", "()", "empty"], + }, + uniqueItems: true, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missingOpeningSpace: "There must be a space after this paren.", + missingClosingSpace: "There must be a space before this paren.", + rejectedOpeningSpace: "There should be no space after this paren.", + rejectedClosingSpace: "There should be no space before this paren.", + }, + }, + + create(context) { + const ALWAYS = context.options[0] === "always", + exceptionsArrayOptions = + (context.options[1] && context.options[1].exceptions) || [], + options = {}; + + let exceptions; + + if (exceptionsArrayOptions.length) { + options.braceException = exceptionsArrayOptions.includes("{}"); + options.bracketException = exceptionsArrayOptions.includes("[]"); + options.parenException = exceptionsArrayOptions.includes("()"); + options.empty = exceptionsArrayOptions.includes("empty"); + } + + /** + * Produces an object with the opener and closer exception values + * @returns {Object} `openers` and `closers` exception values + * @private + */ + function getExceptions() { + const openers = [], + closers = []; + + if (options.braceException) { + openers.push("{"); + closers.push("}"); + } + + if (options.bracketException) { + openers.push("["); + closers.push("]"); + } + + if (options.parenException) { + openers.push("("); + closers.push(")"); + } + + if (options.empty) { + openers.push(")"); + closers.push("("); + } + + return { + openers, + closers, + }; + } + + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + const sourceCode = context.sourceCode; + + /** + * Determines if a token is one of the exceptions for the opener paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the opener paren + */ + function isOpenerException(token) { + return exceptions.openers.includes(token.value); + } + + /** + * Determines if a token is one of the exceptions for the closer paren + * @param {Object} token The token to check + * @returns {boolean} True if the token is one of the exceptions for the closer paren + */ + function isCloserException(token) { + return exceptions.closers.includes(token.value); + } + + /** + * Determines if an opening paren is immediately followed by a required space + * @param {Object} openingParenToken The paren token + * @param {Object} tokenAfterOpeningParen The token after it + * @returns {boolean} True if the opening paren is missing a required space + */ + function openerMissingSpace(openingParenToken, tokenAfterOpeningParen) { + if ( + sourceCode.isSpaceBetweenTokens( + openingParenToken, + tokenAfterOpeningParen, + ) + ) { + return false; + } + + if ( + !options.empty && + astUtils.isClosingParenToken(tokenAfterOpeningParen) + ) { + return false; + } + + if (ALWAYS) { + return !isOpenerException(tokenAfterOpeningParen); + } + return isOpenerException(tokenAfterOpeningParen); + } + + /** + * Determines if an opening paren is immediately followed by a disallowed space + * @param {Object} openingParenToken The paren token + * @param {Object} tokenAfterOpeningParen The token after it + * @returns {boolean} True if the opening paren has a disallowed space + */ + function openerRejectsSpace(openingParenToken, tokenAfterOpeningParen) { + if ( + !astUtils.isTokenOnSameLine( + openingParenToken, + tokenAfterOpeningParen, + ) + ) { + return false; + } + + if (tokenAfterOpeningParen.type === "Line") { + return false; + } + + if ( + !sourceCode.isSpaceBetweenTokens( + openingParenToken, + tokenAfterOpeningParen, + ) + ) { + return false; + } + + if (ALWAYS) { + return isOpenerException(tokenAfterOpeningParen); + } + return !isOpenerException(tokenAfterOpeningParen); + } + + /** + * Determines if a closing paren is immediately preceded by a required space + * @param {Object} tokenBeforeClosingParen The token before the paren + * @param {Object} closingParenToken The paren token + * @returns {boolean} True if the closing paren is missing a required space + */ + function closerMissingSpace( + tokenBeforeClosingParen, + closingParenToken, + ) { + if ( + sourceCode.isSpaceBetweenTokens( + tokenBeforeClosingParen, + closingParenToken, + ) + ) { + return false; + } + + if ( + !options.empty && + astUtils.isOpeningParenToken(tokenBeforeClosingParen) + ) { + return false; + } + + if (ALWAYS) { + return !isCloserException(tokenBeforeClosingParen); + } + return isCloserException(tokenBeforeClosingParen); + } + + /** + * Determines if a closer paren is immediately preceded by a disallowed space + * @param {Object} tokenBeforeClosingParen The token before the paren + * @param {Object} closingParenToken The paren token + * @returns {boolean} True if the closing paren has a disallowed space + */ + function closerRejectsSpace( + tokenBeforeClosingParen, + closingParenToken, + ) { + if ( + !astUtils.isTokenOnSameLine( + tokenBeforeClosingParen, + closingParenToken, + ) + ) { + return false; + } + + if ( + !sourceCode.isSpaceBetweenTokens( + tokenBeforeClosingParen, + closingParenToken, + ) + ) { + return false; + } + + if (ALWAYS) { + return isCloserException(tokenBeforeClosingParen); + } + return !isCloserException(tokenBeforeClosingParen); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program: function checkParenSpaces(node) { + exceptions = getExceptions(); + const tokens = sourceCode.tokensAndComments; + + tokens.forEach((token, i) => { + const prevToken = tokens[i - 1]; + const nextToken = tokens[i + 1]; + + // if token is not an opening or closing paren token, do nothing + if ( + !astUtils.isOpeningParenToken(token) && + !astUtils.isClosingParenToken(token) + ) { + return; + } + + // if token is an opening paren and is not followed by a required space + if ( + token.value === "(" && + openerMissingSpace(token, nextToken) + ) { + context.report({ + node, + loc: token.loc, + messageId: "missingOpeningSpace", + fix(fixer) { + return fixer.insertTextAfter(token, " "); + }, + }); + } + + // if token is an opening paren and is followed by a disallowed space + if ( + token.value === "(" && + openerRejectsSpace(token, nextToken) + ) { + context.report({ + node, + loc: { + start: token.loc.end, + end: nextToken.loc.start, + }, + messageId: "rejectedOpeningSpace", + fix(fixer) { + return fixer.removeRange([ + token.range[1], + nextToken.range[0], + ]); + }, + }); + } + + // if token is a closing paren and is not preceded by a required space + if ( + token.value === ")" && + closerMissingSpace(prevToken, token) + ) { + context.report({ + node, + loc: token.loc, + messageId: "missingClosingSpace", + fix(fixer) { + return fixer.insertTextBefore(token, " "); + }, + }); + } + + // if token is a closing paren and is preceded by a disallowed space + if ( + token.value === ")" && + closerRejectsSpace(prevToken, token) + ) { + context.report({ + node, + loc: { + start: prevToken.loc.end, + end: token.loc.start, + }, + messageId: "rejectedClosingSpace", + fix(fixer) { + return fixer.removeRange([ + prevToken.range[1], + token.range[0], + ]); + }, + }); + } + }); + }, + }; + }, }; diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js index b8ac4a01cef8..055787c8c85a 100644 --- a/lib/rules/space-infix-ops.js +++ b/lib/rules/space-infix-ops.js @@ -13,204 +13,240 @@ const { isEqToken } = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "space-infix-ops", - url: "https://eslint.style/rules/js/space-infix-ops" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require spacing around infix operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/space-infix-ops" - }, - - fixable: "whitespace", - - schema: [ - { - type: "object", - properties: { - int32Hint: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - messages: { - missingSpace: "Operator '{{operator}}' must be spaced." - } - }, - - create(context) { - const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false; - const sourceCode = context.sourceCode; - - /** - * Returns the first token which violates the rule - * @param {ASTNode} left The left node of the main node - * @param {ASTNode} right The right node of the main node - * @param {string} op The operator of the main node - * @returns {Object} The violator token or null - * @private - */ - function getFirstNonSpacedToken(left, right, op) { - const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op); - const prev = sourceCode.getTokenBefore(operator); - const next = sourceCode.getTokenAfter(operator); - - if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) { - return operator; - } - - return null; - } - - /** - * Reports an AST node as a rule violation - * @param {ASTNode} mainNode The node to report - * @param {Object} culpritToken The token which has a problem - * @returns {void} - * @private - */ - function report(mainNode, culpritToken) { - context.report({ - node: mainNode, - loc: culpritToken.loc, - messageId: "missingSpace", - data: { - operator: culpritToken.value - }, - fix(fixer) { - const previousToken = sourceCode.getTokenBefore(culpritToken); - const afterToken = sourceCode.getTokenAfter(culpritToken); - let fixString = ""; - - if (culpritToken.range[0] - previousToken.range[1] === 0) { - fixString = " "; - } - - fixString += culpritToken.value; - - if (afterToken.range[0] - culpritToken.range[1] === 0) { - fixString += " "; - } - - return fixer.replaceText(culpritToken, fixString); - } - }); - } - - /** - * Check if the node is binary then report - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkBinary(node) { - const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left; - const rightNode = node.right; - - // search for = in AssignmentPattern nodes - const operator = node.operator || "="; - - const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator); - - if (nonSpacedNode) { - if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) { - report(node, nonSpacedNode); - } - } - } - - /** - * Check if the node is conditional - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkConditional(node) { - const nonSpacedConsequentNode = getFirstNonSpacedToken(node.test, node.consequent, "?"); - const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":"); - - if (nonSpacedConsequentNode) { - report(node, nonSpacedConsequentNode); - } - - if (nonSpacedAlternateNode) { - report(node, nonSpacedAlternateNode); - } - } - - /** - * Check if the node is a variable - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkVar(node) { - const leftNode = (node.id.typeAnnotation) ? node.id.typeAnnotation : node.id; - const rightNode = node.init; - - if (rightNode) { - const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "="); - - if (nonSpacedNode) { - report(node, nonSpacedNode); - } - } - } - - return { - AssignmentExpression: checkBinary, - AssignmentPattern: checkBinary, - BinaryExpression: checkBinary, - LogicalExpression: checkBinary, - ConditionalExpression: checkConditional, - VariableDeclarator: checkVar, - - PropertyDefinition(node) { - if (!node.value) { - return; - } - - /* - * Because of computed properties and type annotations, some - * tokens may exist between `node.key` and `=`. - * Therefore, find the `=` from the right. - */ - const operatorToken = sourceCode.getTokenBefore(node.value, isEqToken); - const leftToken = sourceCode.getTokenBefore(operatorToken); - const rightToken = sourceCode.getTokenAfter(operatorToken); - - if ( - !sourceCode.isSpaceBetweenTokens(leftToken, operatorToken) || - !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken) - ) { - report(node, operatorToken); - } - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "space-infix-ops", + url: "https://eslint.style/rules/js/space-infix-ops", + }, + }, + ], + }, + type: "layout", + + docs: { + description: "Require spacing around infix operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/space-infix-ops", + }, + + fixable: "whitespace", + + schema: [ + { + type: "object", + properties: { + int32Hint: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + missingSpace: "Operator '{{operator}}' must be spaced.", + }, + }, + + create(context) { + const int32Hint = context.options[0] + ? context.options[0].int32Hint === true + : false; + const sourceCode = context.sourceCode; + + /** + * Returns the first token which violates the rule + * @param {ASTNode} left The left node of the main node + * @param {ASTNode} right The right node of the main node + * @param {string} op The operator of the main node + * @returns {Object} The violator token or null + * @private + */ + function getFirstNonSpacedToken(left, right, op) { + const operator = sourceCode.getFirstTokenBetween( + left, + right, + token => token.value === op, + ); + const prev = sourceCode.getTokenBefore(operator); + const next = sourceCode.getTokenAfter(operator); + + if ( + !sourceCode.isSpaceBetweenTokens(prev, operator) || + !sourceCode.isSpaceBetweenTokens(operator, next) + ) { + return operator; + } + + return null; + } + + /** + * Reports an AST node as a rule violation + * @param {ASTNode} mainNode The node to report + * @param {Object} culpritToken The token which has a problem + * @returns {void} + * @private + */ + function report(mainNode, culpritToken) { + context.report({ + node: mainNode, + loc: culpritToken.loc, + messageId: "missingSpace", + data: { + operator: culpritToken.value, + }, + fix(fixer) { + const previousToken = + sourceCode.getTokenBefore(culpritToken); + const afterToken = sourceCode.getTokenAfter(culpritToken); + let fixString = ""; + + if (culpritToken.range[0] - previousToken.range[1] === 0) { + fixString = " "; + } + + fixString += culpritToken.value; + + if (afterToken.range[0] - culpritToken.range[1] === 0) { + fixString += " "; + } + + return fixer.replaceText(culpritToken, fixString); + }, + }); + } + + /** + * Check if the node is binary then report + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkBinary(node) { + const leftNode = node.left.typeAnnotation + ? node.left.typeAnnotation + : node.left; + const rightNode = node.right; + + // search for = in AssignmentPattern nodes + const operator = node.operator || "="; + + const nonSpacedNode = getFirstNonSpacedToken( + leftNode, + rightNode, + operator, + ); + + if (nonSpacedNode) { + if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) { + report(node, nonSpacedNode); + } + } + } + + /** + * Check if the node is conditional + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkConditional(node) { + const nonSpacedConsequentNode = getFirstNonSpacedToken( + node.test, + node.consequent, + "?", + ); + const nonSpacedAlternateNode = getFirstNonSpacedToken( + node.consequent, + node.alternate, + ":", + ); + + if (nonSpacedConsequentNode) { + report(node, nonSpacedConsequentNode); + } + + if (nonSpacedAlternateNode) { + report(node, nonSpacedAlternateNode); + } + } + + /** + * Check if the node is a variable + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkVar(node) { + const leftNode = node.id.typeAnnotation + ? node.id.typeAnnotation + : node.id; + const rightNode = node.init; + + if (rightNode) { + const nonSpacedNode = getFirstNonSpacedToken( + leftNode, + rightNode, + "=", + ); + + if (nonSpacedNode) { + report(node, nonSpacedNode); + } + } + } + + return { + AssignmentExpression: checkBinary, + AssignmentPattern: checkBinary, + BinaryExpression: checkBinary, + LogicalExpression: checkBinary, + ConditionalExpression: checkConditional, + VariableDeclarator: checkVar, + + PropertyDefinition(node) { + if (!node.value) { + return; + } + + /* + * Because of computed properties and type annotations, some + * tokens may exist between `node.key` and `=`. + * Therefore, find the `=` from the right. + */ + const operatorToken = sourceCode.getTokenBefore( + node.value, + isEqToken, + ); + const leftToken = sourceCode.getTokenBefore(operatorToken); + const rightToken = sourceCode.getTokenAfter(operatorToken); + + if ( + !sourceCode.isSpaceBetweenTokens( + leftToken, + operatorToken, + ) || + !sourceCode.isSpaceBetweenTokens(operatorToken, rightToken) + ) { + report(node, operatorToken); + } + }, + }; + }, }; diff --git a/lib/rules/space-unary-ops.js b/lib/rules/space-unary-ops.js index 876fb88abbec..36f3365eaa66 100644 --- a/lib/rules/space-unary-ops.js +++ b/lib/rules/space-unary-ops.js @@ -17,326 +17,384 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "space-unary-ops", - url: "https://eslint.style/rules/js/space-unary-ops" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "space-unary-ops", + url: "https://eslint.style/rules/js/space-unary-ops", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce consistent spacing before or after unary operators", - recommended: false, - url: "https://eslint.org/docs/latest/rules/space-unary-ops" - }, + docs: { + description: + "Enforce consistent spacing before or after unary operators", + recommended: false, + url: "https://eslint.org/docs/latest/rules/space-unary-ops", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - type: "object", - properties: { - words: { - type: "boolean", - default: true - }, - nonwords: { - type: "boolean", - default: false - }, - overrides: { - type: "object", - additionalProperties: { - type: "boolean" - } - } - }, - additionalProperties: false - } - ], - messages: { - unexpectedBefore: "Unexpected space before unary operator '{{operator}}'.", - unexpectedAfter: "Unexpected space after unary operator '{{operator}}'.", - unexpectedAfterWord: "Unexpected space after unary word operator '{{word}}'.", - wordOperator: "Unary word operator '{{word}}' must be followed by whitespace.", - operator: "Unary operator '{{operator}}' must be followed by whitespace.", - beforeUnaryExpressions: "Space is required before unary expressions '{{token}}'." - } - }, + schema: [ + { + type: "object", + properties: { + words: { + type: "boolean", + default: true, + }, + nonwords: { + type: "boolean", + default: false, + }, + overrides: { + type: "object", + additionalProperties: { + type: "boolean", + }, + }, + }, + additionalProperties: false, + }, + ], + messages: { + unexpectedBefore: + "Unexpected space before unary operator '{{operator}}'.", + unexpectedAfter: + "Unexpected space after unary operator '{{operator}}'.", + unexpectedAfterWord: + "Unexpected space after unary word operator '{{word}}'.", + wordOperator: + "Unary word operator '{{word}}' must be followed by whitespace.", + operator: + "Unary operator '{{operator}}' must be followed by whitespace.", + beforeUnaryExpressions: + "Space is required before unary expressions '{{token}}'.", + }, + }, - create(context) { - const options = context.options[0] || { words: true, nonwords: false }; + create(context) { + const options = context.options[0] || { words: true, nonwords: false }; - const sourceCode = context.sourceCode; + const sourceCode = context.sourceCode; - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- - /** - * Check if the node is the first "!" in a "!!" convert to Boolean expression - * @param {ASTnode} node AST node - * @returns {boolean} Whether or not the node is first "!" in "!!" - */ - function isFirstBangInBangBangExpression(node) { - return node && node.type === "UnaryExpression" && node.argument.operator === "!" && - node.argument && node.argument.type === "UnaryExpression" && node.argument.operator === "!"; - } + /** + * Check if the node is the first "!" in a "!!" convert to Boolean expression + * @param {ASTnode} node AST node + * @returns {boolean} Whether or not the node is first "!" in "!!" + */ + function isFirstBangInBangBangExpression(node) { + return ( + node && + node.type === "UnaryExpression" && + node.argument.operator === "!" && + node.argument && + node.argument.type === "UnaryExpression" && + node.argument.operator === "!" + ); + } - /** - * Checks if an override exists for a given operator. - * @param {string} operator Operator - * @returns {boolean} Whether or not an override has been provided for the operator - */ - function overrideExistsForOperator(operator) { - return options.overrides && Object.hasOwn(options.overrides, operator); - } + /** + * Checks if an override exists for a given operator. + * @param {string} operator Operator + * @returns {boolean} Whether or not an override has been provided for the operator + */ + function overrideExistsForOperator(operator) { + return ( + options.overrides && Object.hasOwn(options.overrides, operator) + ); + } - /** - * Gets the value that the override was set to for this operator - * @param {string} operator Operator - * @returns {boolean} Whether or not an override enforces a space with this operator - */ - function overrideEnforcesSpaces(operator) { - return options.overrides[operator]; - } + /** + * Gets the value that the override was set to for this operator + * @param {string} operator Operator + * @returns {boolean} Whether or not an override enforces a space with this operator + */ + function overrideEnforcesSpaces(operator) { + return options.overrides[operator]; + } - /** - * Verify Unary Word Operator has spaces after the word operator - * @param {ASTnode} node AST node - * @param {Object} firstToken first token from the AST node - * @param {Object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function verifyWordHasSpaces(node, firstToken, secondToken, word) { - if (secondToken.range[0] === firstToken.range[1]) { - context.report({ - node, - messageId: "wordOperator", - data: { - word - }, - fix(fixer) { - return fixer.insertTextAfter(firstToken, " "); - } - }); - } - } + /** + * Verify Unary Word Operator has spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordHasSpaces(node, firstToken, secondToken, word) { + if (secondToken.range[0] === firstToken.range[1]) { + context.report({ + node, + messageId: "wordOperator", + data: { + word, + }, + fix(fixer) { + return fixer.insertTextAfter(firstToken, " "); + }, + }); + } + } - /** - * Verify Unary Word Operator doesn't have spaces after the word operator - * @param {ASTnode} node AST node - * @param {Object} firstToken first token from the AST node - * @param {Object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word) { - if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { - if (secondToken.range[0] > firstToken.range[1]) { - context.report({ - node, - messageId: "unexpectedAfterWord", - data: { - word - }, - fix(fixer) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); - } - }); - } - } - } + /** + * Verify Unary Word Operator doesn't have spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function verifyWordDoesntHaveSpaces( + node, + firstToken, + secondToken, + word, + ) { + if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedAfterWord", + data: { + word, + }, + fix(fixer) { + return fixer.removeRange([ + firstToken.range[1], + secondToken.range[0], + ]); + }, + }); + } + } + } - /** - * Check Unary Word Operators for spaces after the word operator - * @param {ASTnode} node AST node - * @param {Object} firstToken first token from the AST node - * @param {Object} secondToken second token from the AST node - * @param {string} word The word to be used for reporting - * @returns {void} - */ - function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) { - if (overrideExistsForOperator(word)) { - if (overrideEnforcesSpaces(word)) { - verifyWordHasSpaces(node, firstToken, secondToken, word); - } else { - verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); - } - } else if (options.words) { - verifyWordHasSpaces(node, firstToken, secondToken, word); - } else { - verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); - } - } + /** + * Check Unary Word Operators for spaces after the word operator + * @param {ASTnode} node AST node + * @param {Object} firstToken first token from the AST node + * @param {Object} secondToken second token from the AST node + * @param {string} word The word to be used for reporting + * @returns {void} + */ + function checkUnaryWordOperatorForSpaces( + node, + firstToken, + secondToken, + word, + ) { + if (overrideExistsForOperator(word)) { + if (overrideEnforcesSpaces(word)) { + verifyWordHasSpaces(node, firstToken, secondToken, word); + } else { + verifyWordDoesntHaveSpaces( + node, + firstToken, + secondToken, + word, + ); + } + } else if (options.words) { + verifyWordHasSpaces(node, firstToken, secondToken, word); + } else { + verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word); + } + } - /** - * Verifies YieldExpressions satisfy spacing requirements - * @param {ASTnode} node AST node - * @returns {void} - */ - function checkForSpacesAfterYield(node) { - const tokens = sourceCode.getFirstTokens(node, 3), - word = "yield"; + /** + * Verifies YieldExpressions satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpacesAfterYield(node) { + const tokens = sourceCode.getFirstTokens(node, 3), + word = "yield"; - if (!node.argument || node.delegate) { - return; - } + if (!node.argument || node.delegate) { + return; + } - checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); - } + checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], word); + } - /** - * Verifies AwaitExpressions satisfy spacing requirements - * @param {ASTNode} node AwaitExpression AST node - * @returns {void} - */ - function checkForSpacesAfterAwait(node) { - const tokens = sourceCode.getFirstTokens(node, 3); + /** + * Verifies AwaitExpressions satisfy spacing requirements + * @param {ASTNode} node AwaitExpression AST node + * @returns {void} + */ + function checkForSpacesAfterAwait(node) { + const tokens = sourceCode.getFirstTokens(node, 3); - checkUnaryWordOperatorForSpaces(node, tokens[0], tokens[1], "await"); - } + checkUnaryWordOperatorForSpaces( + node, + tokens[0], + tokens[1], + "await", + ); + } - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator - * @param {ASTnode} node AST node - * @param {Object} firstToken First token in the expression - * @param {Object} secondToken Second token in the expression - * @returns {void} - */ - function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { - if (node.prefix) { - if (isFirstBangInBangBangExpression(node)) { - return; - } - if (firstToken.range[1] === secondToken.range[0]) { - context.report({ - node, - messageId: "operator", - data: { - operator: firstToken.value - }, - fix(fixer) { - return fixer.insertTextAfter(firstToken, " "); - } - }); - } - } else { - if (firstToken.range[1] === secondToken.range[0]) { - context.report({ - node, - messageId: "beforeUnaryExpressions", - data: { - token: secondToken.value - }, - fix(fixer) { - return fixer.insertTextBefore(secondToken, " "); - } - }); - } - } - } + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {Object} firstToken First token in the expression + * @param {Object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (isFirstBangInBangBangExpression(node)) { + return; + } + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node, + messageId: "operator", + data: { + operator: firstToken.value, + }, + fix(fixer) { + return fixer.insertTextAfter(firstToken, " "); + }, + }); + } + } else { + if (firstToken.range[1] === secondToken.range[0]) { + context.report({ + node, + messageId: "beforeUnaryExpressions", + data: { + token: secondToken.value, + }, + fix(fixer) { + return fixer.insertTextBefore(secondToken, " "); + }, + }); + } + } + } - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator - * @param {ASTnode} node AST node - * @param {Object} firstToken First token in the expression - * @param {Object} secondToken Second token in the expression - * @returns {void} - */ - function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { - if (node.prefix) { - if (secondToken.range[0] > firstToken.range[1]) { - context.report({ - node, - messageId: "unexpectedAfter", - data: { - operator: firstToken.value - }, - fix(fixer) { - if (astUtils.canTokensBeAdjacent(firstToken, secondToken)) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); - } - return null; - } - }); - } - } else { - if (secondToken.range[0] > firstToken.range[1]) { - context.report({ - node, - messageId: "unexpectedBefore", - data: { - operator: secondToken.value - }, - fix(fixer) { - return fixer.removeRange([firstToken.range[1], secondToken.range[0]]); - } - }); - } - } - } + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression don't have spaces before or after the operator + * @param {ASTnode} node AST node + * @param {Object} firstToken First token in the expression + * @param {Object} secondToken Second token in the expression + * @returns {void} + */ + function verifyNonWordsDontHaveSpaces(node, firstToken, secondToken) { + if (node.prefix) { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedAfter", + data: { + operator: firstToken.value, + }, + fix(fixer) { + if ( + astUtils.canTokensBeAdjacent( + firstToken, + secondToken, + ) + ) { + return fixer.removeRange([ + firstToken.range[1], + secondToken.range[0], + ]); + } + return null; + }, + }); + } + } else { + if (secondToken.range[0] > firstToken.range[1]) { + context.report({ + node, + messageId: "unexpectedBefore", + data: { + operator: secondToken.value, + }, + fix(fixer) { + return fixer.removeRange([ + firstToken.range[1], + secondToken.range[0], + ]); + }, + }); + } + } + } - /** - * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements - * @param {ASTnode} node AST node - * @returns {void} - */ - function checkForSpaces(node) { - const tokens = node.type === "UpdateExpression" && !node.prefix - ? sourceCode.getLastTokens(node, 2) - : sourceCode.getFirstTokens(node, 2); - const firstToken = tokens[0]; - const secondToken = tokens[1]; + /** + * Verifies UnaryExpression, UpdateExpression and NewExpression satisfy spacing requirements + * @param {ASTnode} node AST node + * @returns {void} + */ + function checkForSpaces(node) { + const tokens = + node.type === "UpdateExpression" && !node.prefix + ? sourceCode.getLastTokens(node, 2) + : sourceCode.getFirstTokens(node, 2); + const firstToken = tokens[0]; + const secondToken = tokens[1]; - if ((node.type === "NewExpression" || node.prefix) && firstToken.type === "Keyword") { - checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, firstToken.value); - return; - } + if ( + (node.type === "NewExpression" || node.prefix) && + firstToken.type === "Keyword" + ) { + checkUnaryWordOperatorForSpaces( + node, + firstToken, + secondToken, + firstToken.value, + ); + return; + } - const operator = node.prefix ? tokens[0].value : tokens[1].value; + const operator = node.prefix ? tokens[0].value : tokens[1].value; - if (overrideExistsForOperator(operator)) { - if (overrideEnforcesSpaces(operator)) { - verifyNonWordsHaveSpaces(node, firstToken, secondToken); - } else { - verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); - } - } else if (options.nonwords) { - verifyNonWordsHaveSpaces(node, firstToken, secondToken); - } else { - verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); - } - } + if (overrideExistsForOperator(operator)) { + if (overrideEnforcesSpaces(operator)) { + verifyNonWordsHaveSpaces(node, firstToken, secondToken); + } else { + verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); + } + } else if (options.nonwords) { + verifyNonWordsHaveSpaces(node, firstToken, secondToken); + } else { + verifyNonWordsDontHaveSpaces(node, firstToken, secondToken); + } + } - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - return { - UnaryExpression: checkForSpaces, - UpdateExpression: checkForSpaces, - NewExpression: checkForSpaces, - YieldExpression: checkForSpacesAfterYield, - AwaitExpression: checkForSpacesAfterAwait - }; - - } + return { + UnaryExpression: checkForSpaces, + UpdateExpression: checkForSpaces, + NewExpression: checkForSpaces, + YieldExpression: checkForSpacesAfterYield, + AwaitExpression: checkForSpacesAfterAwait, + }; + }, }; diff --git a/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js index 3cdc5f977902..93a0ddddac01 100644 --- a/lib/rules/spaced-comment.js +++ b/lib/rules/spaced-comment.js @@ -18,7 +18,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {string} An escaped string. */ function escape(s) { - return `(?:${escapeRegExp(s)})`; + return `(?:${escapeRegExp(s)})`; } /** @@ -28,7 +28,7 @@ function escape(s) { * @returns {string} An escaped string. */ function escapeAndRepeat(s) { - return `${escape(s)}+`; + return `${escape(s)}+`; } /** @@ -38,13 +38,12 @@ function escapeAndRepeat(s) { * @returns {string[]} A marker list. */ function parseMarkersOption(markers) { + // `*` is a marker for JSDoc comments. + if (!markers.includes("*")) { + return markers.concat("*"); + } - // `*` is a marker for JSDoc comments. - if (!markers.includes("*")) { - return markers.concat("*"); - } - - return markers; + return markers; } /** @@ -56,39 +55,35 @@ function parseMarkersOption(markers) { * @returns {string} A regular expression string for exceptions. */ function createExceptionsPattern(exceptions) { - let pattern = ""; - - /* - * A space or an exception pattern sequence. - * [] ==> "\s" - * ["-"] ==> "(?:\s|\-+$)" - * ["-", "="] ==> "(?:\s|(?:\-+|=+)$)" - * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24) - */ - if (exceptions.length === 0) { - - // a space. - pattern += "\\s"; - } else { - - // a space or... - pattern += "(?:\\s|"; - - if (exceptions.length === 1) { - - // a sequence of the exception pattern. - pattern += escapeAndRepeat(exceptions[0]); - } else { - - // a sequence of one of the exception patterns. - pattern += "(?:"; - pattern += exceptions.map(escapeAndRepeat).join("|"); - pattern += ")"; - } - pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`; - } - - return pattern; + let pattern = ""; + + /* + * A space or an exception pattern sequence. + * [] ==> "\s" + * ["-"] ==> "(?:\s|\-+$)" + * ["-", "="] ==> "(?:\s|(?:\-+|=+)$)" + * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24) + */ + if (exceptions.length === 0) { + // a space. + pattern += "\\s"; + } else { + // a space or... + pattern += "(?:\\s|"; + + if (exceptions.length === 1) { + // a sequence of the exception pattern. + pattern += escapeAndRepeat(exceptions[0]); + } else { + // a sequence of one of the exception patterns. + pattern += "(?:"; + pattern += exceptions.map(escapeAndRepeat).join("|"); + pattern += ")"; + } + pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`; + } + + return pattern; } /** @@ -102,30 +97,28 @@ function createExceptionsPattern(exceptions) { * @returns {RegExp} A RegExp object for the beginning of a comment in `always` mode. */ function createAlwaysStylePattern(markers, exceptions) { - let pattern = "^"; - - /* - * A marker or nothing. - * ["*"] ==> "\*?" - * ["*", "!"] ==> "(?:\*|!)?" - * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F - */ - if (markers.length === 1) { - - // the marker. - pattern += escape(markers[0]); - } else { - - // one of markers. - pattern += "(?:"; - pattern += markers.map(escape).join("|"); - pattern += ")"; - } - - pattern += "?"; // or nothing. - pattern += createExceptionsPattern(exceptions); - - return new RegExp(pattern, "u"); + let pattern = "^"; + + /* + * A marker or nothing. + * ["*"] ==> "\*?" + * ["*", "!"] ==> "(?:\*|!)?" + * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F + */ + if (markers.length === 1) { + // the marker. + pattern += escape(markers[0]); + } else { + // one of markers. + pattern += "(?:"; + pattern += markers.map(escape).join("|"); + pattern += ")"; + } + + pattern += "?"; // or nothing. + pattern += createExceptionsPattern(exceptions); + + return new RegExp(pattern, "u"); } /** @@ -138,9 +131,9 @@ function createAlwaysStylePattern(markers, exceptions) { * @returns {RegExp} A RegExp object for `never` mode. */ function createNeverStylePattern(markers) { - const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`; + const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`; - return new RegExp(pattern, "u"); + return new RegExp(pattern, "u"); } //------------------------------------------------------------------------------ @@ -149,255 +142,306 @@ function createNeverStylePattern(markers) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "spaced-comment", - url: "https://eslint.style/rules/js/spaced-comment" - } - } - ] - }, - type: "suggestion", - - docs: { - description: "Enforce consistent spacing after the `//` or `/*` in a comment", - recommended: false, - url: "https://eslint.org/docs/latest/rules/spaced-comment" - }, - - fixable: "whitespace", - - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - type: "string" - } - }, - markers: { - type: "array", - items: { - type: "string" - } - }, - line: { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - type: "string" - } - }, - markers: { - type: "array", - items: { - type: "string" - } - } - }, - additionalProperties: false - }, - block: { - type: "object", - properties: { - exceptions: { - type: "array", - items: { - type: "string" - } - }, - markers: { - type: "array", - items: { - type: "string" - } - }, - balanced: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - }, - additionalProperties: false - } - ], - - messages: { - unexpectedSpaceAfterMarker: "Unexpected space or tab after marker ({{refChar}}) in comment.", - expectedExceptionAfter: "Expected exception block, space or tab after '{{refChar}}' in comment.", - unexpectedSpaceBefore: "Unexpected space or tab before '*/' in comment.", - unexpectedSpaceAfter: "Unexpected space or tab after '{{refChar}}' in comment.", - expectedSpaceBefore: "Expected space or tab before '*/' in comment.", - expectedSpaceAfter: "Expected space or tab after '{{refChar}}' in comment." - } - }, - - create(context) { - - const sourceCode = context.sourceCode; - - // Unless the first option is never, require a space - const requireSpace = context.options[0] !== "never"; - - /* - * Parse the second options. - * If markers don't include `"*"`, it's added automatically for JSDoc - * comments. - */ - const config = context.options[1] || {}; - const balanced = config.block && config.block.balanced; - - const styleRules = ["block", "line"].reduce((rule, type) => { - const markers = parseMarkersOption(config[type] && config[type].markers || config.markers || []); - const exceptions = config[type] && config[type].exceptions || config.exceptions || []; - const endNeverPattern = "[ \t]+$"; - - // Create RegExp object for valid patterns. - rule[type] = { - beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers), - endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"), - hasExceptions: exceptions.length > 0, - captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"), - markers: new Set(markers) - }; - - return rule; - }, {}); - - /** - * Reports a beginning spacing error with an appropriate message. - * @param {ASTNode} node A comment node to check. - * @param {string} messageId An error message to report. - * @param {Array} match An array of match results for markers. - * @param {string} refChar Character used for reference in the error message. - * @returns {void} - */ - function reportBegin(node, messageId, match, refChar) { - const type = node.type.toLowerCase(), - commentIdentifier = type === "block" ? "/*" : "//"; - - context.report({ - node, - fix(fixer) { - const start = node.range[0]; - let end = start + 2; - - if (requireSpace) { - if (match) { - end += match[0].length; - } - return fixer.insertTextAfterRange([start, end], " "); - } - end += match[0].length; - return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : "")); - - }, - messageId, - data: { refChar } - }); - } - - /** - * Reports an ending spacing error with an appropriate message. - * @param {ASTNode} node A comment node to check. - * @param {string} messageId An error message to report. - * @param {string} match An array of the matched whitespace characters. - * @returns {void} - */ - function reportEnd(node, messageId, match) { - context.report({ - node, - fix(fixer) { - if (requireSpace) { - return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " "); - } - const end = node.range[1] - 2, - start = end - match[0].length; - - return fixer.replaceTextRange([start, end], ""); - - }, - messageId - }); - } - - /** - * Reports a given comment if it's invalid. - * @param {ASTNode} node a comment node to check. - * @returns {void} - */ - function checkCommentForSpace(node) { - const type = node.type.toLowerCase(), - rule = styleRules[type], - commentIdentifier = type === "block" ? "/*" : "//"; - - // Ignores empty comments and comments that consist only of a marker. - if (node.value.length === 0 || rule.markers.has(node.value)) { - return; - } - - const beginMatch = rule.beginRegex.exec(node.value); - const endMatch = rule.endRegex.exec(node.value); - - // Checks. - if (requireSpace) { - if (!beginMatch) { - const hasMarker = rule.captureMarker.exec(node.value); - const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier; - - if (rule.hasExceptions) { - reportBegin(node, "expectedExceptionAfter", hasMarker, marker); - } else { - reportBegin(node, "expectedSpaceAfter", hasMarker, marker); - } - } - - if (balanced && type === "block" && !endMatch) { - reportEnd(node, "expectedSpaceBefore"); - } - } else { - if (beginMatch) { - if (!beginMatch[1]) { - reportBegin(node, "unexpectedSpaceAfter", beginMatch, commentIdentifier); - } else { - reportBegin(node, "unexpectedSpaceAfterMarker", beginMatch, beginMatch[1]); - } - } - - if (balanced && type === "block" && endMatch) { - reportEnd(node, "unexpectedSpaceBefore", endMatch); - } - } - } - - return { - Program() { - const comments = sourceCode.getAllComments(); - - comments.filter(token => token.type !== "Shebang").forEach(checkCommentForSpace); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "spaced-comment", + url: "https://eslint.style/rules/js/spaced-comment", + }, + }, + ], + }, + type: "suggestion", + + docs: { + description: + "Enforce consistent spacing after the `//` or `/*` in a comment", + recommended: false, + url: "https://eslint.org/docs/latest/rules/spaced-comment", + }, + + fixable: "whitespace", + + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string", + }, + }, + markers: { + type: "array", + items: { + type: "string", + }, + }, + line: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string", + }, + }, + markers: { + type: "array", + items: { + type: "string", + }, + }, + }, + additionalProperties: false, + }, + block: { + type: "object", + properties: { + exceptions: { + type: "array", + items: { + type: "string", + }, + }, + markers: { + type: "array", + items: { + type: "string", + }, + }, + balanced: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], + + messages: { + unexpectedSpaceAfterMarker: + "Unexpected space or tab after marker ({{refChar}}) in comment.", + expectedExceptionAfter: + "Expected exception block, space or tab after '{{refChar}}' in comment.", + unexpectedSpaceBefore: + "Unexpected space or tab before '*/' in comment.", + unexpectedSpaceAfter: + "Unexpected space or tab after '{{refChar}}' in comment.", + expectedSpaceBefore: + "Expected space or tab before '*/' in comment.", + expectedSpaceAfter: + "Expected space or tab after '{{refChar}}' in comment.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + // Unless the first option is never, require a space + const requireSpace = context.options[0] !== "never"; + + /* + * Parse the second options. + * If markers don't include `"*"`, it's added automatically for JSDoc + * comments. + */ + const config = context.options[1] || {}; + const balanced = config.block && config.block.balanced; + + const styleRules = ["block", "line"].reduce((rule, type) => { + const markers = parseMarkersOption( + (config[type] && config[type].markers) || config.markers || [], + ); + const exceptions = + (config[type] && config[type].exceptions) || + config.exceptions || + []; + const endNeverPattern = "[ \t]+$"; + + // Create RegExp object for valid patterns. + rule[type] = { + beginRegex: requireSpace + ? createAlwaysStylePattern(markers, exceptions) + : createNeverStylePattern(markers), + endRegex: + balanced && requireSpace + ? new RegExp( + `${createExceptionsPattern(exceptions)}$`, + "u", + ) + : new RegExp(endNeverPattern, "u"), + hasExceptions: exceptions.length > 0, + captureMarker: new RegExp( + `^(${markers.map(escape).join("|")})`, + "u", + ), + markers: new Set(markers), + }; + + return rule; + }, {}); + + /** + * Reports a beginning spacing error with an appropriate message. + * @param {ASTNode} node A comment node to check. + * @param {string} messageId An error message to report. + * @param {Array} match An array of match results for markers. + * @param {string} refChar Character used for reference in the error message. + * @returns {void} + */ + function reportBegin(node, messageId, match, refChar) { + const type = node.type.toLowerCase(), + commentIdentifier = type === "block" ? "/*" : "//"; + + context.report({ + node, + fix(fixer) { + const start = node.range[0]; + let end = start + 2; + + if (requireSpace) { + if (match) { + end += match[0].length; + } + return fixer.insertTextAfterRange([start, end], " "); + } + end += match[0].length; + return fixer.replaceTextRange( + [start, end], + commentIdentifier + (match[1] ? match[1] : ""), + ); + }, + messageId, + data: { refChar }, + }); + } + + /** + * Reports an ending spacing error with an appropriate message. + * @param {ASTNode} node A comment node to check. + * @param {string} messageId An error message to report. + * @param {string} match An array of the matched whitespace characters. + * @returns {void} + */ + function reportEnd(node, messageId, match) { + context.report({ + node, + fix(fixer) { + if (requireSpace) { + return fixer.insertTextAfterRange( + [node.range[0], node.range[1] - 2], + " ", + ); + } + const end = node.range[1] - 2, + start = end - match[0].length; + + return fixer.replaceTextRange([start, end], ""); + }, + messageId, + }); + } + + /** + * Reports a given comment if it's invalid. + * @param {ASTNode} node a comment node to check. + * @returns {void} + */ + function checkCommentForSpace(node) { + const type = node.type.toLowerCase(), + rule = styleRules[type], + commentIdentifier = type === "block" ? "/*" : "//"; + + // Ignores empty comments and comments that consist only of a marker. + if (node.value.length === 0 || rule.markers.has(node.value)) { + return; + } + + const beginMatch = rule.beginRegex.exec(node.value); + const endMatch = rule.endRegex.exec(node.value); + + // Checks. + if (requireSpace) { + if (!beginMatch) { + const hasMarker = rule.captureMarker.exec(node.value); + const marker = hasMarker + ? commentIdentifier + hasMarker[0] + : commentIdentifier; + + if (rule.hasExceptions) { + reportBegin( + node, + "expectedExceptionAfter", + hasMarker, + marker, + ); + } else { + reportBegin( + node, + "expectedSpaceAfter", + hasMarker, + marker, + ); + } + } + + if (balanced && type === "block" && !endMatch) { + reportEnd(node, "expectedSpaceBefore"); + } + } else { + if (beginMatch) { + if (!beginMatch[1]) { + reportBegin( + node, + "unexpectedSpaceAfter", + beginMatch, + commentIdentifier, + ); + } else { + reportBegin( + node, + "unexpectedSpaceAfterMarker", + beginMatch, + beginMatch[1], + ); + } + } + + if (balanced && type === "block" && endMatch) { + reportEnd(node, "unexpectedSpaceBefore", endMatch); + } + } + } + + return { + Program() { + const comments = sourceCode.getAllComments(); + + comments + .filter(token => token.type !== "Shebang") + .forEach(checkCommentForSpace); + }, + }; + }, }; diff --git a/lib/rules/strict.js b/lib/rules/strict.js index 198bf8521b74..f9ff2a216d39 100644 --- a/lib/rules/strict.js +++ b/lib/rules/strict.js @@ -22,23 +22,23 @@ const astUtils = require("./utils/ast-utils"); * @returns {ASTNode[]} All of the Use Strict Directives. */ function getUseStrictDirectives(statements) { - const directives = []; - - for (let i = 0; i < statements.length; i++) { - const statement = statements[i]; - - if ( - statement.type === "ExpressionStatement" && - statement.expression.type === "Literal" && - statement.expression.value === "use strict" - ) { - directives[i] = statement; - } else { - break; - } - } - - return directives; + const directives = []; + + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + + if ( + statement.type === "ExpressionStatement" && + statement.expression.type === "Literal" && + statement.expression.value === "use strict" + ) { + directives[i] = statement; + } else { + break; + } + } + + return directives; } /** @@ -47,7 +47,7 @@ function getUseStrictDirectives(statements) { * @returns {boolean} `true` if the node is an Identifier node. */ function isSimpleParameter(node) { - return node.type === "Identifier"; + return node.type === "Identifier"; } /** @@ -56,7 +56,7 @@ function isSimpleParameter(node) { * @returns {boolean} `true` if the every parameter is an Identifier node. */ function isSimpleParameterList(params) { - return params.every(isSimpleParameter); + return params.every(isSimpleParameter); } //------------------------------------------------------------------------------ @@ -65,214 +65,249 @@ function isSimpleParameterList(params) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - defaultOptions: ["safe"], - - docs: { - description: "Require or disallow strict mode directives", - recommended: false, - url: "https://eslint.org/docs/latest/rules/strict" - }, - - schema: [ - { - enum: ["never", "global", "function", "safe"] - } - ], - - fixable: "code", - messages: { - function: "Use the function form of 'use strict'.", - global: "Use the global form of 'use strict'.", - multiple: "Multiple 'use strict' directives.", - never: "Strict mode is not permitted.", - unnecessary: "Unnecessary 'use strict' directive.", - module: "'use strict' is unnecessary inside of modules.", - implied: "'use strict' is unnecessary when implied strict mode is enabled.", - unnecessaryInClasses: "'use strict' is unnecessary inside of classes.", - nonSimpleParameterList: "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.", - wrap: "Wrap {{name}} in a function with 'use strict' directive." - } - }, - - create(context) { - const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, - scopes = [], - classScopes = []; - let [mode] = context.options; - - if (ecmaFeatures.impliedStrict) { - mode = "implied"; - } else if (mode === "safe") { - mode = ecmaFeatures.globalReturn || context.languageOptions.sourceType === "commonjs" ? "global" : "function"; - } - - /** - * Determines whether a reported error should be fixed, depending on the error type. - * @param {string} errorType The type of error - * @returns {boolean} `true` if the reported error should be fixed - */ - function shouldFix(errorType) { - return errorType === "multiple" || errorType === "unnecessary" || errorType === "module" || errorType === "implied" || errorType === "unnecessaryInClasses"; - } - - /** - * Gets a fixer function to remove a given 'use strict' directive. - * @param {ASTNode} node The directive that should be removed - * @returns {Function} A fixer function - */ - function getFixFunction(node) { - return fixer => fixer.remove(node); - } - - /** - * Report a slice of an array of nodes with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} start Index to start from. - * @param {string} end Index to end before. - * @param {string} messageId Message to display. - * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) - * @returns {void} - */ - function reportSlice(nodes, start, end, messageId, fix) { - nodes.slice(start, end).forEach(node => { - context.report({ node, messageId, fix: fix ? getFixFunction(node) : null }); - }); - } - - /** - * Report all nodes in an array with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} messageId Message id to display. - * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) - * @returns {void} - */ - function reportAll(nodes, messageId, fix) { - reportSlice(nodes, 0, nodes.length, messageId, fix); - } - - /** - * Report all nodes in an array, except the first, with a given message. - * @param {ASTNode[]} nodes Nodes. - * @param {string} messageId Message id to display. - * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) - * @returns {void} - */ - function reportAllExceptFirst(nodes, messageId, fix) { - reportSlice(nodes, 1, nodes.length, messageId, fix); - } - - /** - * Entering a function in 'function' mode pushes a new nested scope onto the - * stack. The new scope is true if the nested function is strict mode code. - * @param {ASTNode} node The function declaration or expression. - * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. - * @returns {void} - */ - function enterFunctionInFunctionMode(node, useStrictDirectives) { - const isInClass = classScopes.length > 0, - isParentGlobal = scopes.length === 0 && classScopes.length === 0, - isParentStrict = scopes.length > 0 && scopes.at(-1), - isStrict = useStrictDirectives.length > 0; - - if (isStrict) { - if (!isSimpleParameterList(node.params)) { - context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); - } else if (isParentStrict) { - context.report({ node: useStrictDirectives[0], messageId: "unnecessary", fix: getFixFunction(useStrictDirectives[0]) }); - } else if (isInClass) { - context.report({ node: useStrictDirectives[0], messageId: "unnecessaryInClasses", fix: getFixFunction(useStrictDirectives[0]) }); - } - - reportAllExceptFirst(useStrictDirectives, "multiple", true); - } else if (isParentGlobal) { - if (isSimpleParameterList(node.params)) { - context.report({ node, messageId: "function" }); - } else { - context.report({ - node, - messageId: "wrap", - data: { name: astUtils.getFunctionNameWithKind(node) } - }); - } - } - - scopes.push(isParentStrict || isStrict); - } - - /** - * Exiting a function in 'function' mode pops its scope off the stack. - * @returns {void} - */ - function exitFunctionInFunctionMode() { - scopes.pop(); - } - - /** - * Enter a function and either: - * - Push a new nested scope onto the stack (in 'function' mode). - * - Report all the Use Strict Directives (in the other modes). - * @param {ASTNode} node The function declaration or expression. - * @returns {void} - */ - function enterFunction(node) { - const isBlock = node.body.type === "BlockStatement", - useStrictDirectives = isBlock - ? getUseStrictDirectives(node.body.body) : []; - - if (mode === "function") { - enterFunctionInFunctionMode(node, useStrictDirectives); - } else if (useStrictDirectives.length > 0) { - if (isSimpleParameterList(node.params)) { - reportAll(useStrictDirectives, mode, shouldFix(mode)); - } else { - context.report({ node: useStrictDirectives[0], messageId: "nonSimpleParameterList" }); - reportAllExceptFirst(useStrictDirectives, "multiple", true); - } - } - } - - const rule = { - Program(node) { - const useStrictDirectives = getUseStrictDirectives(node.body); - - if (node.sourceType === "module") { - mode = "module"; - } - - if (mode === "global") { - if (node.body.length > 0 && useStrictDirectives.length === 0) { - context.report({ node, messageId: "global" }); - } - reportAllExceptFirst(useStrictDirectives, "multiple", true); - } else { - reportAll(useStrictDirectives, mode, shouldFix(mode)); - } - }, - FunctionDeclaration: enterFunction, - FunctionExpression: enterFunction, - ArrowFunctionExpression: enterFunction - }; - - if (mode === "function") { - Object.assign(rule, { - - // Inside of class bodies are always strict mode. - ClassBody() { - classScopes.push(true); - }, - "ClassBody:exit"() { - classScopes.pop(); - }, - - "FunctionDeclaration:exit": exitFunctionInFunctionMode, - "FunctionExpression:exit": exitFunctionInFunctionMode, - "ArrowFunctionExpression:exit": exitFunctionInFunctionMode - }); - } - - return rule; - } + meta: { + type: "suggestion", + + defaultOptions: ["safe"], + + docs: { + description: "Require or disallow strict mode directives", + recommended: false, + url: "https://eslint.org/docs/latest/rules/strict", + }, + + schema: [ + { + enum: ["never", "global", "function", "safe"], + }, + ], + + fixable: "code", + messages: { + function: "Use the function form of 'use strict'.", + global: "Use the global form of 'use strict'.", + multiple: "Multiple 'use strict' directives.", + never: "Strict mode is not permitted.", + unnecessary: "Unnecessary 'use strict' directive.", + module: "'use strict' is unnecessary inside of modules.", + implied: + "'use strict' is unnecessary when implied strict mode is enabled.", + unnecessaryInClasses: + "'use strict' is unnecessary inside of classes.", + nonSimpleParameterList: + "'use strict' directive inside a function with non-simple parameter list throws a syntax error since ES2016.", + wrap: "Wrap {{name}} in a function with 'use strict' directive.", + }, + }, + + create(context) { + const ecmaFeatures = context.parserOptions.ecmaFeatures || {}, + scopes = [], + classScopes = []; + let [mode] = context.options; + + if (ecmaFeatures.impliedStrict) { + mode = "implied"; + } else if (mode === "safe") { + mode = + ecmaFeatures.globalReturn || + context.languageOptions.sourceType === "commonjs" + ? "global" + : "function"; + } + + /** + * Determines whether a reported error should be fixed, depending on the error type. + * @param {string} errorType The type of error + * @returns {boolean} `true` if the reported error should be fixed + */ + function shouldFix(errorType) { + return ( + errorType === "multiple" || + errorType === "unnecessary" || + errorType === "module" || + errorType === "implied" || + errorType === "unnecessaryInClasses" + ); + } + + /** + * Gets a fixer function to remove a given 'use strict' directive. + * @param {ASTNode} node The directive that should be removed + * @returns {Function} A fixer function + */ + function getFixFunction(node) { + return fixer => fixer.remove(node); + } + + /** + * Report a slice of an array of nodes with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} start Index to start from. + * @param {string} end Index to end before. + * @param {string} messageId Message to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportSlice(nodes, start, end, messageId, fix) { + nodes.slice(start, end).forEach(node => { + context.report({ + node, + messageId, + fix: fix ? getFixFunction(node) : null, + }); + }); + } + + /** + * Report all nodes in an array with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} messageId Message id to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportAll(nodes, messageId, fix) { + reportSlice(nodes, 0, nodes.length, messageId, fix); + } + + /** + * Report all nodes in an array, except the first, with a given message. + * @param {ASTNode[]} nodes Nodes. + * @param {string} messageId Message id to display. + * @param {boolean} fix `true` if the directive should be fixed (i.e. removed) + * @returns {void} + */ + function reportAllExceptFirst(nodes, messageId, fix) { + reportSlice(nodes, 1, nodes.length, messageId, fix); + } + + /** + * Entering a function in 'function' mode pushes a new nested scope onto the + * stack. The new scope is true if the nested function is strict mode code. + * @param {ASTNode} node The function declaration or expression. + * @param {ASTNode[]} useStrictDirectives The Use Strict Directives of the node. + * @returns {void} + */ + function enterFunctionInFunctionMode(node, useStrictDirectives) { + const isInClass = classScopes.length > 0, + isParentGlobal = + scopes.length === 0 && classScopes.length === 0, + isParentStrict = scopes.length > 0 && scopes.at(-1), + isStrict = useStrictDirectives.length > 0; + + if (isStrict) { + if (!isSimpleParameterList(node.params)) { + context.report({ + node: useStrictDirectives[0], + messageId: "nonSimpleParameterList", + }); + } else if (isParentStrict) { + context.report({ + node: useStrictDirectives[0], + messageId: "unnecessary", + fix: getFixFunction(useStrictDirectives[0]), + }); + } else if (isInClass) { + context.report({ + node: useStrictDirectives[0], + messageId: "unnecessaryInClasses", + fix: getFixFunction(useStrictDirectives[0]), + }); + } + + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } else if (isParentGlobal) { + if (isSimpleParameterList(node.params)) { + context.report({ node, messageId: "function" }); + } else { + context.report({ + node, + messageId: "wrap", + data: { name: astUtils.getFunctionNameWithKind(node) }, + }); + } + } + + scopes.push(isParentStrict || isStrict); + } + + /** + * Exiting a function in 'function' mode pops its scope off the stack. + * @returns {void} + */ + function exitFunctionInFunctionMode() { + scopes.pop(); + } + + /** + * Enter a function and either: + * - Push a new nested scope onto the stack (in 'function' mode). + * - Report all the Use Strict Directives (in the other modes). + * @param {ASTNode} node The function declaration or expression. + * @returns {void} + */ + function enterFunction(node) { + const isBlock = node.body.type === "BlockStatement", + useStrictDirectives = isBlock + ? getUseStrictDirectives(node.body.body) + : []; + + if (mode === "function") { + enterFunctionInFunctionMode(node, useStrictDirectives); + } else if (useStrictDirectives.length > 0) { + if (isSimpleParameterList(node.params)) { + reportAll(useStrictDirectives, mode, shouldFix(mode)); + } else { + context.report({ + node: useStrictDirectives[0], + messageId: "nonSimpleParameterList", + }); + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } + } + } + + const rule = { + Program(node) { + const useStrictDirectives = getUseStrictDirectives(node.body); + + if (node.sourceType === "module") { + mode = "module"; + } + + if (mode === "global") { + if ( + node.body.length > 0 && + useStrictDirectives.length === 0 + ) { + context.report({ node, messageId: "global" }); + } + reportAllExceptFirst(useStrictDirectives, "multiple", true); + } else { + reportAll(useStrictDirectives, mode, shouldFix(mode)); + } + }, + FunctionDeclaration: enterFunction, + FunctionExpression: enterFunction, + ArrowFunctionExpression: enterFunction, + }; + + if (mode === "function") { + Object.assign(rule, { + // Inside of class bodies are always strict mode. + ClassBody() { + classScopes.push(true); + }, + "ClassBody:exit"() { + classScopes.pop(); + }, + + "FunctionDeclaration:exit": exitFunctionInFunctionMode, + "FunctionExpression:exit": exitFunctionInFunctionMode, + "ArrowFunctionExpression:exit": exitFunctionInFunctionMode, + }); + } + + return rule; + }, }; diff --git a/lib/rules/switch-colon-spacing.js b/lib/rules/switch-colon-spacing.js index b482f2ea6ff7..02a28f0ef4e6 100644 --- a/lib/rules/switch-colon-spacing.js +++ b/lib/rules/switch-colon-spacing.js @@ -18,133 +18,141 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "switch-colon-spacing", - url: "https://eslint.style/rules/js/switch-colon-spacing" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "switch-colon-spacing", + url: "https://eslint.style/rules/js/switch-colon-spacing", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Enforce spacing around colons of switch statements", - recommended: false, - url: "https://eslint.org/docs/latest/rules/switch-colon-spacing" - }, + docs: { + description: "Enforce spacing around colons of switch statements", + recommended: false, + url: "https://eslint.org/docs/latest/rules/switch-colon-spacing", + }, - schema: [ - { - type: "object", - properties: { - before: { type: "boolean", default: false }, - after: { type: "boolean", default: true } - }, - additionalProperties: false - } - ], - fixable: "whitespace", - messages: { - expectedBefore: "Expected space(s) before this colon.", - expectedAfter: "Expected space(s) after this colon.", - unexpectedBefore: "Unexpected space(s) before this colon.", - unexpectedAfter: "Unexpected space(s) after this colon." - } - }, + schema: [ + { + type: "object", + properties: { + before: { type: "boolean", default: false }, + after: { type: "boolean", default: true }, + }, + additionalProperties: false, + }, + ], + fixable: "whitespace", + messages: { + expectedBefore: "Expected space(s) before this colon.", + expectedAfter: "Expected space(s) after this colon.", + unexpectedBefore: "Unexpected space(s) before this colon.", + unexpectedAfter: "Unexpected space(s) after this colon.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; - const options = context.options[0] || {}; - const beforeSpacing = options.before === true; // false by default - const afterSpacing = options.after !== false; // true by default + create(context) { + const sourceCode = context.sourceCode; + const options = context.options[0] || {}; + const beforeSpacing = options.before === true; // false by default + const afterSpacing = options.after !== false; // true by default - /** - * Check whether the spacing between the given 2 tokens is valid or not. - * @param {Token} left The left token to check. - * @param {Token} right The right token to check. - * @param {boolean} expected The expected spacing to check. `true` if there should be a space. - * @returns {boolean} `true` if the spacing between the tokens is valid. - */ - function isValidSpacing(left, right, expected) { - return ( - astUtils.isClosingBraceToken(right) || - !astUtils.isTokenOnSameLine(left, right) || - sourceCode.isSpaceBetweenTokens(left, right) === expected - ); - } + /** + * Check whether the spacing between the given 2 tokens is valid or not. + * @param {Token} left The left token to check. + * @param {Token} right The right token to check. + * @param {boolean} expected The expected spacing to check. `true` if there should be a space. + * @returns {boolean} `true` if the spacing between the tokens is valid. + */ + function isValidSpacing(left, right, expected) { + return ( + astUtils.isClosingBraceToken(right) || + !astUtils.isTokenOnSameLine(left, right) || + sourceCode.isSpaceBetweenTokens(left, right) === expected + ); + } - /** - * Check whether comments exist between the given 2 tokens. - * @param {Token} left The left token to check. - * @param {Token} right The right token to check. - * @returns {boolean} `true` if comments exist between the given 2 tokens. - */ - function commentsExistBetween(left, right) { - return sourceCode.getFirstTokenBetween( - left, - right, - { - includeComments: true, - filter: astUtils.isCommentToken - } - ) !== null; - } + /** + * Check whether comments exist between the given 2 tokens. + * @param {Token} left The left token to check. + * @param {Token} right The right token to check. + * @returns {boolean} `true` if comments exist between the given 2 tokens. + */ + function commentsExistBetween(left, right) { + return ( + sourceCode.getFirstTokenBetween(left, right, { + includeComments: true, + filter: astUtils.isCommentToken, + }) !== null + ); + } - /** - * Fix the spacing between the given 2 tokens. - * @param {RuleFixer} fixer The fixer to fix. - * @param {Token} left The left token of fix range. - * @param {Token} right The right token of fix range. - * @param {boolean} spacing The spacing style. `true` if there should be a space. - * @returns {Fix|null} The fix object. - */ - function fix(fixer, left, right, spacing) { - if (commentsExistBetween(left, right)) { - return null; - } - if (spacing) { - return fixer.insertTextAfter(left, " "); - } - return fixer.removeRange([left.range[1], right.range[0]]); - } + /** + * Fix the spacing between the given 2 tokens. + * @param {RuleFixer} fixer The fixer to fix. + * @param {Token} left The left token of fix range. + * @param {Token} right The right token of fix range. + * @param {boolean} spacing The spacing style. `true` if there should be a space. + * @returns {Fix|null} The fix object. + */ + function fix(fixer, left, right, spacing) { + if (commentsExistBetween(left, right)) { + return null; + } + if (spacing) { + return fixer.insertTextAfter(left, " "); + } + return fixer.removeRange([left.range[1], right.range[0]]); + } - return { - SwitchCase(node) { - const colonToken = astUtils.getSwitchCaseColonToken(node, sourceCode); - const beforeToken = sourceCode.getTokenBefore(colonToken); - const afterToken = sourceCode.getTokenAfter(colonToken); + return { + SwitchCase(node) { + const colonToken = astUtils.getSwitchCaseColonToken( + node, + sourceCode, + ); + const beforeToken = sourceCode.getTokenBefore(colonToken); + const afterToken = sourceCode.getTokenAfter(colonToken); - if (!isValidSpacing(beforeToken, colonToken, beforeSpacing)) { - context.report({ - node, - loc: colonToken.loc, - messageId: beforeSpacing ? "expectedBefore" : "unexpectedBefore", - fix: fixer => fix(fixer, beforeToken, colonToken, beforeSpacing) - }); - } - if (!isValidSpacing(colonToken, afterToken, afterSpacing)) { - context.report({ - node, - loc: colonToken.loc, - messageId: afterSpacing ? "expectedAfter" : "unexpectedAfter", - fix: fixer => fix(fixer, colonToken, afterToken, afterSpacing) - }); - } - } - }; - } + if (!isValidSpacing(beforeToken, colonToken, beforeSpacing)) { + context.report({ + node, + loc: colonToken.loc, + messageId: beforeSpacing + ? "expectedBefore" + : "unexpectedBefore", + fix: fixer => + fix(fixer, beforeToken, colonToken, beforeSpacing), + }); + } + if (!isValidSpacing(colonToken, afterToken, afterSpacing)) { + context.report({ + node, + loc: colonToken.loc, + messageId: afterSpacing + ? "expectedAfter" + : "unexpectedAfter", + fix: fixer => + fix(fixer, colonToken, afterToken, afterSpacing), + }); + } + }, + }; + }, }; diff --git a/lib/rules/symbol-description.js b/lib/rules/symbol-description.js index 4528f09cf6c6..7517a1048bf9 100644 --- a/lib/rules/symbol-description.js +++ b/lib/rules/symbol-description.js @@ -15,59 +15,56 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ - /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "Require symbol descriptions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/symbol-description" - }, - fixable: null, - schema: [], - messages: { - expected: "Expected Symbol to have a description." - } - }, - - create(context) { + meta: { + type: "suggestion", - const sourceCode = context.sourceCode; + docs: { + description: "Require symbol descriptions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/symbol-description", + }, + fixable: null, + schema: [], + messages: { + expected: "Expected Symbol to have a description.", + }, + }, - /** - * Reports if node does not conform the rule in case rule is set to - * report missing description - * @param {ASTNode} node A CallExpression node to check. - * @returns {void} - */ - function checkArgument(node) { - if (node.arguments.length === 0) { - context.report({ - node, - messageId: "expected" - }); - } - } + create(context) { + const sourceCode = context.sourceCode; - return { - "Program:exit"(node) { - const scope = sourceCode.getScope(node); - const variable = astUtils.getVariableByName(scope, "Symbol"); + /** + * Reports if node does not conform the rule in case rule is set to + * report missing description + * @param {ASTNode} node A CallExpression node to check. + * @returns {void} + */ + function checkArgument(node) { + if (node.arguments.length === 0) { + context.report({ + node, + messageId: "expected", + }); + } + } - if (variable && variable.defs.length === 0) { - variable.references.forEach(reference => { - const idNode = reference.identifier; + return { + "Program:exit"(node) { + const scope = sourceCode.getScope(node); + const variable = astUtils.getVariableByName(scope, "Symbol"); - if (astUtils.isCallee(idNode)) { - checkArgument(idNode.parent); - } - }); - } - } - }; + if (variable && variable.defs.length === 0) { + variable.references.forEach(reference => { + const idNode = reference.identifier; - } + if (astUtils.isCallee(idNode)) { + checkArgument(idNode.parent); + } + }); + } + }, + }; + }, }; diff --git a/lib/rules/template-curly-spacing.js b/lib/rules/template-curly-spacing.js index 2112efa52a2a..e21dcb5c12f0 100644 --- a/lib/rules/template-curly-spacing.js +++ b/lib/rules/template-curly-spacing.js @@ -18,145 +18,151 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "template-curly-spacing", - url: "https://eslint.style/rules/js/template-curly-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow spacing around embedded expressions of template strings", - recommended: false, - url: "https://eslint.org/docs/latest/rules/template-curly-spacing" - }, - - fixable: "whitespace", - - schema: [ - { enum: ["always", "never"] } - ], - messages: { - expectedBefore: "Expected space(s) before '}'.", - expectedAfter: "Expected space(s) after '${'.", - unexpectedBefore: "Unexpected space(s) before '}'.", - unexpectedAfter: "Unexpected space(s) after '${'." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - const always = context.options[0] === "always"; - - /** - * Checks spacing before `}` of a given token. - * @param {Token} token A token to check. This is a Template token. - * @returns {void} - */ - function checkSpacingBefore(token) { - if (!token.value.startsWith("}")) { - return; // starts with a backtick, this is the first template element in the template literal - } - - const prevToken = sourceCode.getTokenBefore(token, { includeComments: true }), - hasSpace = sourceCode.isSpaceBetween(prevToken, token); - - if (!astUtils.isTokenOnSameLine(prevToken, token)) { - return; - } - - if (always && !hasSpace) { - context.report({ - loc: { - start: token.loc.start, - end: { - line: token.loc.start.line, - column: token.loc.start.column + 1 - } - }, - messageId: "expectedBefore", - fix: fixer => fixer.insertTextBefore(token, " ") - }); - } - - if (!always && hasSpace) { - context.report({ - loc: { - start: prevToken.loc.end, - end: token.loc.start - }, - messageId: "unexpectedBefore", - fix: fixer => fixer.removeRange([prevToken.range[1], token.range[0]]) - }); - } - } - - /** - * Checks spacing after `${` of a given token. - * @param {Token} token A token to check. This is a Template token. - * @returns {void} - */ - function checkSpacingAfter(token) { - if (!token.value.endsWith("${")) { - return; // ends with a backtick, this is the last template element in the template literal - } - - const nextToken = sourceCode.getTokenAfter(token, { includeComments: true }), - hasSpace = sourceCode.isSpaceBetween(token, nextToken); - - if (!astUtils.isTokenOnSameLine(token, nextToken)) { - return; - } - - if (always && !hasSpace) { - context.report({ - loc: { - start: { - line: token.loc.end.line, - column: token.loc.end.column - 2 - }, - end: token.loc.end - }, - messageId: "expectedAfter", - fix: fixer => fixer.insertTextAfter(token, " ") - }); - } - - if (!always && hasSpace) { - context.report({ - loc: { - start: token.loc.end, - end: nextToken.loc.start - }, - messageId: "unexpectedAfter", - fix: fixer => fixer.removeRange([token.range[1], nextToken.range[0]]) - }); - } - } - - return { - TemplateElement(node) { - const token = sourceCode.getFirstToken(node); - - checkSpacingBefore(token); - checkSpacingAfter(token); - } - }; - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "template-curly-spacing", + url: "https://eslint.style/rules/js/template-curly-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Require or disallow spacing around embedded expressions of template strings", + recommended: false, + url: "https://eslint.org/docs/latest/rules/template-curly-spacing", + }, + + fixable: "whitespace", + + schema: [{ enum: ["always", "never"] }], + messages: { + expectedBefore: "Expected space(s) before '}'.", + expectedAfter: "Expected space(s) after '${'.", + unexpectedBefore: "Unexpected space(s) before '}'.", + unexpectedAfter: "Unexpected space(s) after '${'.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + const always = context.options[0] === "always"; + + /** + * Checks spacing before `}` of a given token. + * @param {Token} token A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingBefore(token) { + if (!token.value.startsWith("}")) { + return; // starts with a backtick, this is the first template element in the template literal + } + + const prevToken = sourceCode.getTokenBefore(token, { + includeComments: true, + }), + hasSpace = sourceCode.isSpaceBetween(prevToken, token); + + if (!astUtils.isTokenOnSameLine(prevToken, token)) { + return; + } + + if (always && !hasSpace) { + context.report({ + loc: { + start: token.loc.start, + end: { + line: token.loc.start.line, + column: token.loc.start.column + 1, + }, + }, + messageId: "expectedBefore", + fix: fixer => fixer.insertTextBefore(token, " "), + }); + } + + if (!always && hasSpace) { + context.report({ + loc: { + start: prevToken.loc.end, + end: token.loc.start, + }, + messageId: "unexpectedBefore", + fix: fixer => + fixer.removeRange([prevToken.range[1], token.range[0]]), + }); + } + } + + /** + * Checks spacing after `${` of a given token. + * @param {Token} token A token to check. This is a Template token. + * @returns {void} + */ + function checkSpacingAfter(token) { + if (!token.value.endsWith("${")) { + return; // ends with a backtick, this is the last template element in the template literal + } + + const nextToken = sourceCode.getTokenAfter(token, { + includeComments: true, + }), + hasSpace = sourceCode.isSpaceBetween(token, nextToken); + + if (!astUtils.isTokenOnSameLine(token, nextToken)) { + return; + } + + if (always && !hasSpace) { + context.report({ + loc: { + start: { + line: token.loc.end.line, + column: token.loc.end.column - 2, + }, + end: token.loc.end, + }, + messageId: "expectedAfter", + fix: fixer => fixer.insertTextAfter(token, " "), + }); + } + + if (!always && hasSpace) { + context.report({ + loc: { + start: token.loc.end, + end: nextToken.loc.start, + }, + messageId: "unexpectedAfter", + fix: fixer => + fixer.removeRange([token.range[1], nextToken.range[0]]), + }); + } + } + + return { + TemplateElement(node) { + const token = sourceCode.getFirstToken(node); + + checkSpacingBefore(token); + checkSpacingAfter(token); + }, + }; + }, }; diff --git a/lib/rules/template-tag-spacing.js b/lib/rules/template-tag-spacing.js index 302646173e25..ba3f8f767d3d 100644 --- a/lib/rules/template-tag-spacing.js +++ b/lib/rules/template-tag-spacing.js @@ -12,100 +12,110 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "template-tag-spacing", - url: "https://eslint.style/rules/js/template-tag-spacing" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "template-tag-spacing", + url: "https://eslint.style/rules/js/template-tag-spacing", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Require or disallow spacing between template tags and their literals", - recommended: false, - url: "https://eslint.org/docs/latest/rules/template-tag-spacing" - }, + docs: { + description: + "Require or disallow spacing between template tags and their literals", + recommended: false, + url: "https://eslint.org/docs/latest/rules/template-tag-spacing", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { enum: ["always", "never"] } - ], - messages: { - unexpected: "Unexpected space between template tag and template literal.", - missing: "Missing space between template tag and template literal." - } - }, + schema: [{ enum: ["always", "never"] }], + messages: { + unexpected: + "Unexpected space between template tag and template literal.", + missing: "Missing space between template tag and template literal.", + }, + }, - create(context) { - const never = context.options[0] !== "always"; - const sourceCode = context.sourceCode; + create(context) { + const never = context.options[0] !== "always"; + const sourceCode = context.sourceCode; - /** - * Check if a space is present between a template tag and its literal - * @param {ASTNode} node node to evaluate - * @returns {void} - * @private - */ - function checkSpacing(node) { - const tagToken = sourceCode.getTokenBefore(node.quasi); - const literalToken = sourceCode.getFirstToken(node.quasi); - const hasWhitespace = sourceCode.isSpaceBetweenTokens(tagToken, literalToken); + /** + * Check if a space is present between a template tag and its literal + * @param {ASTNode} node node to evaluate + * @returns {void} + * @private + */ + function checkSpacing(node) { + const tagToken = sourceCode.getTokenBefore(node.quasi); + const literalToken = sourceCode.getFirstToken(node.quasi); + const hasWhitespace = sourceCode.isSpaceBetweenTokens( + tagToken, + literalToken, + ); - if (never && hasWhitespace) { - context.report({ - node, - loc: { - start: tagToken.loc.end, - end: literalToken.loc.start - }, - messageId: "unexpected", - fix(fixer) { - const comments = sourceCode.getCommentsBefore(node.quasi); + if (never && hasWhitespace) { + context.report({ + node, + loc: { + start: tagToken.loc.end, + end: literalToken.loc.start, + }, + messageId: "unexpected", + fix(fixer) { + const comments = sourceCode.getCommentsBefore( + node.quasi, + ); - // Don't fix anything if there's a single line comment after the template tag - if (comments.some(comment => comment.type === "Line")) { - return null; - } + // Don't fix anything if there's a single line comment after the template tag + if (comments.some(comment => comment.type === "Line")) { + return null; + } - return fixer.replaceTextRange( - [tagToken.range[1], literalToken.range[0]], - comments.reduce((text, comment) => text + sourceCode.getText(comment), "") - ); - } - }); - } else if (!never && !hasWhitespace) { - context.report({ - node, - loc: { - start: node.loc.start, - end: literalToken.loc.start - }, - messageId: "missing", - fix(fixer) { - return fixer.insertTextAfter(tagToken, " "); - } - }); - } - } + return fixer.replaceTextRange( + [tagToken.range[1], literalToken.range[0]], + comments.reduce( + (text, comment) => + text + sourceCode.getText(comment), + "", + ), + ); + }, + }); + } else if (!never && !hasWhitespace) { + context.report({ + node, + loc: { + start: node.loc.start, + end: literalToken.loc.start, + }, + messageId: "missing", + fix(fixer) { + return fixer.insertTextAfter(tagToken, " "); + }, + }); + } + } - return { - TaggedTemplateExpression: checkSpacing - }; - } + return { + TaggedTemplateExpression: checkSpacing, + }; + }, }; diff --git a/lib/rules/unicode-bom.js b/lib/rules/unicode-bom.js index 15c7ad77a513..87bb250b33a1 100644 --- a/lib/rules/unicode-bom.js +++ b/lib/rules/unicode-bom.js @@ -10,66 +10,64 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "layout", + meta: { + type: "layout", - defaultOptions: ["never"], + defaultOptions: ["never"], - docs: { - description: "Require or disallow Unicode byte order mark (BOM)", - recommended: false, - url: "https://eslint.org/docs/latest/rules/unicode-bom" - }, + docs: { + description: "Require or disallow Unicode byte order mark (BOM)", + recommended: false, + url: "https://eslint.org/docs/latest/rules/unicode-bom", + }, - fixable: "whitespace", + fixable: "whitespace", - schema: [ - { - enum: ["always", "never"] - } - ], - messages: { - expected: "Expected Unicode BOM (Byte Order Mark).", - unexpected: "Unexpected Unicode BOM (Byte Order Mark)." - } - }, + schema: [ + { + enum: ["always", "never"], + }, + ], + messages: { + expected: "Expected Unicode BOM (Byte Order Mark).", + unexpected: "Unexpected Unicode BOM (Byte Order Mark).", + }, + }, - create(context) { + create(context) { + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- + return { + Program: function checkUnicodeBOM(node) { + const sourceCode = context.sourceCode, + location = { column: 0, line: 1 }; + const [requireBOM] = context.options; - return { - - Program: function checkUnicodeBOM(node) { - - const sourceCode = context.sourceCode, - location = { column: 0, line: 1 }; - const [requireBOM] = context.options; - - if (!sourceCode.hasBOM && (requireBOM === "always")) { - context.report({ - node, - loc: location, - messageId: "expected", - fix(fixer) { - return fixer.insertTextBeforeRange([0, 1], "\uFEFF"); - } - }); - } else if (sourceCode.hasBOM && (requireBOM === "never")) { - context.report({ - node, - loc: location, - messageId: "unexpected", - fix(fixer) { - return fixer.removeRange([-1, 0]); - } - }); - } - } - - }; - - } + if (!sourceCode.hasBOM && requireBOM === "always") { + context.report({ + node, + loc: location, + messageId: "expected", + fix(fixer) { + return fixer.insertTextBeforeRange( + [0, 1], + "\uFEFF", + ); + }, + }); + } else if (sourceCode.hasBOM && requireBOM === "never") { + context.report({ + node, + loc: location, + messageId: "unexpected", + fix(fixer) { + return fixer.removeRange([-1, 0]); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/use-isnan.js b/lib/rules/use-isnan.js index 046b9122c9b0..456052024052 100644 --- a/lib/rules/use-isnan.js +++ b/lib/rules/use-isnan.js @@ -21,18 +21,17 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} `true` if the node is 'NaN' identifier. */ function isNaNIdentifier(node) { - if (!node) { - return false; - } - - const nodeToCheck = node.type === "SequenceExpression" - ? node.expressions.at(-1) - : node; - - return ( - astUtils.isSpecificId(nodeToCheck, "NaN") || - astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN") - ); + if (!node) { + return false; + } + + const nodeToCheck = + node.type === "SequenceExpression" ? node.expressions.at(-1) : node; + + return ( + astUtils.isSpecificId(nodeToCheck, "NaN") || + astUtils.isSpecificMemberAccess(nodeToCheck, "Number", "NaN") + ); } //------------------------------------------------------------------------------ @@ -41,197 +40,229 @@ function isNaNIdentifier(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - type: "object", - properties: { - enforceForSwitchCase: { - type: "boolean" - }, - enforceForIndexOf: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - defaultOptions: [{ - enforceForIndexOf: false, - enforceForSwitchCase: true - }], - - messages: { - comparisonWithNaN: "Use the isNaN function to compare with NaN.", - switchNaN: "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.", - caseNaN: "'case NaN' can never match. Use Number.isNaN before the switch.", - indexOfNaN: "Array prototype method '{{ methodName }}' cannot find NaN.", - replaceWithIsNaN: "Replace with Number.isNaN.", - replaceWithCastingAndIsNaN: "Replace with Number.isNaN and cast to a Number.", - replaceWithFindIndex: "Replace with Array.prototype.{{ methodName }}." - } - }, - - create(context) { - - const [{ enforceForIndexOf, enforceForSwitchCase }] = context.options; - const sourceCode = context.sourceCode; - - const fixableOperators = new Set(["==", "===", "!=", "!=="]); - const castableOperators = new Set(["==", "!="]); - - /** - * Get a fixer for a binary expression that compares to NaN. - * @param {ASTNode} node The node to fix. - * @param {function(string): string} wrapValue A function that wraps the compared value with a fix. - * @returns {function(Fixer): Fix} The fixer function. - */ - function getBinaryExpressionFixer(node, wrapValue) { - return fixer => { - const comparedValue = isNaNIdentifier(node.left) ? node.right : node.left; - const shouldWrap = comparedValue.type === "SequenceExpression"; - const shouldNegate = node.operator[0] === "!"; - - const negation = shouldNegate ? "!" : ""; - let comparedValueText = sourceCode.getText(comparedValue); - - if (shouldWrap) { - comparedValueText = `(${comparedValueText})`; - } - - const fixedValue = wrapValue(comparedValueText); - - return fixer.replaceText(node, `${negation}${fixedValue}`); - }; - } - - /** - * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkBinaryExpression(node) { - if ( - /^(?:[<>]|[!=]=)=?$/u.test(node.operator) && - (isNaNIdentifier(node.left) || isNaNIdentifier(node.right)) - ) { - const suggestedFixes = []; - const NaNNode = isNaNIdentifier(node.left) ? node.left : node.right; - - const isSequenceExpression = NaNNode.type === "SequenceExpression"; - const isSuggestable = fixableOperators.has(node.operator) && !isSequenceExpression; - const isCastable = castableOperators.has(node.operator); - - if (isSuggestable) { - suggestedFixes.push({ - messageId: "replaceWithIsNaN", - fix: getBinaryExpressionFixer(node, value => `Number.isNaN(${value})`) - }); - - if (isCastable) { - suggestedFixes.push({ - messageId: "replaceWithCastingAndIsNaN", - fix: getBinaryExpressionFixer(node, value => `Number.isNaN(Number(${value}))`) - }); - } - } - - context.report({ - node, - messageId: "comparisonWithNaN", - suggest: suggestedFixes - }); - } - } - - /** - * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:` - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkSwitchStatement(node) { - if (isNaNIdentifier(node.discriminant)) { - context.report({ node, messageId: "switchNaN" }); - } - - for (const switchCase of node.cases) { - if (isNaNIdentifier(switchCase.test)) { - context.report({ node: switchCase, messageId: "caseNaN" }); - } - } - } - - /** - * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`. - * @param {ASTNode} node The node to check. - * @returns {void} - */ - function checkCallExpression(node) { - const callee = astUtils.skipChainExpression(node.callee); - - if (callee.type === "MemberExpression") { - const methodName = astUtils.getStaticPropertyName(callee); - - if ( - (methodName === "indexOf" || methodName === "lastIndexOf") && - node.arguments.length <= 2 && - isNaNIdentifier(node.arguments[0]) - ) { - - /* - * To retain side effects, it's essential to address `NaN` beforehand, which - * is not possible with fixes like `arr.findIndex(Number.isNaN)`. - */ - const isSuggestable = node.arguments[0].type !== "SequenceExpression" && !node.arguments[1]; - const suggestedFixes = []; - - if (isSuggestable) { - const shouldWrap = callee.computed; - const findIndexMethod = methodName === "indexOf" ? "findIndex" : "findLastIndex"; - const propertyName = shouldWrap ? `"${findIndexMethod}"` : findIndexMethod; - - suggestedFixes.push({ - messageId: "replaceWithFindIndex", - data: { methodName: findIndexMethod }, - fix: fixer => [ - fixer.replaceText(callee.property, propertyName), - fixer.replaceText(node.arguments[0], "Number.isNaN") - ] - }); - } - - context.report({ - node, - messageId: "indexOfNaN", - data: { methodName }, - suggest: suggestedFixes - }); - } - } - } - - const listeners = { - BinaryExpression: checkBinaryExpression - }; - - if (enforceForSwitchCase) { - listeners.SwitchStatement = checkSwitchStatement; - } - - if (enforceForIndexOf) { - listeners.CallExpression = checkCallExpression; - } - - return listeners; - } + meta: { + 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", + }, + + schema: [ + { + type: "object", + properties: { + enforceForSwitchCase: { + type: "boolean", + }, + enforceForIndexOf: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + defaultOptions: [ + { + enforceForIndexOf: false, + enforceForSwitchCase: true, + }, + ], + + messages: { + comparisonWithNaN: "Use the isNaN function to compare with NaN.", + switchNaN: + "'switch(NaN)' can never match a case clause. Use Number.isNaN instead of the switch.", + caseNaN: + "'case NaN' can never match. Use Number.isNaN before the switch.", + indexOfNaN: + "Array prototype method '{{ methodName }}' cannot find NaN.", + replaceWithIsNaN: "Replace with Number.isNaN.", + replaceWithCastingAndIsNaN: + "Replace with Number.isNaN and cast to a Number.", + replaceWithFindIndex: + "Replace with Array.prototype.{{ methodName }}.", + }, + }, + + create(context) { + const [{ enforceForIndexOf, enforceForSwitchCase }] = context.options; + const sourceCode = context.sourceCode; + + const fixableOperators = new Set(["==", "===", "!=", "!=="]); + const castableOperators = new Set(["==", "!="]); + + /** + * Get a fixer for a binary expression that compares to NaN. + * @param {ASTNode} node The node to fix. + * @param {function(string): string} wrapValue A function that wraps the compared value with a fix. + * @returns {function(Fixer): Fix} The fixer function. + */ + function getBinaryExpressionFixer(node, wrapValue) { + return fixer => { + const comparedValue = isNaNIdentifier(node.left) + ? node.right + : node.left; + const shouldWrap = comparedValue.type === "SequenceExpression"; + const shouldNegate = node.operator[0] === "!"; + + const negation = shouldNegate ? "!" : ""; + let comparedValueText = sourceCode.getText(comparedValue); + + if (shouldWrap) { + comparedValueText = `(${comparedValueText})`; + } + + const fixedValue = wrapValue(comparedValueText); + + return fixer.replaceText(node, `${negation}${fixedValue}`); + }; + } + + /** + * Checks the given `BinaryExpression` node for `foo === NaN` and other comparisons. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkBinaryExpression(node) { + if ( + /^(?:[<>]|[!=]=)=?$/u.test(node.operator) && + (isNaNIdentifier(node.left) || isNaNIdentifier(node.right)) + ) { + const suggestedFixes = []; + const NaNNode = isNaNIdentifier(node.left) + ? node.left + : node.right; + + const isSequenceExpression = + NaNNode.type === "SequenceExpression"; + const isSuggestable = + fixableOperators.has(node.operator) && + !isSequenceExpression; + const isCastable = castableOperators.has(node.operator); + + if (isSuggestable) { + suggestedFixes.push({ + messageId: "replaceWithIsNaN", + fix: getBinaryExpressionFixer( + node, + value => `Number.isNaN(${value})`, + ), + }); + + if (isCastable) { + suggestedFixes.push({ + messageId: "replaceWithCastingAndIsNaN", + fix: getBinaryExpressionFixer( + node, + value => `Number.isNaN(Number(${value}))`, + ), + }); + } + } + + context.report({ + node, + messageId: "comparisonWithNaN", + suggest: suggestedFixes, + }); + } + } + + /** + * Checks the discriminant and all case clauses of the given `SwitchStatement` node for `switch(NaN)` and `case NaN:` + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkSwitchStatement(node) { + if (isNaNIdentifier(node.discriminant)) { + context.report({ node, messageId: "switchNaN" }); + } + + for (const switchCase of node.cases) { + if (isNaNIdentifier(switchCase.test)) { + context.report({ node: switchCase, messageId: "caseNaN" }); + } + } + } + + /** + * Checks the given `CallExpression` node for `.indexOf(NaN)` and `.lastIndexOf(NaN)`. + * @param {ASTNode} node The node to check. + * @returns {void} + */ + function checkCallExpression(node) { + const callee = astUtils.skipChainExpression(node.callee); + + if (callee.type === "MemberExpression") { + const methodName = astUtils.getStaticPropertyName(callee); + + if ( + (methodName === "indexOf" || + methodName === "lastIndexOf") && + node.arguments.length <= 2 && + isNaNIdentifier(node.arguments[0]) + ) { + /* + * To retain side effects, it's essential to address `NaN` beforehand, which + * is not possible with fixes like `arr.findIndex(Number.isNaN)`. + */ + const isSuggestable = + node.arguments[0].type !== "SequenceExpression" && + !node.arguments[1]; + const suggestedFixes = []; + + if (isSuggestable) { + const shouldWrap = callee.computed; + const findIndexMethod = + methodName === "indexOf" + ? "findIndex" + : "findLastIndex"; + const propertyName = shouldWrap + ? `"${findIndexMethod}"` + : findIndexMethod; + + suggestedFixes.push({ + messageId: "replaceWithFindIndex", + data: { methodName: findIndexMethod }, + fix: fixer => [ + fixer.replaceText( + callee.property, + propertyName, + ), + fixer.replaceText( + node.arguments[0], + "Number.isNaN", + ), + ], + }); + } + + context.report({ + node, + messageId: "indexOfNaN", + data: { methodName }, + suggest: suggestedFixes, + }); + } + } + } + + const listeners = { + BinaryExpression: checkBinaryExpression, + }; + + if (enforceForSwitchCase) { + listeners.SwitchStatement = checkSwitchStatement; + } + + if (enforceForIndexOf) { + listeners.CallExpression = checkCallExpression; + } + + return listeners; + }, }; diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js index 7a93da144b10..0a06235ed9a8 100644 --- a/lib/rules/utils/ast-utils.js +++ b/lib/rules/utils/ast-utils.js @@ -14,10 +14,10 @@ const esutils = require("esutils"); const espree = require("espree"); const escapeRegExp = require("escape-string-regexp"); const { - breakableTypePattern, - createGlobalLinebreakMatcher, - lineBreakPattern, - shebangPattern + breakableTypePattern, + createGlobalLinebreakMatcher, + lineBreakPattern, + shebangPattern, } = require("../../shared/ast-utils"); const globals = require("../../../conf/globals"); const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); @@ -26,25 +26,33 @@ const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); // Helpers //------------------------------------------------------------------------------ -const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; +const anyFunctionPattern = + /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u; const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u; -const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u; +const arrayMethodWithThisArgPattern = + /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u; const arrayOrTypedArrayPattern = /Array$/u; const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u; const thisTagPattern = /^[\s*]*@this/mu; - -const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; +const COMMENTS_IGNORE_PATTERN = + /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u; const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u; const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]); // A set of node types that can contain a list of statements -const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]); +const STATEMENT_LIST_PARENTS = new Set([ + "Program", + "BlockStatement", + "StaticBlock", + "SwitchCase", +]); const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u; // Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string -const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su; +const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = + /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su; const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]); @@ -63,21 +71,22 @@ const ECMASCRIPT_GLOBALS = globals[`es${LATEST_ECMA_VERSION}`]; * @private */ function isModifyingReference(reference, index, references) { - const identifier = reference.identifier; - - /* - * Destructuring assignments can have multiple default value, so - * possibly there are multiple writeable references for the same - * identifier. - */ - const modifyingDifferentIdentifier = index === 0 || - references[index - 1].identifier !== identifier; - - return (identifier && - reference.init === false && - reference.isWrite() && - modifyingDifferentIdentifier - ); + const identifier = reference.identifier; + + /* + * Destructuring assignments can have multiple default value, so + * possibly there are multiple writeable references for the same + * identifier. + */ + const modifyingDifferentIdentifier = + index === 0 || references[index - 1].identifier !== identifier; + + return ( + identifier && + reference.init === false && + reference.isWrite() && + modifyingDifferentIdentifier + ); } /** @@ -86,7 +95,7 @@ function isModifyingReference(reference, index, references) { * @returns {boolean} `true` if the string starts with uppercase. */ function startsWithUpperCase(s) { - return s[0] !== s[0].toLocaleLowerCase(); + return s[0] !== s[0].toLocaleLowerCase(); } /** @@ -95,7 +104,7 @@ function startsWithUpperCase(s) { * @returns {boolean} Whether or not a node is a constructor. */ function isES5Constructor(node) { - return (node.id && startsWithUpperCase(node.id.name)); + return node.id && startsWithUpperCase(node.id.name); } /** @@ -104,12 +113,16 @@ function isES5Constructor(node) { * @returns {Node|null} A found function node. */ function getUpperFunction(node) { - for (let currentNode = node; currentNode; currentNode = currentNode.parent) { - if (anyFunctionPattern.test(currentNode.type)) { - return currentNode; - } - } - return null; + for ( + let currentNode = node; + currentNode; + currentNode = currentNode.parent + ) { + if (anyFunctionPattern.test(currentNode.type)) { + return currentNode; + } + } + return null; } /** @@ -123,7 +136,7 @@ function getUpperFunction(node) { * @returns {boolean} `true` if the node is a function node. */ function isFunction(node) { - return Boolean(node && anyFunctionPattern.test(node.type)); + return Boolean(node && anyFunctionPattern.test(node.type)); } /** @@ -139,7 +152,7 @@ function isFunction(node) { * @returns {boolean} `true` if the node is a loop node. */ function isLoop(node) { - return Boolean(node && anyLoopPattern.test(node.type)); + return Boolean(node && anyLoopPattern.test(node.type)); } /** @@ -148,13 +161,17 @@ function isLoop(node) { * @returns {boolean} `true` if the node is in a loop. */ function isInLoop(node) { - for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) { - if (isLoop(currentNode)) { - return true; - } - } - - return false; + for ( + let currentNode = node; + currentNode && !isFunction(currentNode); + currentNode = currentNode.parent + ) { + if (isLoop(currentNode)) { + return true; + } + } + + return false; } /** @@ -163,15 +180,19 @@ function isInLoop(node) { * @returns {boolean} `true` if the node is a `null` literal */ function isNullLiteral(node) { - - /* - * Checking `node.value === null` does not guarantee that a literal is a null literal. - * When parsing values that cannot be represented in the current environment (e.g. unicode - * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to - * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check - * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 - */ - return node.type === "Literal" && node.value === null && !node.regex && !node.bigint; + /* + * Checking `node.value === null` does not guarantee that a literal is a null literal. + * When parsing values that cannot be represented in the current environment (e.g. unicode + * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to + * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check + * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 + */ + return ( + node.type === "Literal" && + node.value === null && + !node.regex && + !node.bigint + ); } /** @@ -181,11 +202,11 @@ function isNullLiteral(node) { * @public */ function isNullOrUndefined(node) { - return ( - isNullLiteral(node) || - (node.type === "Identifier" && node.name === "undefined") || - (node.type === "UnaryExpression" && node.operator === "void") - ); + return ( + isNullLiteral(node) || + (node.type === "Identifier" && node.name === "undefined") || + (node.type === "UnaryExpression" && node.operator === "void") + ); } /** @@ -194,7 +215,7 @@ function isNullOrUndefined(node) { * @returns {boolean} Whether or not the node is callee. */ function isCallee(node) { - return node.parent.type === "CallExpression" && node.parent.callee === node; + return node.parent.type === "CallExpression" && node.parent.callee === node; } /** @@ -207,35 +228,34 @@ function isCallee(node) { * @returns {string|null} String value if it can be determined. Otherwise, `null`. */ function getStaticStringValue(node) { - switch (node.type) { - case "Literal": - if (node.value === null) { - if (isNullLiteral(node)) { - return String(node.value); // "null" - } - if (node.regex) { - return `/${node.regex.pattern}/${node.regex.flags}`; - } - if (node.bigint) { - return node.bigint; - } - - // Otherwise, this is an unknown literal. The function will return null. - - } else { - return String(node.value); - } - break; - case "TemplateLiteral": - if (node.expressions.length === 0 && node.quasis.length === 1) { - return node.quasis[0].value.cooked; - } - break; - - // no default - } - - return null; + switch (node.type) { + case "Literal": + if (node.value === null) { + if (isNullLiteral(node)) { + return String(node.value); // "null" + } + if (node.regex) { + return `/${node.regex.pattern}/${node.regex.flags}`; + } + if (node.bigint) { + return node.bigint; + } + + // Otherwise, this is an unknown literal. The function will return null. + } else { + return String(node.value); + } + break; + case "TemplateLiteral": + if (node.expressions.length === 0 && node.quasis.length === 1) { + return node.quasis[0].value.cooked; + } + break; + + // no default + } + + return null; } /** @@ -269,34 +289,34 @@ function getStaticStringValue(node) { * @returns {string|null} The property name if static. Otherwise, null. */ function getStaticPropertyName(node) { - let prop; + let prop; - switch (node && node.type) { - case "ChainExpression": - return getStaticPropertyName(node.expression); + switch (node && node.type) { + case "ChainExpression": + return getStaticPropertyName(node.expression); - case "Property": - case "PropertyDefinition": - case "MethodDefinition": - prop = node.key; - break; + case "Property": + case "PropertyDefinition": + case "MethodDefinition": + prop = node.key; + break; - case "MemberExpression": - prop = node.property; - break; + case "MemberExpression": + prop = node.property; + break; - // no default - } + // no default + } - if (prop) { - if (prop.type === "Identifier" && !node.computed) { - return prop.name; - } + if (prop) { + if (prop.type === "Identifier" && !node.computed) { + return prop.name; + } - return getStaticStringValue(prop); - } + return getStaticStringValue(prop); + } - return null; + return null; } /** @@ -305,7 +325,7 @@ function getStaticPropertyName(node) { * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node. */ function skipChainExpression(node) { - return node && node.type === "ChainExpression" ? node.expression : node; + return node && node.type === "ChainExpression" ? node.expression : node; } /** @@ -315,9 +335,9 @@ function skipChainExpression(node) { * @returns {boolean} `true` if the `actual` is an expected value. */ function checkText(actual, expected) { - return typeof expected === "string" - ? actual === expected - : expected.test(actual); + return typeof expected === "string" + ? actual === expected + : expected.test(actual); } /** @@ -327,7 +347,7 @@ function checkText(actual, expected) { * @returns {boolean} `true` if the node is an Identifier node with the name. */ function isSpecificId(node, name) { - return node.type === "Identifier" && checkText(node.name, name); + return node.type === "Identifier" && checkText(node.name, name); } /** @@ -340,25 +360,28 @@ function isSpecificId(node, name) { * The node is a `MemberExpression` or `ChainExpression`. */ function isSpecificMemberAccess(node, objectName, propertyName) { - const checkNode = skipChainExpression(node); + const checkNode = skipChainExpression(node); - if (checkNode.type !== "MemberExpression") { - return false; - } + if (checkNode.type !== "MemberExpression") { + return false; + } - if (objectName && !isSpecificId(checkNode.object, objectName)) { - return false; - } + if (objectName && !isSpecificId(checkNode.object, objectName)) { + return false; + } - if (propertyName) { - const actualPropertyName = getStaticPropertyName(checkNode); + if (propertyName) { + const actualPropertyName = getStaticPropertyName(checkNode); - if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) { - return false; - } - } + if ( + typeof actualPropertyName !== "string" || + !checkText(actualPropertyName, propertyName) + ) { + return false; + } + } - return true; + return true; } /** @@ -368,23 +391,22 @@ function isSpecificMemberAccess(node, objectName, propertyName) { * @returns {boolean} `true` if the two literal nodes are the same value. */ function equalLiteralValue(left, right) { - - // RegExp literal. - if (left.regex || right.regex) { - return Boolean( - left.regex && - right.regex && - left.regex.pattern === right.regex.pattern && - left.regex.flags === right.regex.flags - ); - } - - // BigInt literal. - if (left.bigint || right.bigint) { - return left.bigint === right.bigint; - } - - return left.value === right.value; + // RegExp literal. + if (left.regex || right.regex) { + return Boolean( + left.regex && + right.regex && + left.regex.pattern === right.regex.pattern && + left.regex.flags === right.regex.flags, + ); + } + + // BigInt literal. + if (left.bigint || right.bigint) { + return left.bigint === right.bigint; + } + + return left.value === right.value; } /** @@ -399,61 +421,83 @@ function equalLiteralValue(left, right) { * @returns {boolean} `true` if both sides match and reference the same value. */ function isSameReference(left, right, disableStaticComputedKey = false) { - if (left.type !== right.type) { - - // Handle `a.b` and `a?.b` are samely. - if (left.type === "ChainExpression") { - return isSameReference(left.expression, right, disableStaticComputedKey); - } - if (right.type === "ChainExpression") { - return isSameReference(left, right.expression, disableStaticComputedKey); - } - - return false; - } - - switch (left.type) { - case "Super": - case "ThisExpression": - return true; - - case "Identifier": - case "PrivateIdentifier": - return left.name === right.name; - case "Literal": - return equalLiteralValue(left, right); - - case "ChainExpression": - return isSameReference(left.expression, right.expression, disableStaticComputedKey); - - case "MemberExpression": { - if (!disableStaticComputedKey) { - const nameA = getStaticPropertyName(left); - - // x.y = x["y"] - if (nameA !== null) { - return ( - isSameReference(left.object, right.object, disableStaticComputedKey) && - nameA === getStaticPropertyName(right) - ); - } - } - - /* - * x[0] = x[0] - * x[y] = x[y] - * x.y = x.y - */ - return ( - left.computed === right.computed && - isSameReference(left.object, right.object, disableStaticComputedKey) && - isSameReference(left.property, right.property, disableStaticComputedKey) - ); - } - - default: - return false; - } + if (left.type !== right.type) { + // Handle `a.b` and `a?.b` are samely. + if (left.type === "ChainExpression") { + return isSameReference( + left.expression, + right, + disableStaticComputedKey, + ); + } + if (right.type === "ChainExpression") { + return isSameReference( + left, + right.expression, + disableStaticComputedKey, + ); + } + + return false; + } + + switch (left.type) { + case "Super": + case "ThisExpression": + return true; + + case "Identifier": + case "PrivateIdentifier": + return left.name === right.name; + case "Literal": + return equalLiteralValue(left, right); + + case "ChainExpression": + return isSameReference( + left.expression, + right.expression, + disableStaticComputedKey, + ); + + case "MemberExpression": { + if (!disableStaticComputedKey) { + const nameA = getStaticPropertyName(left); + + // x.y = x["y"] + if (nameA !== null) { + return ( + isSameReference( + left.object, + right.object, + disableStaticComputedKey, + ) && nameA === getStaticPropertyName(right) + ); + } + } + + /* + * x[0] = x[0] + * x[y] = x[y] + * x.y = x.y + */ + return ( + left.computed === right.computed && + isSameReference( + left.object, + right.object, + disableStaticComputedKey, + ) && + isSameReference( + left.property, + right.property, + disableStaticComputedKey, + ) + ); + } + + default: + return false; + } } /** @@ -462,7 +506,7 @@ function isSameReference(left, right, disableStaticComputedKey = false) { * @returns {boolean} Whether or not the node is a `Reflect.apply`. */ function isReflectApply(node) { - return isSpecificMemberAccess(node, "Reflect", "apply"); + return isSpecificMemberAccess(node, "Reflect", "apply"); } /** @@ -471,7 +515,7 @@ function isReflectApply(node) { * @returns {boolean} Whether or not the node is a `Array.from`. */ function isArrayFromMethod(node) { - return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from"); + return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from"); } /** @@ -480,7 +524,7 @@ function isArrayFromMethod(node) { * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument. */ function isMethodWhichHasThisArg(node) { - return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern); + return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern); } /** @@ -489,7 +533,7 @@ function isMethodWhichHasThisArg(node) { * @returns {Function} Negated function. */ function negate(f) { - return token => !f(token); + return token => !f(token); } /** @@ -499,17 +543,19 @@ function negate(f) { * @returns {boolean} Whether or not the node has a `@this` tag in its comments. */ function hasJSDocThisTag(node, sourceCode) { - const jsdocComment = sourceCode.getJSDocComment(node); - - if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { - return true; - } - - // Checks `@this` in its leading comments for callbacks, - // because callbacks don't have its JSDoc comment. - // e.g. - // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); - return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)); + const jsdocComment = sourceCode.getJSDocComment(node); + + if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { + return true; + } + + // Checks `@this` in its leading comments for callbacks, + // because callbacks don't have its JSDoc comment. + // e.g. + // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); + return sourceCode + .getCommentsBefore(node) + .some(comment => thisTagPattern.test(comment.value)); } /** @@ -520,12 +566,16 @@ function hasJSDocThisTag(node, sourceCode) { * @private */ function isParenthesised(sourceCode, node) { - const previousToken = sourceCode.getTokenBefore(node), - nextToken = sourceCode.getTokenAfter(node); - - return Boolean(previousToken && nextToken) && - previousToken.value === "(" && previousToken.range[1] <= node.range[0] && - nextToken.value === ")" && nextToken.range[0] >= node.range[1]; + const previousToken = sourceCode.getTokenBefore(node), + nextToken = sourceCode.getTokenAfter(node); + + return ( + Boolean(previousToken && nextToken) && + previousToken.value === "(" && + previousToken.range[1] <= node.range[0] && + nextToken.value === ")" && + nextToken.range[0] >= node.range[1] + ); } /** @@ -534,7 +584,7 @@ function isParenthesised(sourceCode, node) { * @returns {boolean} `true` if the token is a `=` token. */ function isEqToken(token) { - return token.value === "=" && token.type === "Punctuator"; + return token.value === "=" && token.type === "Punctuator"; } /** @@ -543,7 +593,7 @@ function isEqToken(token) { * @returns {boolean} `true` if the token is an arrow token. */ function isArrowToken(token) { - return token.value === "=>" && token.type === "Punctuator"; + return token.value === "=>" && token.type === "Punctuator"; } /** @@ -552,7 +602,7 @@ function isArrowToken(token) { * @returns {boolean} `true` if the token is a comma token. */ function isCommaToken(token) { - return token.value === "," && token.type === "Punctuator"; + return token.value === "," && token.type === "Punctuator"; } /** @@ -561,7 +611,7 @@ function isCommaToken(token) { * @returns {boolean} `true` if the token is a dot token. */ function isDotToken(token) { - return token.value === "." && token.type === "Punctuator"; + return token.value === "." && token.type === "Punctuator"; } /** @@ -570,7 +620,7 @@ function isDotToken(token) { * @returns {boolean} `true` if the token is a `?.` token. */ function isQuestionDotToken(token) { - return token.value === "?." && token.type === "Punctuator"; + return token.value === "?." && token.type === "Punctuator"; } /** @@ -579,7 +629,7 @@ function isQuestionDotToken(token) { * @returns {boolean} `true` if the token is a semicolon token. */ function isSemicolonToken(token) { - return token.value === ";" && token.type === "Punctuator"; + return token.value === ";" && token.type === "Punctuator"; } /** @@ -588,7 +638,7 @@ function isSemicolonToken(token) { * @returns {boolean} `true` if the token is a colon token. */ function isColonToken(token) { - return token.value === ":" && token.type === "Punctuator"; + return token.value === ":" && token.type === "Punctuator"; } /** @@ -597,7 +647,7 @@ function isColonToken(token) { * @returns {boolean} `true` if the token is an opening parenthesis token. */ function isOpeningParenToken(token) { - return token.value === "(" && token.type === "Punctuator"; + return token.value === "(" && token.type === "Punctuator"; } /** @@ -606,7 +656,7 @@ function isOpeningParenToken(token) { * @returns {boolean} `true` if the token is a closing parenthesis token. */ function isClosingParenToken(token) { - return token.value === ")" && token.type === "Punctuator"; + return token.value === ")" && token.type === "Punctuator"; } /** @@ -615,7 +665,7 @@ function isClosingParenToken(token) { * @returns {boolean} `true` if the token is an opening square bracket token. */ function isOpeningBracketToken(token) { - return token.value === "[" && token.type === "Punctuator"; + return token.value === "[" && token.type === "Punctuator"; } /** @@ -624,7 +674,7 @@ function isOpeningBracketToken(token) { * @returns {boolean} `true` if the token is a closing square bracket token. */ function isClosingBracketToken(token) { - return token.value === "]" && token.type === "Punctuator"; + return token.value === "]" && token.type === "Punctuator"; } /** @@ -633,7 +683,7 @@ function isClosingBracketToken(token) { * @returns {boolean} `true` if the token is an opening brace token. */ function isOpeningBraceToken(token) { - return token.value === "{" && token.type === "Punctuator"; + return token.value === "{" && token.type === "Punctuator"; } /** @@ -642,7 +692,7 @@ function isOpeningBraceToken(token) { * @returns {boolean} `true` if the token is a closing brace token. */ function isClosingBraceToken(token) { - return token.value === "}" && token.type === "Punctuator"; + return token.value === "}" && token.type === "Punctuator"; } /** @@ -651,7 +701,11 @@ function isClosingBraceToken(token) { * @returns {boolean} `true` if the token is a comment token. */ function isCommentToken(token) { - return token.type === "Line" || token.type === "Block" || token.type === "Shebang"; + return ( + token.type === "Line" || + token.type === "Block" || + token.type === "Shebang" + ); } /** @@ -660,7 +714,7 @@ function isCommentToken(token) { * @returns {boolean} `true` if the token is a keyword token. */ function isKeywordToken(token) { - return token.type === "Keyword"; + return token.type === "Keyword"; } /** @@ -670,19 +724,20 @@ function isKeywordToken(token) { * @returns {Token} `(` token. */ function getOpeningParenOfParams(node, sourceCode) { - - // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param. - if (node.type === "ArrowFunctionExpression" && node.params.length === 1) { - const argToken = sourceCode.getFirstToken(node.params[0]); - const maybeParenToken = sourceCode.getTokenBefore(argToken); - - return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken; - } - - // Otherwise, returns paren. - return node.id - ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) - : sourceCode.getFirstToken(node, isOpeningParenToken); + // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param. + if (node.type === "ArrowFunctionExpression" && node.params.length === 1) { + const argToken = sourceCode.getFirstToken(node.params[0]); + const maybeParenToken = sourceCode.getTokenBefore(argToken); + + return isOpeningParenToken(maybeParenToken) + ? maybeParenToken + : argToken; + } + + // Otherwise, returns paren. + return node.id + ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) + : sourceCode.getFirstToken(node, isOpeningParenToken); } /** @@ -693,21 +748,22 @@ function getOpeningParenOfParams(node, sourceCode) { * @returns {boolean} the source code for the given node. */ function equalTokens(left, right, sourceCode) { - const tokensL = sourceCode.getTokens(left); - const tokensR = sourceCode.getTokens(right); - - if (tokensL.length !== tokensR.length) { - return false; - } - for (let i = 0; i < tokensL.length; ++i) { - if (tokensL[i].type !== tokensR[i].type || - tokensL[i].value !== tokensR[i].value - ) { - return false; - } - } - - return true; + const tokensL = sourceCode.getTokens(left); + const tokensR = sourceCode.getTokens(right); + + if (tokensL.length !== tokensR.length) { + return false; + } + for (let i = 0; i < tokensL.length; ++i) { + if ( + tokensL[i].type !== tokensR[i].type || + tokensL[i].value !== tokensR[i].value + ) { + return false; + } + } + + return true; } /** @@ -723,10 +779,10 @@ function equalTokens(left, right, sourceCode) { * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression */ function isLogicalExpression(node) { - return ( - node.type === "LogicalExpression" && - (node.operator === "&&" || node.operator === "||") - ); + return ( + node.type === "LogicalExpression" && + (node.operator === "&&" || node.operator === "||") + ); } /** @@ -741,7 +797,7 @@ function isLogicalExpression(node) { * @returns {boolean} `true` if the node is `??`. */ function isCoalesceExpression(node) { - return node.type === "LogicalExpression" && node.operator === "??"; + return node.type === "LogicalExpression" && node.operator === "??"; } /** @@ -751,10 +807,10 @@ function isCoalesceExpression(node) { * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression. */ function isMixedLogicalAndCoalesceExpressions(left, right) { - return ( - (isLogicalExpression(left) && isCoalesceExpression(right)) || - (isCoalesceExpression(left) && isLogicalExpression(right)) - ); + return ( + (isLogicalExpression(left) && isCoalesceExpression(right)) || + (isCoalesceExpression(left) && isLogicalExpression(right)) + ); } /** @@ -763,7 +819,7 @@ function isMixedLogicalAndCoalesceExpressions(left, right) { * @returns {boolean} `true` if the operator is a logical assignment operator. */ function isLogicalAssignmentOperator(operator) { - return LOGICAL_ASSIGNMENT_OPERATORS.has(operator); + return LOGICAL_ASSIGNMENT_OPERATORS.has(operator); } /** @@ -773,10 +829,10 @@ function isLogicalAssignmentOperator(operator) { * @returns {Token} The colon token of the node. */ function getSwitchCaseColonToken(node, sourceCode) { - if (node.test) { - return sourceCode.getTokenAfter(node.test, isColonToken); - } - return sourceCode.getFirstToken(node, 1); + if (node.test) { + return sourceCode.getTokenAfter(node.test, isColonToken); + } + return sourceCode.getFirstToken(node, 1); } /** @@ -790,12 +846,12 @@ function getSwitchCaseColonToken(node, sourceCode) { * @returns {string} The module export name. */ function getModuleExportName(node) { - if (node.type === "Identifier") { - return node.name; - } + if (node.type === "Identifier") { + return node.name; + } - // string literal - return node.value; + // string literal + return node.value; } /** @@ -805,27 +861,26 @@ function getModuleExportName(node) { * `null` when it cannot be determined. */ function getBooleanValue(node) { - if (node.value === null) { - - /* - * it might be a null literal or bigint/regex literal in unsupported environments . - * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral - * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral - */ - - if (node.raw === "null") { - return false; - } - - // regex is always truthy - if (typeof node.regex === "object") { - return true; - } - - return null; - } - - return !!node.value; + if (node.value === null) { + /* + * it might be a null literal or bigint/regex literal in unsupported environments . + * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral + * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral + */ + + if (node.raw === "null") { + return false; + } + + // regex is always truthy + if (typeof node.regex === "object") { + return true; + } + + return null; + } + + return !!node.value; } /** @@ -835,34 +890,37 @@ function getBooleanValue(node) { * @returns {boolean} true when condition short circuits whole condition */ function isLogicalIdentity(node, operator) { - switch (node.type) { - case "Literal": - return (operator === "||" && getBooleanValue(node) === true) || - (operator === "&&" && getBooleanValue(node) === false); - - case "UnaryExpression": - return (operator === "&&" && node.operator === "void"); - - case "LogicalExpression": - - /* - * handles `a && false || b` - * `false` is an identity element of `&&` but not `||` - */ - return operator === node.operator && - ( - isLogicalIdentity(node.left, operator) || - isLogicalIdentity(node.right, operator) - ); - - case "AssignmentExpression": - return ["||=", "&&="].includes(node.operator) && - operator === node.operator.slice(0, -1) && - isLogicalIdentity(node.right, operator); - - // no default - } - return false; + switch (node.type) { + case "Literal": + return ( + (operator === "||" && getBooleanValue(node) === true) || + (operator === "&&" && getBooleanValue(node) === false) + ); + + case "UnaryExpression": + return operator === "&&" && node.operator === "void"; + + case "LogicalExpression": + /* + * handles `a && false || b` + * `false` is an identity element of `&&` but not `||` + */ + return ( + operator === node.operator && + (isLogicalIdentity(node.left, operator) || + isLogicalIdentity(node.right, operator)) + ); + + case "AssignmentExpression": + return ( + ["||=", "&&="].includes(node.operator) && + operator === node.operator.slice(0, -1) && + isLogicalIdentity(node.right, operator) + ); + + // no default + } + return false; } /** @@ -872,17 +930,16 @@ function isLogicalIdentity(node, operator) { * @returns {boolean} `true` if the identifier is a reference to a global variable. */ function isReferenceToGlobalVariable(scope, node) { - const reference = scope.references.find(ref => ref.identifier === node); - - return Boolean( - reference && - reference.resolved && - reference.resolved.scope.type === "global" && - reference.resolved.defs.length === 0 - ); + const reference = scope.references.find(ref => ref.identifier === node); + + return Boolean( + reference && + reference.resolved && + reference.resolved.scope.type === "global" && + reference.resolved.defs.length === 0, + ); } - /** * Checks if a node has a constant truthiness value. * @param {Scope} scope Scope in which the node appears. @@ -895,104 +952,139 @@ function isReferenceToGlobalVariable(scope, node) { * @private */ function isConstant(scope, node, inBooleanPosition) { - - // node.elements can return null values in the case of sparse arrays ex. [,] - if (!node) { - return true; - } - switch (node.type) { - case "Literal": - case "ArrowFunctionExpression": - case "FunctionExpression": - return true; - case "ClassExpression": - case "ObjectExpression": - - /** - * In theory objects like: - * - * `{toString: () => a}` - * `{valueOf: () => a}` - * - * Or a classes like: - * - * `class { static toString() { return a } }` - * `class { static valueOf() { return a } }` - * - * Are not constant verifiably when `inBooleanPosition` is - * false, but it's an edge case we've opted not to handle. - */ - return true; - case "TemplateLiteral": - return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) || - node.expressions.every(exp => isConstant(scope, exp, false)); - - case "ArrayExpression": { - if (!inBooleanPosition) { - return node.elements.every(element => isConstant(scope, element, false)); - } - return true; - } - - case "UnaryExpression": - if ( - node.operator === "void" || - node.operator === "typeof" && inBooleanPosition - ) { - return true; - } - - if (node.operator === "!") { - return isConstant(scope, node.argument, true); - } - - return isConstant(scope, node.argument, false); - - case "BinaryExpression": - return isConstant(scope, node.left, false) && - isConstant(scope, node.right, false) && - node.operator !== "in"; - - case "LogicalExpression": { - const isLeftConstant = isConstant(scope, node.left, inBooleanPosition); - const isRightConstant = isConstant(scope, node.right, inBooleanPosition); - const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); - const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator)); - - return (isLeftConstant && isRightConstant) || - isLeftShortCircuit || - isRightShortCircuit; - } - case "NewExpression": - return inBooleanPosition; - case "AssignmentExpression": - if (node.operator === "=") { - return isConstant(scope, node.right, inBooleanPosition); - } - - if (["||=", "&&="].includes(node.operator) && inBooleanPosition) { - return isLogicalIdentity(node.right, node.operator.slice(0, -1)); - } - - return false; - - case "SequenceExpression": - return isConstant(scope, node.expressions.at(-1), inBooleanPosition); - case "SpreadElement": - return isConstant(scope, node.argument, inBooleanPosition); - case "CallExpression": - if (node.callee.type === "Identifier" && node.callee.name === "Boolean") { - if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) { - return isReferenceToGlobalVariable(scope, node.callee); - } - } - return false; - case "Identifier": - return node.name === "undefined" && isReferenceToGlobalVariable(scope, node); - - // no default - } - return false; + // node.elements can return null values in the case of sparse arrays ex. [,] + if (!node) { + return true; + } + switch (node.type) { + case "Literal": + case "ArrowFunctionExpression": + case "FunctionExpression": + return true; + case "ClassExpression": + case "ObjectExpression": + /** + * In theory objects like: + * + * `{toString: () => a}` + * `{valueOf: () => a}` + * + * Or a classes like: + * + * `class { static toString() { return a } }` + * `class { static valueOf() { return a } }` + * + * Are not constant verifiably when `inBooleanPosition` is + * false, but it's an edge case we've opted not to handle. + */ + return true; + case "TemplateLiteral": + return ( + (inBooleanPosition && + node.quasis.some(quasi => quasi.value.cooked.length)) || + node.expressions.every(exp => isConstant(scope, exp, false)) + ); + + case "ArrayExpression": { + if (!inBooleanPosition) { + return node.elements.every(element => + isConstant(scope, element, false), + ); + } + return true; + } + + case "UnaryExpression": + if ( + node.operator === "void" || + (node.operator === "typeof" && inBooleanPosition) + ) { + return true; + } + + if (node.operator === "!") { + return isConstant(scope, node.argument, true); + } + + return isConstant(scope, node.argument, false); + + case "BinaryExpression": + return ( + isConstant(scope, node.left, false) && + isConstant(scope, node.right, false) && + node.operator !== "in" + ); + + case "LogicalExpression": { + const isLeftConstant = isConstant( + scope, + node.left, + inBooleanPosition, + ); + const isRightConstant = isConstant( + scope, + node.right, + inBooleanPosition, + ); + const isLeftShortCircuit = + isLeftConstant && isLogicalIdentity(node.left, node.operator); + const isRightShortCircuit = + inBooleanPosition && + isRightConstant && + isLogicalIdentity(node.right, node.operator); + + return ( + (isLeftConstant && isRightConstant) || + isLeftShortCircuit || + isRightShortCircuit + ); + } + case "NewExpression": + return inBooleanPosition; + case "AssignmentExpression": + if (node.operator === "=") { + return isConstant(scope, node.right, inBooleanPosition); + } + + if (["||=", "&&="].includes(node.operator) && inBooleanPosition) { + return isLogicalIdentity( + node.right, + node.operator.slice(0, -1), + ); + } + + return false; + + case "SequenceExpression": + return isConstant( + scope, + node.expressions.at(-1), + inBooleanPosition, + ); + case "SpreadElement": + return isConstant(scope, node.argument, inBooleanPosition); + case "CallExpression": + if ( + node.callee.type === "Identifier" && + node.callee.name === "Boolean" + ) { + if ( + node.arguments.length === 0 || + isConstant(scope, node.arguments[0], true) + ) { + return isReferenceToGlobalVariable(scope, node.callee); + } + } + return false; + case "Identifier": + return ( + node.name === "undefined" && + isReferenceToGlobalVariable(scope, node) + ); + + // no default + } + return false; } /** @@ -1005,13 +1097,15 @@ function isConstant(scope, node, inBooleanPosition) { * file or function body. */ function isTopLevelExpressionStatement(node) { - if (node.type !== "ExpressionStatement") { - return false; - } - const parent = node.parent; - - return parent.type === "Program" || (parent.type === "BlockStatement" && isFunction(parent.parent)); - + if (node.type !== "ExpressionStatement") { + return false; + } + const parent = node.parent; + + return ( + parent.type === "Program" || + (parent.type === "BlockStatement" && isFunction(parent.parent)) + ); } /** @@ -1020,7 +1114,10 @@ function isTopLevelExpressionStatement(node) { * @returns {boolean} `true` if the node is a part of directive prologue. */ function isDirective(node) { - return node.type === "ExpressionStatement" && typeof node.directive === "string"; + return ( + node.type === "ExpressionStatement" && + typeof node.directive === "string" + ); } /** @@ -1029,15 +1126,15 @@ function isDirective(node) { * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node. */ function isStartOfExpressionStatement(node) { - const start = node.range[0]; - let ancestor = node; - - while ((ancestor = ancestor.parent) && ancestor.range[0] === start) { - if (ancestor.type === "ExpressionStatement") { - return true; - } - } - return false; + const start = node.range[0]; + let ancestor = node; + + while ((ancestor = ancestor.parent) && ancestor.range[0] === start) { + if (ancestor.type === "ExpressionStatement") { + return true; + } + } + return false; } /** @@ -1052,84 +1149,94 @@ function isStartOfExpressionStatement(node) { let needsPrecedingSemicolon; { - const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]); - - // Declaration types that cannot be continued by a punctuator when ending with a string Literal that is a direct child. - const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]); - - const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]); - - // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types. - const NODE_TYPES_BY_KEYWORD = { - __proto__: null, - break: "BreakStatement", - continue: "ContinueStatement", - debugger: "DebuggerStatement", - do: "DoWhileStatement", - else: "IfStatement", - return: "ReturnStatement", - yield: "YieldExpression" - }; - - /* - * Before an opening parenthesis, postfix `++` and `--` always trigger ASI; - * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement. - */ - const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]); - - /* - * Statements that can contain an `ExpressionStatement` after a closing parenthesis. - * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis. - */ - const STATEMENTS = new Set([ - "DoWhileStatement", - "ForInStatement", - "ForOfStatement", - "ForStatement", - "IfStatement", - "WhileStatement", - "WithStatement" - ]); - - needsPrecedingSemicolon = - function(sourceCode, node) { - const prevToken = sourceCode.getTokenBefore(node); - - if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) { - return false; - } - - const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]); - - if (isClosingParenToken(prevToken)) { - return !STATEMENTS.has(prevNode.type); - } - - if (isClosingBraceToken(prevToken)) { - return ( - prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" && prevNode.parent.parent.type !== "MethodDefinition" || - prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" || - prevNode.type === "ObjectExpression" - ); - } - - if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) { - if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) { - return false; - } - - const keyword = prevToken.value; - const nodeType = NODE_TYPES_BY_KEYWORD[keyword]; - - return prevNode.type !== nodeType; - } - - if (prevToken.type === "String") { - return !DECLARATIONS.has(prevNode.parent.type); - } - - return true; - }; + const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]); + + // Declaration types that cannot be continued by a punctuator when ending with a string Literal that is a direct child. + const DECLARATIONS = new Set([ + "ExportAllDeclaration", + "ExportNamedDeclaration", + "ImportDeclaration", + ]); + + const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]); + + // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types. + const NODE_TYPES_BY_KEYWORD = { + __proto__: null, + break: "BreakStatement", + continue: "ContinueStatement", + debugger: "DebuggerStatement", + do: "DoWhileStatement", + else: "IfStatement", + return: "ReturnStatement", + yield: "YieldExpression", + }; + + /* + * Before an opening parenthesis, postfix `++` and `--` always trigger ASI; + * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement. + */ + const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]); + + /* + * Statements that can contain an `ExpressionStatement` after a closing parenthesis. + * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis. + */ + const STATEMENTS = new Set([ + "DoWhileStatement", + "ForInStatement", + "ForOfStatement", + "ForStatement", + "IfStatement", + "WhileStatement", + "WithStatement", + ]); + + needsPrecedingSemicolon = function (sourceCode, node) { + const prevToken = sourceCode.getTokenBefore(node); + + if ( + !prevToken || + (prevToken.type === "Punctuator" && + PUNCTUATORS.has(prevToken.value)) + ) { + return false; + } + + const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]); + + if (isClosingParenToken(prevToken)) { + return !STATEMENTS.has(prevNode.type); + } + + if (isClosingBraceToken(prevToken)) { + return ( + (prevNode.type === "BlockStatement" && + prevNode.parent.type === "FunctionExpression" && + prevNode.parent.parent.type !== "MethodDefinition") || + (prevNode.type === "ClassBody" && + prevNode.parent.type === "ClassExpression") || + prevNode.type === "ObjectExpression" + ); + } + + if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) { + if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) { + return false; + } + + const keyword = prevToken.value; + const nodeType = NODE_TYPES_BY_KEYWORD[keyword]; + + return prevNode.type !== nodeType; + } + + if (prevToken.type === "String") { + return !DECLARATIONS.has(prevNode.parent.type); + } + + return true; + }; } /** @@ -1138,40 +1245,41 @@ let needsPrecedingSemicolon; * @returns {boolean} Whether the node is used as an import attribute key. */ function isImportAttributeKey(node) { - const { parent } = node; - - // static import/re-export - if (parent.type === "ImportAttribute" && parent.key === node) { - return true; - } - - // dynamic import - if ( - parent.type === "Property" && - !parent.computed && - (parent.key === node || parent.value === node && parent.shorthand && !parent.method) && - parent.parent.type === "ObjectExpression" - ) { - const objectExpression = parent.parent; - const objectExpressionParent = objectExpression.parent; - - if ( - objectExpressionParent.type === "ImportExpression" && - objectExpressionParent.options === objectExpression - ) { - return true; - } - - // nested key - if ( - objectExpressionParent.type === "Property" && - objectExpressionParent.value === objectExpression - ) { - return isImportAttributeKey(objectExpressionParent.key); - } - } - - return false; + const { parent } = node; + + // static import/re-export + if (parent.type === "ImportAttribute" && parent.key === node) { + return true; + } + + // dynamic import + if ( + parent.type === "Property" && + !parent.computed && + (parent.key === node || + (parent.value === node && parent.shorthand && !parent.method)) && + parent.parent.type === "ObjectExpression" + ) { + const objectExpression = parent.parent; + const objectExpressionParent = objectExpression.parent; + + if ( + objectExpressionParent.type === "ImportExpression" && + objectExpressionParent.options === objectExpression + ) { + return true; + } + + // nested key + if ( + objectExpressionParent.type === "Property" && + objectExpressionParent.value === objectExpression + ) { + return isImportAttributeKey(objectExpressionParent.key); + } + } + + return false; } //------------------------------------------------------------------------------ @@ -1179,1298 +1287,1369 @@ function isImportAttributeKey(node) { //------------------------------------------------------------------------------ module.exports = { - COMMENTS_IGNORE_PATTERN, - LINEBREAKS, - LINEBREAK_MATCHER: lineBreakPattern, - SHEBANG_MATCHER: shebangPattern, - STATEMENT_LIST_PARENTS, - ECMASCRIPT_GLOBALS, - - /** - * Determines whether two adjacent tokens are on the same line. - * @param {Object} left The left token object. - * @param {Object} right The right token object. - * @returns {boolean} Whether or not the tokens are on the same line. - * @public - */ - isTokenOnSameLine(left, right) { - return left.loc.end.line === right.loc.start.line; - }, - - isNullOrUndefined, - isCallee, - isES5Constructor, - getUpperFunction, - isFunction, - isLoop, - isInLoop, - isArrayFromMethod, - isParenthesised, - createGlobalLinebreakMatcher, - equalTokens, - - isArrowToken, - isClosingBraceToken, - isClosingBracketToken, - isClosingParenToken, - isColonToken, - isCommaToken, - isCommentToken, - isDotToken, - isQuestionDotToken, - isKeywordToken, - isNotClosingBraceToken: negate(isClosingBraceToken), - isNotClosingBracketToken: negate(isClosingBracketToken), - isNotClosingParenToken: negate(isClosingParenToken), - isNotColonToken: negate(isColonToken), - isNotCommaToken: negate(isCommaToken), - isNotDotToken: negate(isDotToken), - isNotQuestionDotToken: negate(isQuestionDotToken), - isNotOpeningBraceToken: negate(isOpeningBraceToken), - isNotOpeningBracketToken: negate(isOpeningBracketToken), - isNotOpeningParenToken: negate(isOpeningParenToken), - isNotSemicolonToken: negate(isSemicolonToken), - isOpeningBraceToken, - isOpeningBracketToken, - isOpeningParenToken, - isSemicolonToken, - isEqToken, - - /** - * Checks whether or not a given node is a string literal. - * @param {ASTNode} node A node to check. - * @returns {boolean} `true` if the node is a string literal. - */ - isStringLiteral(node) { - return ( - (node.type === "Literal" && typeof node.value === "string") || - node.type === "TemplateLiteral" - ); - }, - - /** - * Checks whether a given node is a breakable statement or not. - * The node is breakable if the node is one of the following type: - * - * - DoWhileStatement - * - ForInStatement - * - ForOfStatement - * - ForStatement - * - SwitchStatement - * - WhileStatement - * @param {ASTNode} node A node to check. - * @returns {boolean} `true` if the node is breakable. - */ - isBreakableStatement(node) { - return breakableTypePattern.test(node.type); - }, - - /** - * Gets references which are non initializer and writable. - * @param {Reference[]} references An array of references. - * @returns {Reference[]} An array of only references which are non initializer and writable. - * @public - */ - getModifyingReferences(references) { - return references.filter(isModifyingReference); - }, - - /** - * Validate that a string passed in is surrounded by the specified character - * @param {string} val The text to check. - * @param {string} character The character to see if it's surrounded by. - * @returns {boolean} True if the text is surrounded by the character, false if not. - * @private - */ - isSurroundedBy(val, character) { - return val[0] === character && val.at(-1) === character; - }, - - /** - * Returns whether the provided node is an ESLint directive comment or not - * @param {Line|Block} node The comment token to be checked - * @returns {boolean} `true` if the node is an ESLint directive comment - */ - isDirectiveComment(node) { - const comment = node.value.trim(); - - return ( - node.type === "Line" && comment.startsWith("eslint-") || - node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment) - ); - }, - - /** - * Gets the trailing statement of a given node. - * - * if (code) - * consequent; - * - * When taking this `IfStatement`, returns `consequent;` statement. - * @param {ASTNode} A node to get. - * @returns {ASTNode|null} The trailing statement's node. - */ - getTrailingStatement: esutils.ast.trailingStatement, - - /** - * Finds the variable by a given name in a given scope and its upper scopes. - * @param {eslint-scope.Scope} initScope A scope to start find. - * @param {string} name A variable name to find. - * @returns {eslint-scope.Variable|null} A found variable or `null`. - */ - getVariableByName(initScope, name) { - let scope = initScope; - - while (scope) { - const variable = scope.set.get(name); - - if (variable) { - return variable; - } - - scope = scope.upper; - } - - return null; - }, - - /** - * Checks whether or not a given function node is the default `this` binding. - * - * First, this checks the node: - * - * - The given node is not in `PropertyDefinition#value` position. - * - The given node is not `StaticBlock`. - * - The function name does not start with uppercase. It's a convention to capitalize the names - * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. - * - The function does not have a JSDoc comment that has a @this tag. - * - * Next, this checks the location of the node. - * If the location is below, this judges `this` is valid. - * - * - The location is not on an object literal. - * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous - * functions only, as the name of the variable is considered to be the name of the function in this case. - * This check is not performed if `capIsConstructor` is set to `false`. - * - The location is not on an ES2015 class. - * - Its `bind`/`call`/`apply` method is not called directly. - * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. - * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock` - * or any expression that is `PropertyDefinition#value` node. - * @param {SourceCode} sourceCode A SourceCode instance to get comments. - * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts - * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. - * @returns {boolean} The function node is the default `this` binding. - */ - isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { - - /* - * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers. - * Therefore, A expression node at `PropertyDefinition#value` is a function. - * In this case, `this` is always not default binding. - */ - if (node.parent.type === "PropertyDefinition" && node.parent.value === node) { - return false; - } - - // Class static blocks are implicit functions. In this case, `this` is always not default binding. - if (node.type === "StaticBlock") { - return false; - } - - if ( - (capIsConstructor && isES5Constructor(node)) || - hasJSDocThisTag(node, sourceCode) - ) { - return false; - } - const isAnonymous = node.id === null; - let currentNode = node; - - while (currentNode) { - const parent = currentNode.parent; - - switch (parent.type) { - - /* - * Looks up the destination. - * e.g., obj.foo = nativeFoo || function foo() { ... }; - */ - case "LogicalExpression": - case "ConditionalExpression": - case "ChainExpression": - currentNode = parent; - break; - - /* - * If the upper function is IIFE, checks the destination of the return value. - * e.g. - * obj.foo = (function() { - * // setup... - * return function foo() { ... }; - * })(); - * obj.foo = (() => - * function foo() { ... } - * )(); - */ - case "ReturnStatement": { - const func = getUpperFunction(parent); - - if (func === null || !isCallee(func)) { - return true; - } - currentNode = func.parent; - break; - } - case "ArrowFunctionExpression": - if (currentNode !== parent.body || !isCallee(parent)) { - return true; - } - currentNode = parent.parent; - break; - - /* - * e.g. - * var obj = { foo() { ... } }; - * var obj = { foo: function() { ... } }; - * class A { constructor() { ... } } - * class A { foo() { ... } } - * class A { get foo() { ... } } - * class A { set foo() { ... } } - * class A { static foo() { ... } } - * class A { foo = function() { ... } } - */ - case "Property": - case "PropertyDefinition": - case "MethodDefinition": - return parent.value !== currentNode; - - /* - * e.g. - * obj.foo = function foo() { ... }; - * Foo = function() { ... }; - * [obj.foo = function foo() { ... }] = a; - * [Foo = function() { ... }] = a; - */ - case "AssignmentExpression": - case "AssignmentPattern": - if (parent.left.type === "MemberExpression") { - return false; - } - if ( - capIsConstructor && - isAnonymous && - parent.left.type === "Identifier" && - startsWithUpperCase(parent.left.name) - ) { - return false; - } - return true; - - /* - * e.g. - * var Foo = function() { ... }; - */ - case "VariableDeclarator": - return !( - capIsConstructor && - isAnonymous && - parent.init === currentNode && - parent.id.type === "Identifier" && - startsWithUpperCase(parent.id.name) - ); - - /* - * e.g. - * var foo = function foo() { ... }.bind(obj); - * (function foo() { ... }).call(obj); - * (function foo() { ... }).apply(obj, []); - */ - case "MemberExpression": - if ( - parent.object === currentNode && - isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern) - ) { - const maybeCalleeNode = parent.parent.type === "ChainExpression" - ? parent.parent - : parent; - - return !( - isCallee(maybeCalleeNode) && - maybeCalleeNode.parent.arguments.length >= 1 && - !isNullOrUndefined(maybeCalleeNode.parent.arguments[0]) - ); - } - return true; - - /* - * e.g. - * Reflect.apply(function() {}, obj, []); - * Array.from([], function() {}, obj); - * list.forEach(function() {}, obj); - */ - case "CallExpression": - if (isReflectApply(parent.callee)) { - return ( - parent.arguments.length !== 3 || - parent.arguments[0] !== currentNode || - isNullOrUndefined(parent.arguments[1]) - ); - } - if (isArrayFromMethod(parent.callee)) { - return ( - parent.arguments.length !== 3 || - parent.arguments[1] !== currentNode || - isNullOrUndefined(parent.arguments[2]) - ); - } - if (isMethodWhichHasThisArg(parent.callee)) { - return ( - parent.arguments.length !== 2 || - parent.arguments[0] !== currentNode || - isNullOrUndefined(parent.arguments[1]) - ); - } - return true; - - // Otherwise `this` is default. - default: - return true; - } - } - - /* c8 ignore next */ - return true; - }, - - /** - * Get the precedence level based on the node type - * @param {ASTNode} node node to evaluate - * @returns {int} precedence level - * @private - */ - getPrecedence(node) { - switch (node.type) { - case "SequenceExpression": - return 0; - - case "AssignmentExpression": - case "ArrowFunctionExpression": - case "YieldExpression": - return 1; - - case "ConditionalExpression": - return 3; - - case "LogicalExpression": - switch (node.operator) { - case "||": - case "??": - return 4; - case "&&": - return 5; - - // no default - } - - /* falls through */ - - case "BinaryExpression": - - switch (node.operator) { - case "|": - return 6; - case "^": - return 7; - case "&": - return 8; - case "==": - case "!=": - case "===": - case "!==": - return 9; - case "<": - case "<=": - case ">": - case ">=": - case "in": - case "instanceof": - return 10; - case "<<": - case ">>": - case ">>>": - return 11; - case "+": - case "-": - return 12; - case "*": - case "/": - case "%": - return 13; - case "**": - return 15; - - // no default - } - - /* falls through */ - - case "UnaryExpression": - case "AwaitExpression": - return 16; - - case "UpdateExpression": - return 17; - - case "CallExpression": - case "ChainExpression": - case "ImportExpression": - return 18; - - case "NewExpression": - return 19; - - default: - if (node.type in eslintVisitorKeys) { - return 20; - } - - /* - * if the node is not a standard node that we know about, then assume it has the lowest precedence - * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of - * unwrapping them and potentially changing the meaning of the code or introducing a syntax error. - */ - return -1; - } - }, - - /** - * Checks whether the given node is an empty block node or not. - * @param {ASTNode|null} node The node to check. - * @returns {boolean} `true` if the node is an empty block. - */ - isEmptyBlock(node) { - return Boolean(node && node.type === "BlockStatement" && node.body.length === 0); - }, - - /** - * Checks whether the given node is an empty function node or not. - * @param {ASTNode|null} node The node to check. - * @returns {boolean} `true` if the node is an empty function. - */ - isEmptyFunction(node) { - return isFunction(node) && module.exports.isEmptyBlock(node.body); - }, - - /** - * Get directives from directive prologue of a Program or Function node. - * @param {ASTNode} node The node to check. - * @returns {ASTNode[]} The directives found in the directive prologue. - */ - getDirectivePrologue(node) { - const directives = []; - - // Directive prologues only occur at the top of files or functions. - if ( - node.type === "Program" || - node.type === "FunctionDeclaration" || - node.type === "FunctionExpression" || - - /* - * Do not check arrow functions with implicit return. - * `() => "use strict";` returns the string `"use strict"`. - */ - (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement") - ) { - const statements = node.type === "Program" ? node.body : node.body.body; - - for (const statement of statements) { - if ( - statement.type === "ExpressionStatement" && - statement.expression.type === "Literal" - ) { - directives.push(statement); - } else { - break; - } - } - } - - return directives; - }, - - /** - * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added - * after the node will be parsed as a decimal point, rather than a property-access dot. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if this node is a decimal integer. - * @example - * - * 0 // true - * 5 // true - * 50 // true - * 5_000 // true - * 1_234_56 // true - * 08 // true - * 0192 // true - * 5. // false - * .5 // false - * 5.0 // false - * 5.00_00 // false - * 05 // false - * 0x5 // false - * 0b101 // false - * 0b11_01 // false - * 0o5 // false - * 5e0 // false - * 5e1_000 // false - * 5n // false - * 1_000n // false - * "5" // false - * - */ - isDecimalInteger(node) { - return node.type === "Literal" && typeof node.value === "number" && - DECIMAL_INTEGER_PATTERN.test(node.raw); - }, - - /** - * Determines whether this token is a decimal integer numeric token. - * This is similar to isDecimalInteger(), but for tokens. - * @param {Token} token The token to check. - * @returns {boolean} `true` if this token is a decimal integer. - */ - isDecimalIntegerNumericToken(token) { - return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value); - }, - - /** - * Gets the name and kind of the given function node. - * - * - `function foo() {}` .................... `function 'foo'` - * - `(function foo() {})` .................. `function 'foo'` - * - `(function() {})` ...................... `function` - * - `function* foo() {}` ................... `generator function 'foo'` - * - `(function* foo() {})` ................. `generator function 'foo'` - * - `(function*() {})` ..................... `generator function` - * - `() => {}` ............................. `arrow function` - * - `async () => {}` ....................... `async arrow function` - * - `({ foo: function foo() {} })` ......... `method 'foo'` - * - `({ foo: function() {} })` ............. `method 'foo'` - * - `({ ['foo']: function() {} })` ......... `method 'foo'` - * - `({ [foo]: function() {} })` ........... `method` - * - `({ foo() {} })` ....................... `method 'foo'` - * - `({ foo: function* foo() {} })` ........ `generator method 'foo'` - * - `({ foo: function*() {} })` ............ `generator method 'foo'` - * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` - * - `({ [foo]: function*() {} })` .......... `generator method` - * - `({ *foo() {} })` ...................... `generator method 'foo'` - * - `({ foo: async function foo() {} })` ... `async method 'foo'` - * - `({ foo: async function() {} })` ....... `async method 'foo'` - * - `({ ['foo']: async function() {} })` ... `async method 'foo'` - * - `({ [foo]: async function() {} })` ..... `async method` - * - `({ async foo() {} })` ................. `async method 'foo'` - * - `({ get foo() {} })` ................... `getter 'foo'` - * - `({ set foo(a) {} })` .................. `setter 'foo'` - * - `class A { constructor() {} }` ......... `constructor` - * - `class A { foo() {} }` ................. `method 'foo'` - * - `class A { *foo() {} }` ................ `generator method 'foo'` - * - `class A { async foo() {} }` ........... `async method 'foo'` - * - `class A { ['foo']() {} }` ............. `method 'foo'` - * - `class A { *['foo']() {} }` ............ `generator method 'foo'` - * - `class A { async ['foo']() {} }` ....... `async method 'foo'` - * - `class A { [foo]() {} }` ............... `method` - * - `class A { *[foo]() {} }` .............. `generator method` - * - `class A { async [foo]() {} }` ......... `async method` - * - `class A { get foo() {} }` ............. `getter 'foo'` - * - `class A { set foo(a) {} }` ............ `setter 'foo'` - * - `class A { static foo() {} }` .......... `static method 'foo'` - * - `class A { static *foo() {} }` ......... `static generator method 'foo'` - * - `class A { static async foo() {} }` .... `static async method 'foo'` - * - `class A { static get foo() {} }` ...... `static getter 'foo'` - * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` - * - `class A { foo = () => {}; }` .......... `method 'foo'` - * - `class A { foo = function() {}; }` ..... `method 'foo'` - * - `class A { foo = function bar() {}; }` . `method 'foo'` - * - `class A { static foo = () => {}; }` ... `static method 'foo'` - * - `class A { '#foo' = () => {}; }` ....... `method '#foo'` - * - `class A { #foo = () => {}; }` ......... `private method #foo` - * - `class A { static #foo = () => {}; }` .. `static private method #foo` - * - `class A { '#foo'() {} }` .............. `method '#foo'` - * - `class A { #foo() {} }` ................ `private method #foo` - * - `class A { static #foo() {} }` ......... `static private method #foo` - * @param {ASTNode} node The function node to get. - * @returns {string} The name and kind of the function node. - */ - getFunctionNameWithKind(node) { - const parent = node.parent; - const tokens = []; - - if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { - - // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features - if (parent.static) { - tokens.push("static"); - } - if (!parent.computed && parent.key.type === "PrivateIdentifier") { - tokens.push("private"); - } - } - if (node.async) { - tokens.push("async"); - } - if (node.generator) { - tokens.push("generator"); - } - - if (parent.type === "Property" || parent.type === "MethodDefinition") { - if (parent.kind === "constructor") { - return "constructor"; - } - if (parent.kind === "get") { - tokens.push("getter"); - } else if (parent.kind === "set") { - tokens.push("setter"); - } else { - tokens.push("method"); - } - } else if (parent.type === "PropertyDefinition") { - tokens.push("method"); - } else { - if (node.type === "ArrowFunctionExpression") { - tokens.push("arrow"); - } - tokens.push("function"); - } - - if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { - if (!parent.computed && parent.key.type === "PrivateIdentifier") { - tokens.push(`#${parent.key.name}`); - } else { - const name = getStaticPropertyName(parent); - - if (name !== null) { - tokens.push(`'${name}'`); - } else if (node.id) { - tokens.push(`'${node.id.name}'`); - } - } - } else if (node.id) { - tokens.push(`'${node.id.name}'`); - } - - return tokens.join(" "); - }, - - /** - * Gets the location of the given function node for reporting. - * - * - `function foo() {}` - * ^^^^^^^^^^^^ - * - `(function foo() {})` - * ^^^^^^^^^^^^ - * - `(function() {})` - * ^^^^^^^^ - * - `function* foo() {}` - * ^^^^^^^^^^^^^ - * - `(function* foo() {})` - * ^^^^^^^^^^^^^ - * - `(function*() {})` - * ^^^^^^^^^ - * - `() => {}` - * ^^ - * - `async () => {}` - * ^^ - * - `({ foo: function foo() {} })` - * ^^^^^^^^^^^^^^^^^ - * - `({ foo: function() {} })` - * ^^^^^^^^^^^^^ - * - `({ ['foo']: function() {} })` - * ^^^^^^^^^^^^^^^^^ - * - `({ [foo]: function() {} })` - * ^^^^^^^^^^^^^^^ - * - `({ foo() {} })` - * ^^^ - * - `({ foo: function* foo() {} })` - * ^^^^^^^^^^^^^^^^^^ - * - `({ foo: function*() {} })` - * ^^^^^^^^^^^^^^ - * - `({ ['foo']: function*() {} })` - * ^^^^^^^^^^^^^^^^^^ - * - `({ [foo]: function*() {} })` - * ^^^^^^^^^^^^^^^^ - * - `({ *foo() {} })` - * ^^^^ - * - `({ foo: async function foo() {} })` - * ^^^^^^^^^^^^^^^^^^^^^^^ - * - `({ foo: async function() {} })` - * ^^^^^^^^^^^^^^^^^^^ - * - `({ ['foo']: async function() {} })` - * ^^^^^^^^^^^^^^^^^^^^^^^ - * - `({ [foo]: async function() {} })` - * ^^^^^^^^^^^^^^^^^^^^^ - * - `({ async foo() {} })` - * ^^^^^^^^^ - * - `({ get foo() {} })` - * ^^^^^^^ - * - `({ set foo(a) {} })` - * ^^^^^^^ - * - `class A { constructor() {} }` - * ^^^^^^^^^^^ - * - `class A { foo() {} }` - * ^^^ - * - `class A { *foo() {} }` - * ^^^^ - * - `class A { async foo() {} }` - * ^^^^^^^^^ - * - `class A { ['foo']() {} }` - * ^^^^^^^ - * - `class A { *['foo']() {} }` - * ^^^^^^^^ - * - `class A { async ['foo']() {} }` - * ^^^^^^^^^^^^^ - * - `class A { [foo]() {} }` - * ^^^^^ - * - `class A { *[foo]() {} }` - * ^^^^^^ - * - `class A { async [foo]() {} }` - * ^^^^^^^^^^^ - * - `class A { get foo() {} }` - * ^^^^^^^ - * - `class A { set foo(a) {} }` - * ^^^^^^^ - * - `class A { static foo() {} }` - * ^^^^^^^^^^ - * - `class A { static *foo() {} }` - * ^^^^^^^^^^^ - * - `class A { static async foo() {} }` - * ^^^^^^^^^^^^^^^^ - * - `class A { static get foo() {} }` - * ^^^^^^^^^^^^^^ - * - `class A { static set foo(a) {} }` - * ^^^^^^^^^^^^^^ - * - `class A { foo = function() {} }` - * ^^^^^^^^^^^^^^ - * - `class A { static foo = function() {} }` - * ^^^^^^^^^^^^^^^^^^^^^ - * - `class A { foo = (a, b) => {} }` - * ^^^^^^ - * @param {ASTNode} node The function node to get. - * @param {SourceCode} sourceCode The source code object to get tokens. - * @returns {string} The location of the function node for reporting. - */ - getFunctionHeadLoc(node, sourceCode) { - const parent = node.parent; - let start; - let end; - - if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") { - start = parent.loc.start; - end = getOpeningParenOfParams(node, sourceCode).loc.start; - } else if (node.type === "ArrowFunctionExpression") { - const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken); - - start = arrowToken.loc.start; - end = arrowToken.loc.end; - } else { - start = node.loc.start; - end = getOpeningParenOfParams(node, sourceCode).loc.start; - } - - return { - start: Object.assign({}, start), - end: Object.assign({}, end) - }; - }, - - /** - * Gets next location when the result is not out of bound, otherwise returns null. - * - * Assumptions: - * - * - The given location represents a valid location in the given source code. - * - Columns are 0-based. - * - Lines are 1-based. - * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location. - * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end. - * The start (column 0) of that extra line is considered to be a valid location. - * - * Examples of successive locations (line, column): - * - * code: foo - * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null - * - * code: foo - * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null - * - * code: foo - * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null - * - * code: ab - * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null - * - * code: ab - * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null - * - * code: ab - * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null - * - * code: a - * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null - * - * code: - * locations: (1, 0) -> (2, 0) -> null - * - * code: - * locations: (1, 0) -> null - * @param {SourceCode} sourceCode The sourceCode - * @param {{line: number, column: number}} location The location - * @returns {{line: number, column: number} | null} Next location - */ - getNextLocation(sourceCode, { line, column }) { - if (column < sourceCode.lines[line - 1].length) { - return { - line, - column: column + 1 - }; - } - - if (line < sourceCode.lines.length) { - return { - line: line + 1, - column: 0 - }; - } - - return null; - }, - - /** - * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses - * surrounding the node. - * @param {SourceCode} sourceCode The source code object - * @param {ASTNode} node An expression node - * @returns {string} The text representing the node, with all surrounding parentheses included - */ - getParenthesisedText(sourceCode, node) { - let leftToken = sourceCode.getFirstToken(node); - let rightToken = sourceCode.getLastToken(node); - - while ( - sourceCode.getTokenBefore(leftToken) && - sourceCode.getTokenBefore(leftToken).type === "Punctuator" && - sourceCode.getTokenBefore(leftToken).value === "(" && - sourceCode.getTokenAfter(rightToken) && - sourceCode.getTokenAfter(rightToken).type === "Punctuator" && - sourceCode.getTokenAfter(rightToken).value === ")" - ) { - leftToken = sourceCode.getTokenBefore(leftToken); - rightToken = sourceCode.getTokenAfter(rightToken); - } - - return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]); - }, - - /** - * Determine if a node has a possibility to be an Error object - * @param {ASTNode} node ASTNode to check - * @returns {boolean} True if there is a chance it contains an Error obj - */ - couldBeError(node) { - switch (node.type) { - case "Identifier": - case "CallExpression": - case "NewExpression": - case "MemberExpression": - case "TaggedTemplateExpression": - case "YieldExpression": - case "AwaitExpression": - case "ChainExpression": - return true; // possibly an error object. - - case "AssignmentExpression": - if (["=", "&&="].includes(node.operator)) { - return module.exports.couldBeError(node.right); - } - - if (["||=", "??="].includes(node.operator)) { - return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right); - } - - /** - * All other assignment operators are mathematical assignment operators (arithmetic or bitwise). - * An assignment expression with a mathematical operator can either evaluate to a primitive value, - * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object. - */ - return false; - - case "SequenceExpression": { - const exprs = node.expressions; - - return exprs.length !== 0 && module.exports.couldBeError(exprs.at(-1)); - } - - case "LogicalExpression": - - /* - * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it - * doesn't short-circuit, it takes the value from the right side, so the right side must always be - * a plausible error. A future improvement could verify that the left side could be truthy by - * excluding falsy literals. - */ - if (node.operator === "&&") { - return module.exports.couldBeError(node.right); - } - - return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right); - - case "ConditionalExpression": - return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate); - - default: - return false; - } - }, - - /** - * Check if a given node is a numeric literal or not. - * @param {ASTNode} node The node to check. - * @returns {boolean} `true` if the node is a number or bigint literal. - */ - isNumericLiteral(node) { - return ( - node.type === "Literal" && - (typeof node.value === "number" || Boolean(node.bigint)) - ); - }, - - /** - * Determines whether two tokens can safely be placed next to each other without merging into a single token - * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. - * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. - * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed - * next to each other, behavior is undefined (although it should return `true` in most cases). - */ - canTokensBeAdjacent(leftValue, rightValue) { - const espreeOptions = { - ecmaVersion: espree.latestEcmaVersion, - comment: true, - range: true - }; - - let leftToken; - - if (typeof leftValue === "string") { - let tokens; - - try { - tokens = espree.tokenize(leftValue, espreeOptions); - } catch { - return false; - } - - const comments = tokens.comments; - - leftToken = tokens.at(-1); - if (comments.length) { - const lastComment = comments.at(-1); - - if (!leftToken || lastComment.range[0] > leftToken.range[0]) { - leftToken = lastComment; - } - } - } else { - leftToken = leftValue; - } - - /* - * If a hashbang comment was passed as a token object from SourceCode, - * its type will be "Shebang" because of the way ESLint itself handles hashbangs. - * If a hashbang comment was passed in a string and then tokenized in this function, - * its type will be "Hashbang" because of the way Espree tokenizes hashbangs. - */ - if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") { - return false; - } - - let rightToken; - - if (typeof rightValue === "string") { - let tokens; - - try { - tokens = espree.tokenize(rightValue, espreeOptions); - } catch { - return false; - } - - const comments = tokens.comments; - - rightToken = tokens[0]; - if (comments.length) { - const firstComment = comments[0]; - - if (!rightToken || firstComment.range[0] < rightToken.range[0]) { - rightToken = firstComment; - } - } - } else { - rightToken = rightValue; - } - - if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") { - if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") { - const PLUS_TOKENS = new Set(["+", "++"]); - const MINUS_TOKENS = new Set(["-", "--"]); - - return !( - PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) || - MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value) - ); - } - if (leftToken.type === "Punctuator" && leftToken.value === "/") { - return !["Block", "Line", "RegularExpression"].includes(rightToken.type); - } - return true; - } - - if ( - leftToken.type === "String" || rightToken.type === "String" || - leftToken.type === "Template" || rightToken.type === "Template" - ) { - return true; - } - - if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) { - return true; - } - - if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") { - return true; - } - - if (rightToken.type === "PrivateIdentifier") { - return true; - } - - return false; - }, - - /** - * Get the `loc` object of a given name in a `/*globals` directive comment. - * @param {SourceCode} sourceCode The source code to convert index to loc. - * @param {Comment} comment The `/*globals` directive comment which include the name. - * @param {string} name The name to find. - * @returns {SourceLocation} The `loc` object. - */ - getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) { - const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu"); - - // To ignore the first text "global". - namePattern.lastIndex = comment.value.indexOf("global") + 6; - - // Search a given variable name. - const match = namePattern.exec(comment.value); - - // Convert the index to loc. - const start = sourceCode.getLocFromIndex( - comment.range[0] + - "/*".length + - (match ? match.index + 1 : 0) - ); - const end = { - line: start.line, - column: start.column + (match ? name.length : 1) - }; - - return { start, end }; - }, - - /** - * Determines whether the given raw string contains an octal escape sequence - * or a non-octal decimal escape sequence ("\8", "\9"). - * - * "\1", "\2" ... "\7", "\8", "\9" - * "\00", "\01" ... "\07", "\08", "\09" - * - * "\0", when not followed by a digit, is not an octal escape sequence. - * @param {string} rawString A string in its raw representation. - * @returns {boolean} `true` if the string contains at least one octal escape sequence - * or at least one non-octal decimal escape sequence. - */ - hasOctalOrNonOctalDecimalEscapeSequence(rawString) { - return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); - }, - - /** - * Determines whether the given node is a template literal without expressions. - * @param {ASTNode} node Node to check. - * @returns {boolean} True if the node is a template literal without expressions. - */ - isStaticTemplateLiteral(node) { - return node.type === "TemplateLiteral" && node.expressions.length === 0; - }, - - /** - * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code. - * The braces, which make the given block body, are necessary in either of the following situations: - * - * 1. The statement is a lexical declaration. - * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace: - * - * if (a) { - * if (b) - * foo(); - * } - * else - * bar(); - * - * if (a) - * while (b) - * while (c) { - * while (d) - * if (e) - * while(f) - * foo(); - * } - * else - * bar(); - * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements. - * @param {SourceCode} sourceCode The source code - * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content) - * would change the semantics of the code or produce a syntax error. - */ - areBracesNecessary(node, sourceCode) { - - /** - * Determines if the given node is a lexical declaration (let, const, function, or class) - * @param {ASTNode} nodeToCheck The node to check - * @returns {boolean} True if the node is a lexical declaration - * @private - */ - function isLexicalDeclaration(nodeToCheck) { - if (nodeToCheck.type === "VariableDeclaration") { - return nodeToCheck.kind === "const" || nodeToCheck.kind === "let"; - } - - return nodeToCheck.type === "FunctionDeclaration" || nodeToCheck.type === "ClassDeclaration"; - } - - - /** - * Checks if the given token is an `else` token or not. - * @param {Token} token The token to check. - * @returns {boolean} `true` if the token is an `else` token. - */ - function isElseKeywordToken(token) { - return token.value === "else" && token.type === "Keyword"; - } - - /** - * Determines whether the given node has an `else` keyword token as the first token after. - * @param {ASTNode} nodeToCheck The node to check. - * @returns {boolean} `true` if the node is followed by an `else` keyword token. - */ - function isFollowedByElseKeyword(nodeToCheck) { - const nextToken = sourceCode.getTokenAfter(nodeToCheck); - - return Boolean(nextToken) && isElseKeywordToken(nextToken); - } - - /** - * Determines whether the code represented by the given node contains an `if` statement - * that would become associated with an `else` keyword directly appended to that code. - * - * Examples where it returns `true`: - * - * if (a) - * foo(); - * - * if (a) { - * foo(); - * } - * - * if (a) - * foo(); - * else if (b) - * bar(); - * - * while (a) - * if (b) - * if(c) - * foo(); - * else - * bar(); - * - * Examples where it returns `false`: - * - * if (a) - * foo(); - * else - * bar(); - * - * while (a) { - * if (b) - * if(c) - * foo(); - * else - * bar(); - * } - * - * while (a) - * if (b) { - * if(c) - * foo(); - * } - * else - * bar(); - * @param {ASTNode} nodeToCheck Node representing the code to check. - * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code. - */ - function hasUnsafeIf(nodeToCheck) { - switch (nodeToCheck.type) { - case "IfStatement": - if (!nodeToCheck.alternate) { - return true; - } - return hasUnsafeIf(nodeToCheck.alternate); - case "ForStatement": - case "ForInStatement": - case "ForOfStatement": - case "LabeledStatement": - case "WithStatement": - case "WhileStatement": - return hasUnsafeIf(nodeToCheck.body); - default: - return false; - } - } - - const statement = node.body[0]; - - return isLexicalDeclaration(statement) || - hasUnsafeIf(statement) && isFollowedByElseKeyword(node); - }, - - isReferenceToGlobalVariable, - isLogicalExpression, - isCoalesceExpression, - isMixedLogicalAndCoalesceExpressions, - isNullLiteral, - getStaticStringValue, - getStaticPropertyName, - skipChainExpression, - isSpecificId, - isSpecificMemberAccess, - equalLiteralValue, - isSameReference, - isLogicalAssignmentOperator, - getSwitchCaseColonToken, - getModuleExportName, - isConstant, - isTopLevelExpressionStatement, - isDirective, - isStartOfExpressionStatement, - needsPrecedingSemicolon, - isImportAttributeKey + COMMENTS_IGNORE_PATTERN, + LINEBREAKS, + LINEBREAK_MATCHER: lineBreakPattern, + SHEBANG_MATCHER: shebangPattern, + STATEMENT_LIST_PARENTS, + ECMASCRIPT_GLOBALS, + + /** + * Determines whether two adjacent tokens are on the same line. + * @param {Object} left The left token object. + * @param {Object} right The right token object. + * @returns {boolean} Whether or not the tokens are on the same line. + * @public + */ + isTokenOnSameLine(left, right) { + return left.loc.end.line === right.loc.start.line; + }, + + isNullOrUndefined, + isCallee, + isES5Constructor, + getUpperFunction, + isFunction, + isLoop, + isInLoop, + isArrayFromMethod, + isParenthesised, + createGlobalLinebreakMatcher, + equalTokens, + + isArrowToken, + isClosingBraceToken, + isClosingBracketToken, + isClosingParenToken, + isColonToken, + isCommaToken, + isCommentToken, + isDotToken, + isQuestionDotToken, + isKeywordToken, + isNotClosingBraceToken: negate(isClosingBraceToken), + isNotClosingBracketToken: negate(isClosingBracketToken), + isNotClosingParenToken: negate(isClosingParenToken), + isNotColonToken: negate(isColonToken), + isNotCommaToken: negate(isCommaToken), + isNotDotToken: negate(isDotToken), + isNotQuestionDotToken: negate(isQuestionDotToken), + isNotOpeningBraceToken: negate(isOpeningBraceToken), + isNotOpeningBracketToken: negate(isOpeningBracketToken), + isNotOpeningParenToken: negate(isOpeningParenToken), + isNotSemicolonToken: negate(isSemicolonToken), + isOpeningBraceToken, + isOpeningBracketToken, + isOpeningParenToken, + isSemicolonToken, + isEqToken, + + /** + * Checks whether or not a given node is a string literal. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is a string literal. + */ + isStringLiteral(node) { + return ( + (node.type === "Literal" && typeof node.value === "string") || + node.type === "TemplateLiteral" + ); + }, + + /** + * Checks whether a given node is a breakable statement or not. + * The node is breakable if the node is one of the following type: + * + * - DoWhileStatement + * - ForInStatement + * - ForOfStatement + * - ForStatement + * - SwitchStatement + * - WhileStatement + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if the node is breakable. + */ + isBreakableStatement(node) { + return breakableTypePattern.test(node.type); + }, + + /** + * Gets references which are non initializer and writable. + * @param {Reference[]} references An array of references. + * @returns {Reference[]} An array of only references which are non initializer and writable. + * @public + */ + getModifyingReferences(references) { + return references.filter(isModifyingReference); + }, + + /** + * Validate that a string passed in is surrounded by the specified character + * @param {string} val The text to check. + * @param {string} character The character to see if it's surrounded by. + * @returns {boolean} True if the text is surrounded by the character, false if not. + * @private + */ + isSurroundedBy(val, character) { + return val[0] === character && val.at(-1) === character; + }, + + /** + * Returns whether the provided node is an ESLint directive comment or not + * @param {Line|Block} node The comment token to be checked + * @returns {boolean} `true` if the node is an ESLint directive comment + */ + isDirectiveComment(node) { + const comment = node.value.trim(); + + return ( + (node.type === "Line" && comment.startsWith("eslint-")) || + (node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)) + ); + }, + + /** + * Gets the trailing statement of a given node. + * + * if (code) + * consequent; + * + * When taking this `IfStatement`, returns `consequent;` statement. + * @param {ASTNode} A node to get. + * @returns {ASTNode|null} The trailing statement's node. + */ + getTrailingStatement: esutils.ast.trailingStatement, + + /** + * Finds the variable by a given name in a given scope and its upper scopes. + * @param {eslint-scope.Scope} initScope A scope to start find. + * @param {string} name A variable name to find. + * @returns {eslint-scope.Variable|null} A found variable or `null`. + */ + getVariableByName(initScope, name) { + let scope = initScope; + + while (scope) { + const variable = scope.set.get(name); + + if (variable) { + return variable; + } + + scope = scope.upper; + } + + return null; + }, + + /** + * Checks whether or not a given function node is the default `this` binding. + * + * First, this checks the node: + * + * - The given node is not in `PropertyDefinition#value` position. + * - The given node is not `StaticBlock`. + * - The function name does not start with uppercase. It's a convention to capitalize the names + * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`. + * - The function does not have a JSDoc comment that has a @this tag. + * + * Next, this checks the location of the node. + * If the location is below, this judges `this` is valid. + * + * - The location is not on an object literal. + * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous + * functions only, as the name of the variable is considered to be the name of the function in this case. + * This check is not performed if `capIsConstructor` is set to `false`. + * - The location is not on an ES2015 class. + * - Its `bind`/`call`/`apply` method is not called directly. + * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. + * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock` + * or any expression that is `PropertyDefinition#value` node. + * @param {SourceCode} sourceCode A SourceCode instance to get comments. + * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts + * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors. + * @returns {boolean} The function node is the default `this` binding. + */ + isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) { + /* + * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers. + * Therefore, A expression node at `PropertyDefinition#value` is a function. + * In this case, `this` is always not default binding. + */ + if ( + node.parent.type === "PropertyDefinition" && + node.parent.value === node + ) { + return false; + } + + // Class static blocks are implicit functions. In this case, `this` is always not default binding. + if (node.type === "StaticBlock") { + return false; + } + + if ( + (capIsConstructor && isES5Constructor(node)) || + hasJSDocThisTag(node, sourceCode) + ) { + return false; + } + const isAnonymous = node.id === null; + let currentNode = node; + + while (currentNode) { + const parent = currentNode.parent; + + switch (parent.type) { + /* + * Looks up the destination. + * e.g., obj.foo = nativeFoo || function foo() { ... }; + */ + case "LogicalExpression": + case "ConditionalExpression": + case "ChainExpression": + currentNode = parent; + break; + + /* + * If the upper function is IIFE, checks the destination of the return value. + * e.g. + * obj.foo = (function() { + * // setup... + * return function foo() { ... }; + * })(); + * obj.foo = (() => + * function foo() { ... } + * )(); + */ + case "ReturnStatement": { + const func = getUpperFunction(parent); + + if (func === null || !isCallee(func)) { + return true; + } + currentNode = func.parent; + break; + } + case "ArrowFunctionExpression": + if (currentNode !== parent.body || !isCallee(parent)) { + return true; + } + currentNode = parent.parent; + break; + + /* + * e.g. + * var obj = { foo() { ... } }; + * var obj = { foo: function() { ... } }; + * class A { constructor() { ... } } + * class A { foo() { ... } } + * class A { get foo() { ... } } + * class A { set foo() { ... } } + * class A { static foo() { ... } } + * class A { foo = function() { ... } } + */ + case "Property": + case "PropertyDefinition": + case "MethodDefinition": + return parent.value !== currentNode; + + /* + * e.g. + * obj.foo = function foo() { ... }; + * Foo = function() { ... }; + * [obj.foo = function foo() { ... }] = a; + * [Foo = function() { ... }] = a; + */ + case "AssignmentExpression": + case "AssignmentPattern": + if (parent.left.type === "MemberExpression") { + return false; + } + if ( + capIsConstructor && + isAnonymous && + parent.left.type === "Identifier" && + startsWithUpperCase(parent.left.name) + ) { + return false; + } + return true; + + /* + * e.g. + * var Foo = function() { ... }; + */ + case "VariableDeclarator": + return !( + capIsConstructor && + isAnonymous && + parent.init === currentNode && + parent.id.type === "Identifier" && + startsWithUpperCase(parent.id.name) + ); + + /* + * e.g. + * var foo = function foo() { ... }.bind(obj); + * (function foo() { ... }).call(obj); + * (function foo() { ... }).apply(obj, []); + */ + case "MemberExpression": + if ( + parent.object === currentNode && + isSpecificMemberAccess( + parent, + null, + bindOrCallOrApplyPattern, + ) + ) { + const maybeCalleeNode = + parent.parent.type === "ChainExpression" + ? parent.parent + : parent; + + return !( + isCallee(maybeCalleeNode) && + maybeCalleeNode.parent.arguments.length >= 1 && + !isNullOrUndefined( + maybeCalleeNode.parent.arguments[0], + ) + ); + } + return true; + + /* + * e.g. + * Reflect.apply(function() {}, obj, []); + * Array.from([], function() {}, obj); + * list.forEach(function() {}, obj); + */ + case "CallExpression": + if (isReflectApply(parent.callee)) { + return ( + parent.arguments.length !== 3 || + parent.arguments[0] !== currentNode || + isNullOrUndefined(parent.arguments[1]) + ); + } + if (isArrayFromMethod(parent.callee)) { + return ( + parent.arguments.length !== 3 || + parent.arguments[1] !== currentNode || + isNullOrUndefined(parent.arguments[2]) + ); + } + if (isMethodWhichHasThisArg(parent.callee)) { + return ( + parent.arguments.length !== 2 || + parent.arguments[0] !== currentNode || + isNullOrUndefined(parent.arguments[1]) + ); + } + return true; + + // Otherwise `this` is default. + default: + return true; + } + } + + /* c8 ignore next */ + return true; + }, + + /** + * Get the precedence level based on the node type + * @param {ASTNode} node node to evaluate + * @returns {int} precedence level + * @private + */ + getPrecedence(node) { + switch (node.type) { + case "SequenceExpression": + return 0; + + case "AssignmentExpression": + case "ArrowFunctionExpression": + case "YieldExpression": + return 1; + + case "ConditionalExpression": + return 3; + + case "LogicalExpression": + switch (node.operator) { + case "||": + case "??": + return 4; + case "&&": + return 5; + + // no default + } + + /* falls through */ + + case "BinaryExpression": + switch (node.operator) { + case "|": + return 6; + case "^": + return 7; + case "&": + return 8; + case "==": + case "!=": + case "===": + case "!==": + return 9; + case "<": + case "<=": + case ">": + case ">=": + case "in": + case "instanceof": + return 10; + case "<<": + case ">>": + case ">>>": + return 11; + case "+": + case "-": + return 12; + case "*": + case "/": + case "%": + return 13; + case "**": + return 15; + + // no default + } + + /* falls through */ + + case "UnaryExpression": + case "AwaitExpression": + return 16; + + case "UpdateExpression": + return 17; + + case "CallExpression": + case "ChainExpression": + case "ImportExpression": + return 18; + + case "NewExpression": + return 19; + + default: + if (node.type in eslintVisitorKeys) { + return 20; + } + + /* + * if the node is not a standard node that we know about, then assume it has the lowest precedence + * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of + * unwrapping them and potentially changing the meaning of the code or introducing a syntax error. + */ + return -1; + } + }, + + /** + * Checks whether the given node is an empty block node or not. + * @param {ASTNode|null} node The node to check. + * @returns {boolean} `true` if the node is an empty block. + */ + isEmptyBlock(node) { + return Boolean( + node && node.type === "BlockStatement" && node.body.length === 0, + ); + }, + + /** + * Checks whether the given node is an empty function node or not. + * @param {ASTNode|null} node The node to check. + * @returns {boolean} `true` if the node is an empty function. + */ + isEmptyFunction(node) { + return isFunction(node) && module.exports.isEmptyBlock(node.body); + }, + + /** + * Get directives from directive prologue of a Program or Function node. + * @param {ASTNode} node The node to check. + * @returns {ASTNode[]} The directives found in the directive prologue. + */ + getDirectivePrologue(node) { + const directives = []; + + // Directive prologues only occur at the top of files or functions. + if ( + node.type === "Program" || + node.type === "FunctionDeclaration" || + node.type === "FunctionExpression" || + /* + * Do not check arrow functions with implicit return. + * `() => "use strict";` returns the string `"use strict"`. + */ + (node.type === "ArrowFunctionExpression" && + node.body.type === "BlockStatement") + ) { + const statements = + node.type === "Program" ? node.body : node.body.body; + + for (const statement of statements) { + if ( + statement.type === "ExpressionStatement" && + statement.expression.type === "Literal" + ) { + directives.push(statement); + } else { + break; + } + } + } + + return directives; + }, + + /** + * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added + * after the node will be parsed as a decimal point, rather than a property-access dot. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if this node is a decimal integer. + * @example + * + * 0 // true + * 5 // true + * 50 // true + * 5_000 // true + * 1_234_56 // true + * 08 // true + * 0192 // true + * 5. // false + * .5 // false + * 5.0 // false + * 5.00_00 // false + * 05 // false + * 0x5 // false + * 0b101 // false + * 0b11_01 // false + * 0o5 // false + * 5e0 // false + * 5e1_000 // false + * 5n // false + * 1_000n // false + * "5" // false + * + */ + isDecimalInteger(node) { + return ( + node.type === "Literal" && + typeof node.value === "number" && + DECIMAL_INTEGER_PATTERN.test(node.raw) + ); + }, + + /** + * Determines whether this token is a decimal integer numeric token. + * This is similar to isDecimalInteger(), but for tokens. + * @param {Token} token The token to check. + * @returns {boolean} `true` if this token is a decimal integer. + */ + isDecimalIntegerNumericToken(token) { + return ( + token.type === "Numeric" && + DECIMAL_INTEGER_PATTERN.test(token.value) + ); + }, + + /** + * Gets the name and kind of the given function node. + * + * - `function foo() {}` .................... `function 'foo'` + * - `(function foo() {})` .................. `function 'foo'` + * - `(function() {})` ...................... `function` + * - `function* foo() {}` ................... `generator function 'foo'` + * - `(function* foo() {})` ................. `generator function 'foo'` + * - `(function*() {})` ..................... `generator function` + * - `() => {}` ............................. `arrow function` + * - `async () => {}` ....................... `async arrow function` + * - `({ foo: function foo() {} })` ......... `method 'foo'` + * - `({ foo: function() {} })` ............. `method 'foo'` + * - `({ ['foo']: function() {} })` ......... `method 'foo'` + * - `({ [foo]: function() {} })` ........... `method` + * - `({ foo() {} })` ....................... `method 'foo'` + * - `({ foo: function* foo() {} })` ........ `generator method 'foo'` + * - `({ foo: function*() {} })` ............ `generator method 'foo'` + * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` + * - `({ [foo]: function*() {} })` .......... `generator method` + * - `({ *foo() {} })` ...................... `generator method 'foo'` + * - `({ foo: async function foo() {} })` ... `async method 'foo'` + * - `({ foo: async function() {} })` ....... `async method 'foo'` + * - `({ ['foo']: async function() {} })` ... `async method 'foo'` + * - `({ [foo]: async function() {} })` ..... `async method` + * - `({ async foo() {} })` ................. `async method 'foo'` + * - `({ get foo() {} })` ................... `getter 'foo'` + * - `({ set foo(a) {} })` .................. `setter 'foo'` + * - `class A { constructor() {} }` ......... `constructor` + * - `class A { foo() {} }` ................. `method 'foo'` + * - `class A { *foo() {} }` ................ `generator method 'foo'` + * - `class A { async foo() {} }` ........... `async method 'foo'` + * - `class A { ['foo']() {} }` ............. `method 'foo'` + * - `class A { *['foo']() {} }` ............ `generator method 'foo'` + * - `class A { async ['foo']() {} }` ....... `async method 'foo'` + * - `class A { [foo]() {} }` ............... `method` + * - `class A { *[foo]() {} }` .............. `generator method` + * - `class A { async [foo]() {} }` ......... `async method` + * - `class A { get foo() {} }` ............. `getter 'foo'` + * - `class A { set foo(a) {} }` ............ `setter 'foo'` + * - `class A { static foo() {} }` .......... `static method 'foo'` + * - `class A { static *foo() {} }` ......... `static generator method 'foo'` + * - `class A { static async foo() {} }` .... `static async method 'foo'` + * - `class A { static get foo() {} }` ...... `static getter 'foo'` + * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` + * - `class A { foo = () => {}; }` .......... `method 'foo'` + * - `class A { foo = function() {}; }` ..... `method 'foo'` + * - `class A { foo = function bar() {}; }` . `method 'foo'` + * - `class A { static foo = () => {}; }` ... `static method 'foo'` + * - `class A { '#foo' = () => {}; }` ....... `method '#foo'` + * - `class A { #foo = () => {}; }` ......... `private method #foo` + * - `class A { static #foo = () => {}; }` .. `static private method #foo` + * - `class A { '#foo'() {} }` .............. `method '#foo'` + * - `class A { #foo() {} }` ................ `private method #foo` + * - `class A { static #foo() {} }` ......... `static private method #foo` + * @param {ASTNode} node The function node to get. + * @returns {string} The name and kind of the function node. + */ + getFunctionNameWithKind(node) { + const parent = node.parent; + const tokens = []; + + if ( + parent.type === "MethodDefinition" || + parent.type === "PropertyDefinition" + ) { + // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features + if (parent.static) { + tokens.push("static"); + } + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push("private"); + } + } + if (node.async) { + tokens.push("async"); + } + if (node.generator) { + tokens.push("generator"); + } + + if (parent.type === "Property" || parent.type === "MethodDefinition") { + if (parent.kind === "constructor") { + return "constructor"; + } + if (parent.kind === "get") { + tokens.push("getter"); + } else if (parent.kind === "set") { + tokens.push("setter"); + } else { + tokens.push("method"); + } + } else if (parent.type === "PropertyDefinition") { + tokens.push("method"); + } else { + if (node.type === "ArrowFunctionExpression") { + tokens.push("arrow"); + } + tokens.push("function"); + } + + if ( + parent.type === "Property" || + parent.type === "MethodDefinition" || + parent.type === "PropertyDefinition" + ) { + if (!parent.computed && parent.key.type === "PrivateIdentifier") { + tokens.push(`#${parent.key.name}`); + } else { + const name = getStaticPropertyName(parent); + + if (name !== null) { + tokens.push(`'${name}'`); + } else if (node.id) { + tokens.push(`'${node.id.name}'`); + } + } + } else if (node.id) { + tokens.push(`'${node.id.name}'`); + } + + return tokens.join(" "); + }, + + /** + * Gets the location of the given function node for reporting. + * + * - `function foo() {}` + * ^^^^^^^^^^^^ + * - `(function foo() {})` + * ^^^^^^^^^^^^ + * - `(function() {})` + * ^^^^^^^^ + * - `function* foo() {}` + * ^^^^^^^^^^^^^ + * - `(function* foo() {})` + * ^^^^^^^^^^^^^ + * - `(function*() {})` + * ^^^^^^^^^ + * - `() => {}` + * ^^ + * - `async () => {}` + * ^^ + * - `({ foo: function foo() {} })` + * ^^^^^^^^^^^^^^^^^ + * - `({ foo: function() {} })` + * ^^^^^^^^^^^^^ + * - `({ ['foo']: function() {} })` + * ^^^^^^^^^^^^^^^^^ + * - `({ [foo]: function() {} })` + * ^^^^^^^^^^^^^^^ + * - `({ foo() {} })` + * ^^^ + * - `({ foo: function* foo() {} })` + * ^^^^^^^^^^^^^^^^^^ + * - `({ foo: function*() {} })` + * ^^^^^^^^^^^^^^ + * - `({ ['foo']: function*() {} })` + * ^^^^^^^^^^^^^^^^^^ + * - `({ [foo]: function*() {} })` + * ^^^^^^^^^^^^^^^^ + * - `({ *foo() {} })` + * ^^^^ + * - `({ foo: async function foo() {} })` + * ^^^^^^^^^^^^^^^^^^^^^^^ + * - `({ foo: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^ + * - `({ ['foo']: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^^^^^ + * - `({ [foo]: async function() {} })` + * ^^^^^^^^^^^^^^^^^^^^^ + * - `({ async foo() {} })` + * ^^^^^^^^^ + * - `({ get foo() {} })` + * ^^^^^^^ + * - `({ set foo(a) {} })` + * ^^^^^^^ + * - `class A { constructor() {} }` + * ^^^^^^^^^^^ + * - `class A { foo() {} }` + * ^^^ + * - `class A { *foo() {} }` + * ^^^^ + * - `class A { async foo() {} }` + * ^^^^^^^^^ + * - `class A { ['foo']() {} }` + * ^^^^^^^ + * - `class A { *['foo']() {} }` + * ^^^^^^^^ + * - `class A { async ['foo']() {} }` + * ^^^^^^^^^^^^^ + * - `class A { [foo]() {} }` + * ^^^^^ + * - `class A { *[foo]() {} }` + * ^^^^^^ + * - `class A { async [foo]() {} }` + * ^^^^^^^^^^^ + * - `class A { get foo() {} }` + * ^^^^^^^ + * - `class A { set foo(a) {} }` + * ^^^^^^^ + * - `class A { static foo() {} }` + * ^^^^^^^^^^ + * - `class A { static *foo() {} }` + * ^^^^^^^^^^^ + * - `class A { static async foo() {} }` + * ^^^^^^^^^^^^^^^^ + * - `class A { static get foo() {} }` + * ^^^^^^^^^^^^^^ + * - `class A { static set foo(a) {} }` + * ^^^^^^^^^^^^^^ + * - `class A { foo = function() {} }` + * ^^^^^^^^^^^^^^ + * - `class A { static foo = function() {} }` + * ^^^^^^^^^^^^^^^^^^^^^ + * - `class A { foo = (a, b) => {} }` + * ^^^^^^ + * @param {ASTNode} node The function node to get. + * @param {SourceCode} sourceCode The source code object to get tokens. + * @returns {string} The location of the function node for reporting. + */ + getFunctionHeadLoc(node, sourceCode) { + const parent = node.parent; + let start; + let end; + + if ( + parent.type === "Property" || + parent.type === "MethodDefinition" || + parent.type === "PropertyDefinition" + ) { + start = parent.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } else if (node.type === "ArrowFunctionExpression") { + const arrowToken = sourceCode.getTokenBefore( + node.body, + isArrowToken, + ); + + start = arrowToken.loc.start; + end = arrowToken.loc.end; + } else { + start = node.loc.start; + end = getOpeningParenOfParams(node, sourceCode).loc.start; + } + + return { + start: Object.assign({}, start), + end: Object.assign({}, end), + }; + }, + + /** + * Gets next location when the result is not out of bound, otherwise returns null. + * + * Assumptions: + * + * - The given location represents a valid location in the given source code. + * - Columns are 0-based. + * - Lines are 1-based. + * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location. + * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end. + * The start (column 0) of that extra line is considered to be a valid location. + * + * Examples of successive locations (line, column): + * + * code: foo + * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null + * + * code: foo + * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null + * + * code: foo + * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null + * + * code: ab + * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null + * + * code: ab + * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null + * + * code: ab + * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null + * + * code: a + * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null + * + * code: + * locations: (1, 0) -> (2, 0) -> null + * + * code: + * locations: (1, 0) -> null + * @param {SourceCode} sourceCode The sourceCode + * @param {{line: number, column: number}} location The location + * @returns {{line: number, column: number} | null} Next location + */ + getNextLocation(sourceCode, { line, column }) { + if (column < sourceCode.lines[line - 1].length) { + return { + line, + column: column + 1, + }; + } + + if (line < sourceCode.lines.length) { + return { + line: line + 1, + column: 0, + }; + } + + return null; + }, + + /** + * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses + * surrounding the node. + * @param {SourceCode} sourceCode The source code object + * @param {ASTNode} node An expression node + * @returns {string} The text representing the node, with all surrounding parentheses included + */ + getParenthesisedText(sourceCode, node) { + let leftToken = sourceCode.getFirstToken(node); + let rightToken = sourceCode.getLastToken(node); + + while ( + sourceCode.getTokenBefore(leftToken) && + sourceCode.getTokenBefore(leftToken).type === "Punctuator" && + sourceCode.getTokenBefore(leftToken).value === "(" && + sourceCode.getTokenAfter(rightToken) && + sourceCode.getTokenAfter(rightToken).type === "Punctuator" && + sourceCode.getTokenAfter(rightToken).value === ")" + ) { + leftToken = sourceCode.getTokenBefore(leftToken); + rightToken = sourceCode.getTokenAfter(rightToken); + } + + return sourceCode + .getText() + .slice(leftToken.range[0], rightToken.range[1]); + }, + + /** + * Determine if a node has a possibility to be an Error object + * @param {ASTNode} node ASTNode to check + * @returns {boolean} True if there is a chance it contains an Error obj + */ + couldBeError(node) { + switch (node.type) { + case "Identifier": + case "CallExpression": + case "NewExpression": + case "MemberExpression": + case "TaggedTemplateExpression": + case "YieldExpression": + case "AwaitExpression": + case "ChainExpression": + return true; // possibly an error object. + + case "AssignmentExpression": + if (["=", "&&="].includes(node.operator)) { + return module.exports.couldBeError(node.right); + } + + if (["||=", "??="].includes(node.operator)) { + return ( + module.exports.couldBeError(node.left) || + module.exports.couldBeError(node.right) + ); + } + + /** + * All other assignment operators are mathematical assignment operators (arithmetic or bitwise). + * An assignment expression with a mathematical operator can either evaluate to a primitive value, + * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object. + */ + return false; + + case "SequenceExpression": { + const exprs = node.expressions; + + return ( + exprs.length !== 0 && + module.exports.couldBeError(exprs.at(-1)) + ); + } + + case "LogicalExpression": + /* + * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it + * doesn't short-circuit, it takes the value from the right side, so the right side must always be + * a plausible error. A future improvement could verify that the left side could be truthy by + * excluding falsy literals. + */ + if (node.operator === "&&") { + return module.exports.couldBeError(node.right); + } + + return ( + module.exports.couldBeError(node.left) || + module.exports.couldBeError(node.right) + ); + + case "ConditionalExpression": + return ( + module.exports.couldBeError(node.consequent) || + module.exports.couldBeError(node.alternate) + ); + + default: + return false; + } + }, + + /** + * Check if a given node is a numeric literal or not. + * @param {ASTNode} node The node to check. + * @returns {boolean} `true` if the node is a number or bigint literal. + */ + isNumericLiteral(node) { + return ( + node.type === "Literal" && + (typeof node.value === "number" || Boolean(node.bigint)) + ); + }, + + /** + * Determines whether two tokens can safely be placed next to each other without merging into a single token + * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. + * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. + * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed + * next to each other, behavior is undefined (although it should return `true` in most cases). + */ + canTokensBeAdjacent(leftValue, rightValue) { + const espreeOptions = { + ecmaVersion: espree.latestEcmaVersion, + comment: true, + range: true, + }; + + let leftToken; + + if (typeof leftValue === "string") { + let tokens; + + try { + tokens = espree.tokenize(leftValue, espreeOptions); + } catch { + return false; + } + + const comments = tokens.comments; + + leftToken = tokens.at(-1); + if (comments.length) { + const lastComment = comments.at(-1); + + if (!leftToken || lastComment.range[0] > leftToken.range[0]) { + leftToken = lastComment; + } + } + } else { + leftToken = leftValue; + } + + /* + * If a hashbang comment was passed as a token object from SourceCode, + * its type will be "Shebang" because of the way ESLint itself handles hashbangs. + * If a hashbang comment was passed in a string and then tokenized in this function, + * its type will be "Hashbang" because of the way Espree tokenizes hashbangs. + */ + if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") { + return false; + } + + let rightToken; + + if (typeof rightValue === "string") { + let tokens; + + try { + tokens = espree.tokenize(rightValue, espreeOptions); + } catch { + return false; + } + + const comments = tokens.comments; + + rightToken = tokens[0]; + if (comments.length) { + const firstComment = comments[0]; + + if ( + !rightToken || + firstComment.range[0] < rightToken.range[0] + ) { + rightToken = firstComment; + } + } + } else { + rightToken = rightValue; + } + + if ( + leftToken.type === "Punctuator" || + rightToken.type === "Punctuator" + ) { + if ( + leftToken.type === "Punctuator" && + rightToken.type === "Punctuator" + ) { + const PLUS_TOKENS = new Set(["+", "++"]); + const MINUS_TOKENS = new Set(["-", "--"]); + + return !( + (PLUS_TOKENS.has(leftToken.value) && + PLUS_TOKENS.has(rightToken.value)) || + (MINUS_TOKENS.has(leftToken.value) && + MINUS_TOKENS.has(rightToken.value)) + ); + } + if (leftToken.type === "Punctuator" && leftToken.value === "/") { + return !["Block", "Line", "RegularExpression"].includes( + rightToken.type, + ); + } + return true; + } + + if ( + leftToken.type === "String" || + rightToken.type === "String" || + leftToken.type === "Template" || + rightToken.type === "Template" + ) { + return true; + } + + if ( + leftToken.type !== "Numeric" && + rightToken.type === "Numeric" && + rightToken.value.startsWith(".") + ) { + return true; + } + + if ( + leftToken.type === "Block" || + rightToken.type === "Block" || + rightToken.type === "Line" + ) { + return true; + } + + if (rightToken.type === "PrivateIdentifier") { + return true; + } + + return false; + }, + + /** + * Get the `loc` object of a given name in a `/*globals` directive comment. + * @param {SourceCode} sourceCode The source code to convert index to loc. + * @param {Comment} comment The `/*globals` directive comment which include the name. + * @param {string} name The name to find. + * @returns {SourceLocation} The `loc` object. + */ + getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) { + const namePattern = new RegExp( + `[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, + "gu", + ); + + // To ignore the first text "global". + namePattern.lastIndex = comment.value.indexOf("global") + 6; + + // Search a given variable name. + const match = namePattern.exec(comment.value); + + // Convert the index to loc. + const start = sourceCode.getLocFromIndex( + comment.range[0] + "/*".length + (match ? match.index + 1 : 0), + ); + const end = { + line: start.line, + column: start.column + (match ? name.length : 1), + }; + + return { start, end }; + }, + + /** + * Determines whether the given raw string contains an octal escape sequence + * or a non-octal decimal escape sequence ("\8", "\9"). + * + * "\1", "\2" ... "\7", "\8", "\9" + * "\00", "\01" ... "\07", "\08", "\09" + * + * "\0", when not followed by a digit, is not an octal escape sequence. + * @param {string} rawString A string in its raw representation. + * @returns {boolean} `true` if the string contains at least one octal escape sequence + * or at least one non-octal decimal escape sequence. + */ + hasOctalOrNonOctalDecimalEscapeSequence(rawString) { + return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString); + }, + + /** + * Determines whether the given node is a template literal without expressions. + * @param {ASTNode} node Node to check. + * @returns {boolean} True if the node is a template literal without expressions. + */ + isStaticTemplateLiteral(node) { + return node.type === "TemplateLiteral" && node.expressions.length === 0; + }, + + /** + * Determines whether the existing curly braces around the single statement are necessary to preserve the semantics of the code. + * The braces, which make the given block body, are necessary in either of the following situations: + * + * 1. The statement is a lexical declaration. + * 2. Without the braces, an `if` within the statement would become associated with an `else` after the closing brace: + * + * if (a) { + * if (b) + * foo(); + * } + * else + * bar(); + * + * if (a) + * while (b) + * while (c) { + * while (d) + * if (e) + * while(f) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} node `BlockStatement` body with exactly one statement directly inside. The statement can have its own nested statements. + * @param {SourceCode} sourceCode The source code + * @returns {boolean} `true` if the braces are necessary - removing them (replacing the given `BlockStatement` body with its single statement content) + * would change the semantics of the code or produce a syntax error. + */ + areBracesNecessary(node, sourceCode) { + /** + * Determines if the given node is a lexical declaration (let, const, function, or class) + * @param {ASTNode} nodeToCheck The node to check + * @returns {boolean} True if the node is a lexical declaration + * @private + */ + function isLexicalDeclaration(nodeToCheck) { + if (nodeToCheck.type === "VariableDeclaration") { + return ( + nodeToCheck.kind === "const" || nodeToCheck.kind === "let" + ); + } + + return ( + nodeToCheck.type === "FunctionDeclaration" || + nodeToCheck.type === "ClassDeclaration" + ); + } + + /** + * Checks if the given token is an `else` token or not. + * @param {Token} token The token to check. + * @returns {boolean} `true` if the token is an `else` token. + */ + function isElseKeywordToken(token) { + return token.value === "else" && token.type === "Keyword"; + } + + /** + * Determines whether the given node has an `else` keyword token as the first token after. + * @param {ASTNode} nodeToCheck The node to check. + * @returns {boolean} `true` if the node is followed by an `else` keyword token. + */ + function isFollowedByElseKeyword(nodeToCheck) { + const nextToken = sourceCode.getTokenAfter(nodeToCheck); + + return Boolean(nextToken) && isElseKeywordToken(nextToken); + } + + /** + * Determines whether the code represented by the given node contains an `if` statement + * that would become associated with an `else` keyword directly appended to that code. + * + * Examples where it returns `true`: + * + * if (a) + * foo(); + * + * if (a) { + * foo(); + * } + * + * if (a) + * foo(); + * else if (b) + * bar(); + * + * while (a) + * if (b) + * if(c) + * foo(); + * else + * bar(); + * + * Examples where it returns `false`: + * + * if (a) + * foo(); + * else + * bar(); + * + * while (a) { + * if (b) + * if(c) + * foo(); + * else + * bar(); + * } + * + * while (a) + * if (b) { + * if(c) + * foo(); + * } + * else + * bar(); + * @param {ASTNode} nodeToCheck Node representing the code to check. + * @returns {boolean} `true` if an `if` statement within the code would become associated with an `else` appended to that code. + */ + function hasUnsafeIf(nodeToCheck) { + switch (nodeToCheck.type) { + case "IfStatement": + if (!nodeToCheck.alternate) { + return true; + } + return hasUnsafeIf(nodeToCheck.alternate); + case "ForStatement": + case "ForInStatement": + case "ForOfStatement": + case "LabeledStatement": + case "WithStatement": + case "WhileStatement": + return hasUnsafeIf(nodeToCheck.body); + default: + return false; + } + } + + const statement = node.body[0]; + + return ( + isLexicalDeclaration(statement) || + (hasUnsafeIf(statement) && isFollowedByElseKeyword(node)) + ); + }, + + isReferenceToGlobalVariable, + isLogicalExpression, + isCoalesceExpression, + isMixedLogicalAndCoalesceExpressions, + isNullLiteral, + getStaticStringValue, + getStaticPropertyName, + skipChainExpression, + isSpecificId, + isSpecificMemberAccess, + equalLiteralValue, + isSameReference, + isLogicalAssignmentOperator, + getSwitchCaseColonToken, + getModuleExportName, + isConstant, + isTopLevelExpressionStatement, + isDirective, + isStartOfExpressionStatement, + needsPrecedingSemicolon, + isImportAttributeKey, }; diff --git a/lib/rules/utils/char-source.js b/lib/rules/utils/char-source.js index 70738625b945..6e9719caf082 100644 --- a/lib/rules/utils/char-source.js +++ b/lib/rules/utils/char-source.js @@ -10,53 +10,60 @@ * literal or template token. */ class CodeUnit { - constructor(start, source) { - this.start = start; - this.source = source; - } - - get end() { - return this.start + this.length; - } - - get length() { - return this.source.length; - } + constructor(start, source) { + this.start = start; + this.source = source; + } + + get end() { + return this.start + this.length; + } + + get length() { + return this.source.length; + } } /** * An object used to keep track of the position in a source text where the next characters will be read. */ class TextReader { - constructor(source) { - this.source = source; - this.pos = 0; - } - - /** - * Advances the reading position of the specified number of characters. - * @param {number} length Number of characters to advance. - * @returns {void} - */ - advance(length) { - this.pos += length; - } - - /** - * Reads characters from the source. - * @param {number} [offset=0] The offset where reading starts, relative to the current position. - * @param {number} [length=1] Number of characters to read. - * @returns {string} A substring of source characters. - */ - read(offset = 0, length = 1) { - const start = offset + this.pos; - - return this.source.slice(start, start + length); - } + constructor(source) { + this.source = source; + this.pos = 0; + } + + /** + * Advances the reading position of the specified number of characters. + * @param {number} length Number of characters to advance. + * @returns {void} + */ + advance(length) { + this.pos += length; + } + + /** + * Reads characters from the source. + * @param {number} [offset=0] The offset where reading starts, relative to the current position. + * @param {number} [length=1] Number of characters to read. + * @returns {string} A substring of source characters. + */ + read(offset = 0, length = 1) { + const start = offset + this.pos; + + return this.source.slice(start, start + length); + } } -const SIMPLE_ESCAPE_SEQUENCES = -{ __proto__: null, b: "\b", f: "\f", n: "\n", r: "\r", t: "\t", v: "\v" }; +const SIMPLE_ESCAPE_SEQUENCES = { + __proto__: null, + b: "\b", + f: "\f", + n: "\n", + r: "\r", + t: "\t", + v: "\v", +}; /** * Reads a hex escape sequence. @@ -65,11 +72,11 @@ const SIMPLE_ESCAPE_SEQUENCES = * @returns {string} A code unit. */ function readHexSequence(reader, length) { - const str = reader.read(0, length); - const charCode = parseInt(str, 16); + const str = reader.read(0, length); + const charCode = parseInt(str, 16); - reader.advance(length); - return String.fromCharCode(charCode); + reader.advance(length); + return String.fromCharCode(charCode); } /** @@ -78,18 +85,18 @@ function readHexSequence(reader, length) { * @returns {string} A code unit. */ function readUnicodeSequence(reader) { - const regExp = /\{(?[\dA-Fa-f]+)\}/uy; + const regExp = /\{(?[\dA-Fa-f]+)\}/uy; - regExp.lastIndex = reader.pos; - const match = regExp.exec(reader.source); + regExp.lastIndex = reader.pos; + const match = regExp.exec(reader.source); - if (match) { - const codePoint = parseInt(match.groups.hexDigits, 16); + if (match) { + const codePoint = parseInt(match.groups.hexDigits, 16); - reader.pos = regExp.lastIndex; - return String.fromCodePoint(codePoint); - } - return readHexSequence(reader, 4); + reader.pos = regExp.lastIndex; + return String.fromCodePoint(codePoint); + } + return readHexSequence(reader, 4); } /** @@ -99,12 +106,12 @@ function readUnicodeSequence(reader) { * @returns {string} A code unit. */ function readOctalSequence(reader, maxLength) { - const [octalStr] = reader.read(-1, maxLength).match(/^[0-7]+/u); + const [octalStr] = reader.read(-1, maxLength).match(/^[0-7]+/u); - reader.advance(octalStr.length - 1); - const octal = parseInt(octalStr, 8); + reader.advance(octalStr.length - 1); + const octal = parseInt(octalStr, 8); - return String.fromCharCode(octal); + return String.fromCharCode(octal); } /** @@ -113,42 +120,42 @@ function readOctalSequence(reader, maxLength) { * @returns {string} A string of zero, one or two code units. */ function readEscapeSequenceOrLineContinuation(reader) { - const char = reader.read(1); - - reader.advance(2); - const unitChar = SIMPLE_ESCAPE_SEQUENCES[char]; - - if (unitChar) { - return unitChar; - } - switch (char) { - case "x": - return readHexSequence(reader, 2); - case "u": - return readUnicodeSequence(reader); - case "\r": - if (reader.read() === "\n") { - reader.advance(1); - } - - // fallthrough - case "\n": - case "\u2028": - case "\u2029": - return ""; - case "0": - case "1": - case "2": - case "3": - return readOctalSequence(reader, 3); - case "4": - case "5": - case "6": - case "7": - return readOctalSequence(reader, 2); - default: - return char; - } + const char = reader.read(1); + + reader.advance(2); + const unitChar = SIMPLE_ESCAPE_SEQUENCES[char]; + + if (unitChar) { + return unitChar; + } + switch (char) { + case "x": + return readHexSequence(reader, 2); + case "u": + return readUnicodeSequence(reader); + case "\r": + if (reader.read() === "\n") { + reader.advance(1); + } + + // fallthrough + case "\n": + case "\u2028": + case "\u2029": + return ""; + case "0": + case "1": + case "2": + case "3": + return readOctalSequence(reader, 3); + case "4": + case "5": + case "6": + case "7": + return readOctalSequence(reader, 2); + default: + return char; + } } /** @@ -156,23 +163,23 @@ function readEscapeSequenceOrLineContinuation(reader) { * @param {TextReader} reader The reader should be positioned on the backslash. * @returns {Generator} Zero, one or two `CodeUnit` elements. */ -function *mapEscapeSequenceOrLineContinuation(reader) { - const start = reader.pos; - const str = readEscapeSequenceOrLineContinuation(reader); - const end = reader.pos; - const source = reader.source.slice(start, end); - - switch (str.length) { - case 0: - break; - case 1: - yield new CodeUnit(start, source); - break; - default: - yield new CodeUnit(start, source); - yield new CodeUnit(start, source); - break; - } +function* mapEscapeSequenceOrLineContinuation(reader) { + const start = reader.pos; + const str = readEscapeSequenceOrLineContinuation(reader); + const end = reader.pos; + const source = reader.source.slice(start, end); + + switch (str.length) { + case 0: + break; + case 1: + yield new CodeUnit(start, source); + break; + default: + yield new CodeUnit(start, source); + yield new CodeUnit(start, source); + break; + } } /** @@ -181,26 +188,26 @@ function *mapEscapeSequenceOrLineContinuation(reader) { * @returns {CodeUnit[]} A list of code units produced by the string literal. */ function parseStringLiteral(source) { - const reader = new TextReader(source); - const quote = reader.read(); - - reader.advance(1); - const codeUnits = []; - - for (;;) { - const char = reader.read(); - - if (char === quote) { - break; - } - if (char === "\\") { - codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); - } else { - codeUnits.push(new CodeUnit(reader.pos, char)); - reader.advance(1); - } - } - return codeUnits; + const reader = new TextReader(source); + const quote = reader.read(); + + reader.advance(1); + const codeUnits = []; + + for (;;) { + const char = reader.read(); + + if (char === quote) { + break; + } + if (char === "\\") { + codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); + } else { + codeUnits.push(new CodeUnit(reader.pos, char)); + reader.advance(1); + } + } + return codeUnits; } /** @@ -209,32 +216,32 @@ function parseStringLiteral(source) { * @returns {CodeUnit[]} A list of code units produced by the template token. */ function parseTemplateToken(source) { - const reader = new TextReader(source); - - reader.advance(1); - const codeUnits = []; - - for (;;) { - const char = reader.read(); - - if (char === "`" || char === "$" && reader.read(1) === "{") { - break; - } - if (char === "\\") { - codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); - } else { - let unitSource; - - if (char === "\r" && reader.read(1) === "\n") { - unitSource = "\r\n"; - } else { - unitSource = char; - } - codeUnits.push(new CodeUnit(reader.pos, unitSource)); - reader.advance(unitSource.length); - } - } - return codeUnits; + const reader = new TextReader(source); + + reader.advance(1); + const codeUnits = []; + + for (;;) { + const char = reader.read(); + + if (char === "`" || (char === "$" && reader.read(1) === "{")) { + break; + } + if (char === "\\") { + codeUnits.push(...mapEscapeSequenceOrLineContinuation(reader)); + } else { + let unitSource; + + if (char === "\r" && reader.read(1) === "\n") { + unitSource = "\r\n"; + } else { + unitSource = char; + } + codeUnits.push(new CodeUnit(reader.pos, unitSource)); + reader.advance(unitSource.length); + } + } + return codeUnits; } module.exports = { parseStringLiteral, parseTemplateToken }; diff --git a/lib/rules/utils/fix-tracker.js b/lib/rules/utils/fix-tracker.js index 589870b39b52..259a2546e30e 100644 --- a/lib/rules/utils/fix-tracker.js +++ b/lib/rules/utils/fix-tracker.js @@ -20,95 +20,98 @@ const astUtils = require("./ast-utils"); * replaced so that other fixes won't touch that region in the same pass. */ class FixTracker { + /** + * Create a new FixTracker. + * @param {ruleFixer} fixer A ruleFixer instance. + * @param {SourceCode} sourceCode A SourceCode object for the current code. + */ + constructor(fixer, sourceCode) { + this.fixer = fixer; + this.sourceCode = sourceCode; + this.retainedRange = null; + } - /** - * Create a new FixTracker. - * @param {ruleFixer} fixer A ruleFixer instance. - * @param {SourceCode} sourceCode A SourceCode object for the current code. - */ - constructor(fixer, sourceCode) { - this.fixer = fixer; - this.sourceCode = sourceCode; - this.retainedRange = null; - } + /** + * Mark the given range as "retained", meaning that other fixes may not + * may not modify this region in the same pass. + * @param {int[]} range The range to retain. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainRange(range) { + this.retainedRange = range; + return this; + } - /** - * Mark the given range as "retained", meaning that other fixes may not - * may not modify this region in the same pass. - * @param {int[]} range The range to retain. - * @returns {FixTracker} The same RuleFixer, for chained calls. - */ - retainRange(range) { - this.retainedRange = range; - return this; - } + /** + * Given a node, find the function containing it (or the entire program) and + * mark it as retained, meaning that other fixes may not modify it in this + * pass. This is useful for avoiding conflicts in fixes that modify control + * flow. + * @param {ASTNode} node The node to use as a starting point. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainEnclosingFunction(node) { + const functionNode = astUtils.getUpperFunction(node); - /** - * Given a node, find the function containing it (or the entire program) and - * mark it as retained, meaning that other fixes may not modify it in this - * pass. This is useful for avoiding conflicts in fixes that modify control - * flow. - * @param {ASTNode} node The node to use as a starting point. - * @returns {FixTracker} The same RuleFixer, for chained calls. - */ - retainEnclosingFunction(node) { - const functionNode = astUtils.getUpperFunction(node); + return this.retainRange( + functionNode ? functionNode.range : this.sourceCode.ast.range, + ); + } - return this.retainRange(functionNode ? functionNode.range : this.sourceCode.ast.range); - } + /** + * Given a node or token, find the token before and afterward, and mark that + * range as retained, meaning that other fixes may not modify it in this + * pass. This is useful for avoiding conflicts in fixes that make a small + * change to the code where the AST should not be changed. + * @param {ASTNode|Token} nodeOrToken The node or token to use as a starting + * point. The token to the left and right are use in the range. + * @returns {FixTracker} The same RuleFixer, for chained calls. + */ + retainSurroundingTokens(nodeOrToken) { + const tokenBefore = + this.sourceCode.getTokenBefore(nodeOrToken) || nodeOrToken; + const tokenAfter = + this.sourceCode.getTokenAfter(nodeOrToken) || nodeOrToken; - /** - * Given a node or token, find the token before and afterward, and mark that - * range as retained, meaning that other fixes may not modify it in this - * pass. This is useful for avoiding conflicts in fixes that make a small - * change to the code where the AST should not be changed. - * @param {ASTNode|Token} nodeOrToken The node or token to use as a starting - * point. The token to the left and right are use in the range. - * @returns {FixTracker} The same RuleFixer, for chained calls. - */ - retainSurroundingTokens(nodeOrToken) { - const tokenBefore = this.sourceCode.getTokenBefore(nodeOrToken) || nodeOrToken; - const tokenAfter = this.sourceCode.getTokenAfter(nodeOrToken) || nodeOrToken; + return this.retainRange([tokenBefore.range[0], tokenAfter.range[1]]); + } - return this.retainRange([tokenBefore.range[0], tokenAfter.range[1]]); - } + /** + * Create a fix command that replaces the given range with the given text, + * accounting for any retained ranges. + * @param {int[]} range The range to remove in the fix. + * @param {string} text The text to insert in place of the range. + * @returns {Object} The fix command. + */ + replaceTextRange(range, text) { + let actualRange; - /** - * Create a fix command that replaces the given range with the given text, - * accounting for any retained ranges. - * @param {int[]} range The range to remove in the fix. - * @param {string} text The text to insert in place of the range. - * @returns {Object} The fix command. - */ - replaceTextRange(range, text) { - let actualRange; + if (this.retainedRange) { + actualRange = [ + Math.min(this.retainedRange[0], range[0]), + Math.max(this.retainedRange[1], range[1]), + ]; + } else { + actualRange = range; + } - if (this.retainedRange) { - actualRange = [ - Math.min(this.retainedRange[0], range[0]), - Math.max(this.retainedRange[1], range[1]) - ]; - } else { - actualRange = range; - } + return this.fixer.replaceTextRange( + actualRange, + this.sourceCode.text.slice(actualRange[0], range[0]) + + text + + this.sourceCode.text.slice(range[1], actualRange[1]), + ); + } - return this.fixer.replaceTextRange( - actualRange, - this.sourceCode.text.slice(actualRange[0], range[0]) + - text + - this.sourceCode.text.slice(range[1], actualRange[1]) - ); - } - - /** - * Create a fix command that removes the given node or token, accounting for - * any retained ranges. - * @param {ASTNode|Token} nodeOrToken The node or token to remove. - * @returns {Object} The fix command. - */ - remove(nodeOrToken) { - return this.replaceTextRange(nodeOrToken.range, ""); - } + /** + * Create a fix command that removes the given node or token, accounting for + * any retained ranges. + * @param {ASTNode|Token} nodeOrToken The node or token to remove. + * @returns {Object} The fix command. + */ + remove(nodeOrToken) { + return this.replaceTextRange(nodeOrToken.range, ""); + } } module.exports = FixTracker; diff --git a/lib/rules/utils/keywords.js b/lib/rules/utils/keywords.js index 3fbb77771df5..eca2076eb4dd 100644 --- a/lib/rules/utils/keywords.js +++ b/lib/rules/utils/keywords.js @@ -5,63 +5,63 @@ "use strict"; module.exports = [ - "abstract", - "boolean", - "break", - "byte", - "case", - "catch", - "char", - "class", - "const", - "continue", - "debugger", - "default", - "delete", - "do", - "double", - "else", - "enum", - "export", - "extends", - "false", - "final", - "finally", - "float", - "for", - "function", - "goto", - "if", - "implements", - "import", - "in", - "instanceof", - "int", - "interface", - "long", - "native", - "new", - "null", - "package", - "private", - "protected", - "public", - "return", - "short", - "static", - "super", - "switch", - "synchronized", - "this", - "throw", - "throws", - "transient", - "true", - "try", - "typeof", - "var", - "void", - "volatile", - "while", - "with" + "abstract", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "double", + "else", + "enum", + "export", + "extends", + "false", + "final", + "finally", + "float", + "for", + "function", + "goto", + "if", + "implements", + "import", + "in", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "typeof", + "var", + "void", + "volatile", + "while", + "with", ]; diff --git a/lib/rules/utils/lazy-loading-rule-map.js b/lib/rules/utils/lazy-loading-rule-map.js index 3fa5d83b0e83..4eb8124f848e 100644 --- a/lib/rules/utils/lazy-loading-rule-map.js +++ b/lib/rules/utils/lazy-loading-rule-map.js @@ -22,94 +22,97 @@ const debug = require("debug")("eslint:rules"); * @extends {Map Rule>} */ class LazyLoadingRuleMap extends Map { + /** + * Initialize this map. + * @param {Array<[string, function(): Rule]>} loaders The rule loaders. + */ + constructor(loaders) { + let remaining = loaders.length; - /** - * Initialize this map. - * @param {Array<[string, function(): Rule]>} loaders The rule loaders. - */ - constructor(loaders) { - let remaining = loaders.length; + super( + debug.enabled + ? loaders.map(([ruleId, load]) => { + let cache = null; - super( - debug.enabled - ? loaders.map(([ruleId, load]) => { - let cache = null; + return [ + ruleId, + () => { + if (!cache) { + debug( + "Loading rule %o (remaining=%d)", + ruleId, + --remaining, + ); + cache = load(); + } + return cache; + }, + ]; + }) + : loaders, + ); - return [ - ruleId, - () => { - if (!cache) { - debug("Loading rule %o (remaining=%d)", ruleId, --remaining); - cache = load(); - } - return cache; - } - ]; - }) - : loaders - ); + // `super(...iterable)` uses `this.set()`, so disable it here. + Object.defineProperty(LazyLoadingRuleMap.prototype, "set", { + configurable: true, + value: void 0, + }); + } - // `super(...iterable)` uses `this.set()`, so disable it here. - Object.defineProperty(LazyLoadingRuleMap.prototype, "set", { - configurable: true, - value: void 0 - }); - } + /** + * Get a rule. + * Each rule will be loaded on the first access. + * @param {string} ruleId The rule ID to get. + * @returns {Rule|undefined} The rule. + */ + get(ruleId) { + const load = super.get(ruleId); - /** - * Get a rule. - * Each rule will be loaded on the first access. - * @param {string} ruleId The rule ID to get. - * @returns {Rule|undefined} The rule. - */ - get(ruleId) { - const load = super.get(ruleId); + return load && load(); + } - return load && load(); - } + /** + * Iterate rules. + * @returns {IterableIterator} Rules. + */ + *values() { + for (const load of super.values()) { + yield load(); + } + } - /** - * Iterate rules. - * @returns {IterableIterator} Rules. - */ - *values() { - for (const load of super.values()) { - yield load(); - } - } + /** + * Iterate rules. + * @returns {IterableIterator<[string, Rule]>} Rules. + */ + *entries() { + for (const [ruleId, load] of super.entries()) { + yield [ruleId, load()]; + } + } - /** - * Iterate rules. - * @returns {IterableIterator<[string, Rule]>} Rules. - */ - *entries() { - for (const [ruleId, load] of super.entries()) { - yield [ruleId, load()]; - } - } - - /** - * Call a function with each rule. - * @param {Function} callbackFn The callback function. - * @param {any} [thisArg] The object to pass to `this` of the callback function. - * @returns {void} - */ - forEach(callbackFn, thisArg) { - for (const [ruleId, load] of super.entries()) { - callbackFn.call(thisArg, load(), ruleId, this); - } - } + /** + * Call a function with each rule. + * @param {Function} callbackFn The callback function. + * @param {any} [thisArg] The object to pass to `this` of the callback function. + * @returns {void} + */ + forEach(callbackFn, thisArg) { + for (const [ruleId, load] of super.entries()) { + callbackFn.call(thisArg, load(), ruleId, this); + } + } } // Forbid mutation. Object.defineProperties(LazyLoadingRuleMap.prototype, { - clear: { configurable: true, value: void 0 }, - delete: { configurable: true, value: void 0 }, - [Symbol.iterator]: { - configurable: true, - writable: true, - value: LazyLoadingRuleMap.prototype.entries - } + clear: { configurable: true, value: void 0 }, + delete: { configurable: true, value: void 0 }, + [Symbol.iterator]: { + configurable: true, + writable: true, + value: LazyLoadingRuleMap.prototype.entries, + }, }); module.exports = { LazyLoadingRuleMap }; diff --git a/lib/rules/utils/regular-expressions.js b/lib/rules/utils/regular-expressions.js index 82fdd9b8a23a..6d80654b6ee5 100644 --- a/lib/rules/utils/regular-expressions.js +++ b/lib/rules/utils/regular-expressions.js @@ -20,31 +20,39 @@ const REGEXPP_LATEST_ECMA_VERSION = 2025; * ecmaVersion doesn't support the `u` flag. */ function isValidWithUnicodeFlag(ecmaVersion, pattern, flag = "u") { - if (flag === "u" && ecmaVersion <= 5) { // ecmaVersion <= 5 doesn't support the 'u' flag - return false; - } - if (flag === "v" && ecmaVersion <= 2023) { - return false; - } - - const validator = new RegExpValidator({ - ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION) - }); - - try { - validator.validatePattern(pattern, void 0, void 0, flag === "u" ? { - unicode: /* uFlag = */ true - } : { - unicodeSets: true - }); - } catch { - return false; - } - - return true; + if (flag === "u" && ecmaVersion <= 5) { + // ecmaVersion <= 5 doesn't support the 'u' flag + return false; + } + if (flag === "v" && ecmaVersion <= 2023) { + return false; + } + + const validator = new RegExpValidator({ + ecmaVersion: Math.min(ecmaVersion, REGEXPP_LATEST_ECMA_VERSION), + }); + + try { + validator.validatePattern( + pattern, + void 0, + void 0, + flag === "u" + ? { + unicode: /* uFlag = */ true, + } + : { + unicodeSets: true, + }, + ); + } catch { + return false; + } + + return true; } module.exports = { - isValidWithUnicodeFlag, - REGEXPP_LATEST_ECMA_VERSION + isValidWithUnicodeFlag, + REGEXPP_LATEST_ECMA_VERSION, }; diff --git a/lib/rules/utils/unicode/index.js b/lib/rules/utils/unicode/index.js index d35d812e1e97..0e7d5d53c148 100644 --- a/lib/rules/utils/unicode/index.js +++ b/lib/rules/utils/unicode/index.js @@ -9,8 +9,8 @@ const isRegionalIndicatorSymbol = require("./is-regional-indicator-symbol"); const isSurrogatePair = require("./is-surrogate-pair"); module.exports = { - isCombiningCharacter, - isEmojiModifier, - isRegionalIndicatorSymbol, - isSurrogatePair + isCombiningCharacter, + isEmojiModifier, + isRegionalIndicatorSymbol, + isSurrogatePair, }; diff --git a/lib/rules/utils/unicode/is-combining-character.js b/lib/rules/utils/unicode/is-combining-character.js index 0498b99a21ed..8a36b223f027 100644 --- a/lib/rules/utils/unicode/is-combining-character.js +++ b/lib/rules/utils/unicode/is-combining-character.js @@ -9,5 +9,5 @@ * @returns {boolean} `true` if the character belongs to the category, any of `Mc`, `Me`, and `Mn`. */ module.exports = function isCombiningCharacter(codePoint) { - return /^[\p{Mc}\p{Me}\p{Mn}]$/u.test(String.fromCodePoint(codePoint)); + return /^[\p{Mc}\p{Me}\p{Mn}]$/u.test(String.fromCodePoint(codePoint)); }; diff --git a/lib/rules/utils/unicode/is-emoji-modifier.js b/lib/rules/utils/unicode/is-emoji-modifier.js index 1bd5f557dcc4..526ac91d6bd6 100644 --- a/lib/rules/utils/unicode/is-emoji-modifier.js +++ b/lib/rules/utils/unicode/is-emoji-modifier.js @@ -9,5 +9,5 @@ * @returns {boolean} `true` if the character is an emoji modifier. */ module.exports = function isEmojiModifier(code) { - return code >= 0x1F3FB && code <= 0x1F3FF; + return code >= 0x1f3fb && code <= 0x1f3ff; }; diff --git a/lib/rules/utils/unicode/is-regional-indicator-symbol.js b/lib/rules/utils/unicode/is-regional-indicator-symbol.js index c48ed46ef8fd..019d9da0afd0 100644 --- a/lib/rules/utils/unicode/is-regional-indicator-symbol.js +++ b/lib/rules/utils/unicode/is-regional-indicator-symbol.js @@ -9,5 +9,5 @@ * @returns {boolean} `true` if the character is a regional indicator symbol. */ module.exports = function isRegionalIndicatorSymbol(code) { - return code >= 0x1F1E6 && code <= 0x1F1FF; + return code >= 0x1f1e6 && code <= 0x1f1ff; }; diff --git a/lib/rules/utils/unicode/is-surrogate-pair.js b/lib/rules/utils/unicode/is-surrogate-pair.js index b8e5c1cacdb6..fbe6a4017c6f 100644 --- a/lib/rules/utils/unicode/is-surrogate-pair.js +++ b/lib/rules/utils/unicode/is-surrogate-pair.js @@ -10,5 +10,5 @@ * @returns {boolean} `true` if the character pair is a surrogate pair. */ module.exports = function isSurrogatePair(lead, tail) { - return lead >= 0xD800 && lead < 0xDC00 && tail >= 0xDC00 && tail < 0xE000; + return lead >= 0xd800 && lead < 0xdc00 && tail >= 0xdc00 && tail < 0xe000; }; diff --git a/lib/rules/valid-typeof.js b/lib/rules/valid-typeof.js index eff4d942ecbd..4e4710058d78 100644 --- a/lib/rules/valid-typeof.js +++ b/lib/rules/valid-typeof.js @@ -16,114 +16,156 @@ const astUtils = require("./utils/ast-utils"); /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "problem", - - defaultOptions: [{ - requireStringLiterals: false - }], - - docs: { - description: "Enforce comparing `typeof` expressions against valid strings", - recommended: true, - url: "https://eslint.org/docs/latest/rules/valid-typeof" - }, - - hasSuggestions: true, - - schema: [ - { - type: "object", - properties: { - requireStringLiterals: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - messages: { - invalidValue: "Invalid typeof comparison value.", - notString: "Typeof comparisons should be to string literals.", - suggestString: 'Use `"{{type}}"` instead of `{{type}}`.' - } - }, - - create(context) { - const VALID_TYPES = new Set(["symbol", "undefined", "object", "boolean", "number", "string", "function", "bigint"]), - OPERATORS = new Set(["==", "===", "!=", "!=="]); - const sourceCode = context.sourceCode; - const [{ requireStringLiterals }] = context.options; - - let globalScope; - - /** - * Checks whether the given node represents a reference to a global variable that is not declared in the source code. - * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. - * @param {ASTNode} node `Identifier` node to check. - * @returns {boolean} `true` if the node is a reference to a global variable. - */ - function isReferenceToGlobalVariable(node) { - const variable = globalScope.set.get(node.name); - - return variable && variable.defs.length === 0 && - variable.references.some(ref => ref.identifier === node); - } - - /** - * Determines whether a node is a typeof expression. - * @param {ASTNode} node The node - * @returns {boolean} `true` if the node is a typeof expression - */ - function isTypeofExpression(node) { - return node.type === "UnaryExpression" && node.operator === "typeof"; - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - - Program(node) { - globalScope = sourceCode.getScope(node); - }, - - UnaryExpression(node) { - if (isTypeofExpression(node)) { - const { parent } = node; - - if (parent.type === "BinaryExpression" && OPERATORS.has(parent.operator)) { - const sibling = parent.left === node ? parent.right : parent.left; - - if (sibling.type === "Literal" || astUtils.isStaticTemplateLiteral(sibling)) { - const value = sibling.type === "Literal" ? sibling.value : sibling.quasis[0].value.cooked; - - if (!VALID_TYPES.has(value)) { - context.report({ node: sibling, messageId: "invalidValue" }); - } - } else if (sibling.type === "Identifier" && sibling.name === "undefined" && isReferenceToGlobalVariable(sibling)) { - context.report({ - node: sibling, - messageId: requireStringLiterals ? "notString" : "invalidValue", - suggest: [ - { - messageId: "suggestString", - data: { type: "undefined" }, - fix(fixer) { - return fixer.replaceText(sibling, '"undefined"'); - } - } - ] - }); - } else if (requireStringLiterals && !isTypeofExpression(sibling)) { - context.report({ node: sibling, messageId: "notString" }); - } - } - } - } - - }; - - } + meta: { + type: "problem", + + defaultOptions: [ + { + requireStringLiterals: false, + }, + ], + + docs: { + description: + "Enforce comparing `typeof` expressions against valid strings", + recommended: true, + url: "https://eslint.org/docs/latest/rules/valid-typeof", + }, + + hasSuggestions: true, + + schema: [ + { + type: "object", + properties: { + requireStringLiterals: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + messages: { + invalidValue: "Invalid typeof comparison value.", + notString: "Typeof comparisons should be to string literals.", + suggestString: 'Use `"{{type}}"` instead of `{{type}}`.', + }, + }, + + create(context) { + const VALID_TYPES = new Set([ + "symbol", + "undefined", + "object", + "boolean", + "number", + "string", + "function", + "bigint", + ]), + OPERATORS = new Set(["==", "===", "!=", "!=="]); + const sourceCode = context.sourceCode; + const [{ requireStringLiterals }] = context.options; + + let globalScope; + + /** + * Checks whether the given node represents a reference to a global variable that is not declared in the source code. + * These identifiers will be allowed, as it is assumed that user has no control over the names of external global variables. + * @param {ASTNode} node `Identifier` node to check. + * @returns {boolean} `true` if the node is a reference to a global variable. + */ + function isReferenceToGlobalVariable(node) { + const variable = globalScope.set.get(node.name); + + return ( + variable && + variable.defs.length === 0 && + variable.references.some(ref => ref.identifier === node) + ); + } + + /** + * Determines whether a node is a typeof expression. + * @param {ASTNode} node The node + * @returns {boolean} `true` if the node is a typeof expression + */ + function isTypeofExpression(node) { + return ( + node.type === "UnaryExpression" && node.operator === "typeof" + ); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + Program(node) { + globalScope = sourceCode.getScope(node); + }, + + UnaryExpression(node) { + if (isTypeofExpression(node)) { + const { parent } = node; + + if ( + parent.type === "BinaryExpression" && + OPERATORS.has(parent.operator) + ) { + const sibling = + parent.left === node ? parent.right : parent.left; + + if ( + sibling.type === "Literal" || + astUtils.isStaticTemplateLiteral(sibling) + ) { + const value = + sibling.type === "Literal" + ? sibling.value + : sibling.quasis[0].value.cooked; + + if (!VALID_TYPES.has(value)) { + context.report({ + node: sibling, + messageId: "invalidValue", + }); + } + } else if ( + sibling.type === "Identifier" && + sibling.name === "undefined" && + isReferenceToGlobalVariable(sibling) + ) { + context.report({ + node: sibling, + messageId: requireStringLiterals + ? "notString" + : "invalidValue", + suggest: [ + { + messageId: "suggestString", + data: { type: "undefined" }, + fix(fixer) { + return fixer.replaceText( + sibling, + '"undefined"', + ); + }, + }, + ], + }); + } else if ( + requireStringLiterals && + !isTypeofExpression(sibling) + ) { + context.report({ + node: sibling, + messageId: "notString", + }); + } + } + } + }, + }; + }, }; diff --git a/lib/rules/vars-on-top.js b/lib/rules/vars-on-top.js index ccb36c426e33..65b05abcdb2c 100644 --- a/lib/rules/vars-on-top.js +++ b/lib/rules/vars-on-top.js @@ -11,148 +11,155 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - type: "suggestion", - - 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" - }, - - schema: [], - messages: { - top: "All 'var' declarations must be at the top of the function scope." - } - }, - - create(context) { - - //-------------------------------------------------------------------------- - // Helpers - //-------------------------------------------------------------------------- - - /** - * Has AST suggesting a directive. - * @param {ASTNode} node any node - * @returns {boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === "ExpressionStatement" && - node.expression.type === "Literal" && typeof node.expression.value === "string"; - } - - /** - * Check to see if its a ES6 import declaration - * @param {ASTNode} node any node - * @returns {boolean} whether the given node represents a import declaration - */ - function looksLikeImport(node) { - return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" || - node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier"; - } - - /** - * Checks whether a given node is a variable declaration or not. - * @param {ASTNode} node any node - * @returns {boolean} `true` if the node is a variable declaration. - */ - function isVariableDeclaration(node) { - return ( - node.type === "VariableDeclaration" || - ( - node.type === "ExportNamedDeclaration" && - node.declaration && - node.declaration.type === "VariableDeclaration" - ) - ); - } - - /** - * Checks whether this variable is on top of the block body - * @param {ASTNode} node The node to check - * @param {ASTNode[]} statements collection of ASTNodes for the parent node block - * @returns {boolean} True if var is on top otherwise false - */ - function isVarOnTop(node, statements) { - const l = statements.length; - let i = 0; - - // Skip over directives and imports. Static blocks don't have either. - if (node.parent.type !== "StaticBlock") { - for (; i < l; ++i) { - if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) { - break; - } - } - } - - for (; i < l; ++i) { - if (!isVariableDeclaration(statements[i])) { - return false; - } - if (statements[i] === node) { - return true; - } - } - - return false; - } - - /** - * Checks whether variable is on top at the global level - * @param {ASTNode} node The node to check - * @param {ASTNode} parent Parent of the node - * @returns {void} - */ - function globalVarCheck(node, parent) { - if (!isVarOnTop(node, parent.body)) { - context.report({ node, messageId: "top" }); - } - } - - /** - * Checks whether variable is on top at functional block scope level - * @param {ASTNode} node The node to check - * @returns {void} - */ - function blockScopeVarCheck(node) { - const { parent } = node; - - if ( - parent.type === "BlockStatement" && - /Function/u.test(parent.parent.type) && - isVarOnTop(node, parent.body) - ) { - return; - } - - if ( - parent.type === "StaticBlock" && - isVarOnTop(node, parent.body) - ) { - return; - } - - context.report({ node, messageId: "top" }); - } - - //-------------------------------------------------------------------------- - // Public API - //-------------------------------------------------------------------------- - - return { - "VariableDeclaration[kind='var']"(node) { - if (node.parent.type === "ExportNamedDeclaration") { - globalVarCheck(node.parent, node.parent.parent); - } else if (node.parent.type === "Program") { - globalVarCheck(node, node.parent); - } else { - blockScopeVarCheck(node); - } - } - }; - - } + meta: { + type: "suggestion", + + 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", + }, + + schema: [], + messages: { + top: "All 'var' declarations must be at the top of the function scope.", + }, + }, + + create(context) { + //-------------------------------------------------------------------------- + // Helpers + //-------------------------------------------------------------------------- + + /** + * Has AST suggesting a directive. + * @param {ASTNode} node any node + * @returns {boolean} whether the given node structurally represents a directive + */ + function looksLikeDirective(node) { + return ( + node.type === "ExpressionStatement" && + node.expression.type === "Literal" && + typeof node.expression.value === "string" + ); + } + + /** + * Check to see if its a ES6 import declaration + * @param {ASTNode} node any node + * @returns {boolean} whether the given node represents a import declaration + */ + function looksLikeImport(node) { + return ( + node.type === "ImportDeclaration" || + node.type === "ImportSpecifier" || + node.type === "ImportDefaultSpecifier" || + node.type === "ImportNamespaceSpecifier" + ); + } + + /** + * Checks whether a given node is a variable declaration or not. + * @param {ASTNode} node any node + * @returns {boolean} `true` if the node is a variable declaration. + */ + function isVariableDeclaration(node) { + return ( + node.type === "VariableDeclaration" || + (node.type === "ExportNamedDeclaration" && + node.declaration && + node.declaration.type === "VariableDeclaration") + ); + } + + /** + * Checks whether this variable is on top of the block body + * @param {ASTNode} node The node to check + * @param {ASTNode[]} statements collection of ASTNodes for the parent node block + * @returns {boolean} True if var is on top otherwise false + */ + function isVarOnTop(node, statements) { + const l = statements.length; + let i = 0; + + // Skip over directives and imports. Static blocks don't have either. + if (node.parent.type !== "StaticBlock") { + for (; i < l; ++i) { + if ( + !looksLikeDirective(statements[i]) && + !looksLikeImport(statements[i]) + ) { + break; + } + } + } + + for (; i < l; ++i) { + if (!isVariableDeclaration(statements[i])) { + return false; + } + if (statements[i] === node) { + return true; + } + } + + return false; + } + + /** + * Checks whether variable is on top at the global level + * @param {ASTNode} node The node to check + * @param {ASTNode} parent Parent of the node + * @returns {void} + */ + function globalVarCheck(node, parent) { + if (!isVarOnTop(node, parent.body)) { + context.report({ node, messageId: "top" }); + } + } + + /** + * Checks whether variable is on top at functional block scope level + * @param {ASTNode} node The node to check + * @returns {void} + */ + function blockScopeVarCheck(node) { + const { parent } = node; + + if ( + parent.type === "BlockStatement" && + /Function/u.test(parent.parent.type) && + isVarOnTop(node, parent.body) + ) { + return; + } + + if ( + parent.type === "StaticBlock" && + isVarOnTop(node, parent.body) + ) { + return; + } + + context.report({ node, messageId: "top" }); + } + + //-------------------------------------------------------------------------- + // Public API + //-------------------------------------------------------------------------- + + return { + "VariableDeclaration[kind='var']"(node) { + if (node.parent.type === "ExportNamedDeclaration") { + globalVarCheck(node.parent, node.parent.parent); + } else if (node.parent.type === "Program") { + globalVarCheck(node, node.parent); + } else { + blockScopeVarCheck(node); + } + }, + }; + }, }; diff --git a/lib/rules/wrap-iife.js b/lib/rules/wrap-iife.js index 1a5cc8a77110..5e66ab841df6 100644 --- a/lib/rules/wrap-iife.js +++ b/lib/rules/wrap-iife.js @@ -24,14 +24,13 @@ const eslintUtils = require("@eslint-community/eslint-utils"); * @private */ function isCalleeOfNewExpression(node) { - const maybeCallee = node.parent.type === "ChainExpression" - ? node.parent - : node; - - return ( - maybeCallee.parent.type === "NewExpression" && - maybeCallee.parent.callee === maybeCallee - ); + const maybeCallee = + node.parent.type === "ChainExpression" ? node.parent : node; + + return ( + maybeCallee.parent.type === "NewExpression" && + maybeCallee.parent.callee === maybeCallee + ); } //------------------------------------------------------------------------------ @@ -40,186 +39,200 @@ function isCalleeOfNewExpression(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "wrap-iife", - url: "https://eslint.style/rules/js/wrap-iife" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require parentheses around immediate `function` invocations", - recommended: false, - url: "https://eslint.org/docs/latest/rules/wrap-iife" - }, - - schema: [ - { - enum: ["outside", "inside", "any"] - }, - { - type: "object", - properties: { - functionPrototypeMethods: { - type: "boolean", - default: false - } - }, - additionalProperties: false - } - ], - - fixable: "code", - messages: { - wrapInvocation: "Wrap an immediate function invocation in parentheses.", - wrapExpression: "Wrap only the function expression in parens.", - moveInvocation: "Move the invocation into the parens that contain the function." - } - }, - - create(context) { - - const style = context.options[0] || "outside"; - const includeFunctionPrototypeMethods = context.options[1] && context.options[1].functionPrototypeMethods; - - const sourceCode = context.sourceCode; - - /** - * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if() - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if it is wrapped in any parens - * @private - */ - function isWrappedInAnyParens(node) { - return astUtils.isParenthesised(sourceCode, node); - } - - /** - * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count - * @param {ASTNode} node node to evaluate - * @returns {boolean} True if it is wrapped in grouping parens - * @private - */ - function isWrappedInGroupingParens(node) { - return eslintUtils.isParenthesized(1, node, sourceCode); - } - - /** - * Get the function node from an IIFE - * @param {ASTNode} node node to evaluate - * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist - */ - function getFunctionNodeFromIIFE(node) { - const callee = astUtils.skipChainExpression(node.callee); - - if (callee.type === "FunctionExpression") { - return callee; - } - - if (includeFunctionPrototypeMethods && - callee.type === "MemberExpression" && - callee.object.type === "FunctionExpression" && - (astUtils.getStaticPropertyName(callee) === "call" || astUtils.getStaticPropertyName(callee) === "apply") - ) { - return callee.object; - } - - return null; - } - - - return { - CallExpression(node) { - const innerNode = getFunctionNodeFromIIFE(node); - - if (!innerNode) { - return; - } - - const isCallExpressionWrapped = isWrappedInAnyParens(node), - isFunctionExpressionWrapped = isWrappedInAnyParens(innerNode); - - if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) { - context.report({ - node, - messageId: "wrapInvocation", - fix(fixer) { - const nodeToSurround = style === "inside" ? innerNode : node; - - return fixer.replaceText(nodeToSurround, `(${sourceCode.getText(nodeToSurround)})`); - } - }); - } else if (style === "inside" && !isFunctionExpressionWrapped) { - context.report({ - node, - messageId: "wrapExpression", - fix(fixer) { - - // The outer call expression will always be wrapped at this point. - - if (isWrappedInGroupingParens(node) && !isCalleeOfNewExpression(node)) { - - /* - * Parenthesize the function expression and remove unnecessary grouping parens around the call expression. - * Replace the range between the end of the function expression and the end of the call expression. - * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`. - */ - - const parenAfter = sourceCode.getTokenAfter(node); - - return fixer.replaceTextRange( - [innerNode.range[1], parenAfter.range[1]], - `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}` - ); - } - - /* - * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens. - * These parens cannot be removed, so just parenthesize the function expression. - */ - - return fixer.replaceText(innerNode, `(${sourceCode.getText(innerNode)})`); - } - }); - } else if (style === "outside" && !isCallExpressionWrapped) { - context.report({ - node, - messageId: "moveInvocation", - fix(fixer) { - - /* - * The inner function expression will always be wrapped at this point. - * It's only necessary to replace the range between the end of the function expression - * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)` - * should get replaced with `(bar))`. - */ - const parenAfter = sourceCode.getTokenAfter(innerNode); - - return fixer.replaceTextRange( - [parenAfter.range[0], node.range[1]], - `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})` - ); - } - }); - } - } - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "wrap-iife", + url: "https://eslint.style/rules/js/wrap-iife", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Require parentheses around immediate `function` invocations", + recommended: false, + url: "https://eslint.org/docs/latest/rules/wrap-iife", + }, + + schema: [ + { + enum: ["outside", "inside", "any"], + }, + { + type: "object", + properties: { + functionPrototypeMethods: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + messages: { + wrapInvocation: + "Wrap an immediate function invocation in parentheses.", + wrapExpression: "Wrap only the function expression in parens.", + moveInvocation: + "Move the invocation into the parens that contain the function.", + }, + }, + + create(context) { + const style = context.options[0] || "outside"; + const includeFunctionPrototypeMethods = + context.options[1] && context.options[1].functionPrototypeMethods; + + const sourceCode = context.sourceCode; + + /** + * Check if the node is wrapped in any (). All parens count: grouping parens and parens for constructs such as if() + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped in any parens + * @private + */ + function isWrappedInAnyParens(node) { + return astUtils.isParenthesised(sourceCode, node); + } + + /** + * Check if the node is wrapped in grouping (). Parens for constructs such as if() don't count + * @param {ASTNode} node node to evaluate + * @returns {boolean} True if it is wrapped in grouping parens + * @private + */ + function isWrappedInGroupingParens(node) { + return eslintUtils.isParenthesized(1, node, sourceCode); + } + + /** + * Get the function node from an IIFE + * @param {ASTNode} node node to evaluate + * @returns {ASTNode} node that is the function expression of the given IIFE, or null if none exist + */ + function getFunctionNodeFromIIFE(node) { + const callee = astUtils.skipChainExpression(node.callee); + + if (callee.type === "FunctionExpression") { + return callee; + } + + if ( + includeFunctionPrototypeMethods && + callee.type === "MemberExpression" && + callee.object.type === "FunctionExpression" && + (astUtils.getStaticPropertyName(callee) === "call" || + astUtils.getStaticPropertyName(callee) === "apply") + ) { + return callee.object; + } + + return null; + } + + return { + CallExpression(node) { + const innerNode = getFunctionNodeFromIIFE(node); + + if (!innerNode) { + return; + } + + const isCallExpressionWrapped = isWrappedInAnyParens(node), + isFunctionExpressionWrapped = + isWrappedInAnyParens(innerNode); + + if (!isCallExpressionWrapped && !isFunctionExpressionWrapped) { + context.report({ + node, + messageId: "wrapInvocation", + fix(fixer) { + const nodeToSurround = + style === "inside" ? innerNode : node; + + return fixer.replaceText( + nodeToSurround, + `(${sourceCode.getText(nodeToSurround)})`, + ); + }, + }); + } else if (style === "inside" && !isFunctionExpressionWrapped) { + context.report({ + node, + messageId: "wrapExpression", + fix(fixer) { + // The outer call expression will always be wrapped at this point. + + if ( + isWrappedInGroupingParens(node) && + !isCalleeOfNewExpression(node) + ) { + /* + * Parenthesize the function expression and remove unnecessary grouping parens around the call expression. + * Replace the range between the end of the function expression and the end of the call expression. + * for example, in `(function(foo) {}(bar))`, the range `(bar))` should get replaced with `)(bar)`. + */ + + const parenAfter = + sourceCode.getTokenAfter(node); + + return fixer.replaceTextRange( + [innerNode.range[1], parenAfter.range[1]], + `)${sourceCode.getText().slice(innerNode.range[1], parenAfter.range[0])}`, + ); + } + + /* + * Call expression is wrapped in mandatory parens such as if(), or in necessary grouping parens. + * These parens cannot be removed, so just parenthesize the function expression. + */ + + return fixer.replaceText( + innerNode, + `(${sourceCode.getText(innerNode)})`, + ); + }, + }); + } else if (style === "outside" && !isCallExpressionWrapped) { + context.report({ + node, + messageId: "moveInvocation", + fix(fixer) { + /* + * The inner function expression will always be wrapped at this point. + * It's only necessary to replace the range between the end of the function expression + * and the call expression. For example, in `(function(foo) {})(bar)`, the range `)(bar)` + * should get replaced with `(bar))`. + */ + const parenAfter = + sourceCode.getTokenAfter(innerNode); + + return fixer.replaceTextRange( + [parenAfter.range[0], node.range[1]], + `${sourceCode.getText().slice(parenAfter.range[1], node.range[1])})`, + ); + }, + }); + } + }, + }; + }, }; diff --git a/lib/rules/wrap-regex.js b/lib/rules/wrap-regex.js index 5d9cf452ad20..8c05ec8f1269 100644 --- a/lib/rules/wrap-regex.js +++ b/lib/rules/wrap-regex.js @@ -12,68 +12,80 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "wrap-regex", - url: "https://eslint.style/rules/js/wrap-regex" - } - } - ] - }, - type: "layout", + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "wrap-regex", + url: "https://eslint.style/rules/js/wrap-regex", + }, + }, + ], + }, + type: "layout", - docs: { - description: "Require parenthesis around regex literals", - recommended: false, - url: "https://eslint.org/docs/latest/rules/wrap-regex" - }, + docs: { + description: "Require parenthesis around regex literals", + recommended: false, + url: "https://eslint.org/docs/latest/rules/wrap-regex", + }, - schema: [], - fixable: "code", + schema: [], + fixable: "code", - messages: { - requireParens: "Wrap the regexp literal in parens to disambiguate the slash." - } - }, + messages: { + requireParens: + "Wrap the regexp literal in parens to disambiguate the slash.", + }, + }, - create(context) { - const sourceCode = context.sourceCode; + create(context) { + const sourceCode = context.sourceCode; - return { + return { + Literal(node) { + const token = sourceCode.getFirstToken(node), + nodeType = token.type; - Literal(node) { - const token = sourceCode.getFirstToken(node), - nodeType = token.type; + if (nodeType === "RegularExpression") { + const beforeToken = sourceCode.getTokenBefore(node); + const afterToken = sourceCode.getTokenAfter(node); + const { parent } = node; - if (nodeType === "RegularExpression") { - const beforeToken = sourceCode.getTokenBefore(node); - const afterToken = sourceCode.getTokenAfter(node); - const { parent } = node; - - if (parent.type === "MemberExpression" && parent.object === node && - !(beforeToken && beforeToken.value === "(" && afterToken && afterToken.value === ")")) { - context.report({ - node, - messageId: "requireParens", - fix: fixer => fixer.replaceText(node, `(${sourceCode.getText(node)})`) - }); - } - } - } - }; - - } + if ( + parent.type === "MemberExpression" && + parent.object === node && + !( + beforeToken && + beforeToken.value === "(" && + afterToken && + afterToken.value === ")" + ) + ) { + context.report({ + node, + messageId: "requireParens", + fix: fixer => + fixer.replaceText( + node, + `(${sourceCode.getText(node)})`, + ), + }); + } + } + }, + }; + }, }; diff --git a/lib/rules/yield-star-spacing.js b/lib/rules/yield-star-spacing.js index dee9b538d19f..a7a61fe946f5 100644 --- a/lib/rules/yield-star-spacing.js +++ b/lib/rules/yield-star-spacing.js @@ -12,137 +12,148 @@ /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "yield-star-spacing", - url: "https://eslint.style/rules/js/yield-star-spacing" - } - } - ] - }, - type: "layout", - - docs: { - description: "Require or disallow spacing around the `*` in `yield*` expressions", - recommended: false, - url: "https://eslint.org/docs/latest/rules/yield-star-spacing" - }, - - fixable: "whitespace", - - schema: [ - { - oneOf: [ - { - enum: ["before", "after", "both", "neither"] - }, - { - type: "object", - properties: { - before: { type: "boolean" }, - after: { type: "boolean" } - }, - additionalProperties: false - } - ] - } - ], - messages: { - missingBefore: "Missing space before *.", - missingAfter: "Missing space after *.", - unexpectedBefore: "Unexpected space before *.", - unexpectedAfter: "Unexpected space after *." - } - }, - - create(context) { - const sourceCode = context.sourceCode; - - const mode = (function(option) { - if (!option || typeof option === "string") { - return { - before: { before: true, after: false }, - after: { before: false, after: true }, - both: { before: true, after: true }, - neither: { before: false, after: false } - }[option || "after"]; - } - return option; - }(context.options[0])); - - /** - * Checks the spacing between two tokens before or after the star token. - * @param {string} side Either "before" or "after". - * @param {Token} leftToken `function` keyword token if side is "before", or - * star token if side is "after". - * @param {Token} rightToken Star token if side is "before", or identifier - * token if side is "after". - * @returns {void} - */ - function checkSpacing(side, leftToken, rightToken) { - if (sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== mode[side]) { - const after = leftToken.value === "*"; - const spaceRequired = mode[side]; - const node = after ? leftToken : rightToken; - let messageId; - - if (spaceRequired) { - messageId = side === "before" ? "missingBefore" : "missingAfter"; - } else { - messageId = side === "before" ? "unexpectedBefore" : "unexpectedAfter"; - } - - context.report({ - node, - messageId, - fix(fixer) { - if (spaceRequired) { - if (after) { - return fixer.insertTextAfter(node, " "); - } - return fixer.insertTextBefore(node, " "); - } - return fixer.removeRange([leftToken.range[1], rightToken.range[0]]); - } - }); - } - } - - /** - * Enforces the spacing around the star if node is a yield* expression. - * @param {ASTNode} node A yield expression node. - * @returns {void} - */ - function checkExpression(node) { - if (!node.delegate) { - return; - } - - const tokens = sourceCode.getFirstTokens(node, 3); - const yieldToken = tokens[0]; - const starToken = tokens[1]; - const nextToken = tokens[2]; - - checkSpacing("before", yieldToken, starToken); - checkSpacing("after", starToken, nextToken); - } - - return { - YieldExpression: checkExpression - }; - - } + meta: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "yield-star-spacing", + url: "https://eslint.style/rules/js/yield-star-spacing", + }, + }, + ], + }, + type: "layout", + + docs: { + description: + "Require or disallow spacing around the `*` in `yield*` expressions", + recommended: false, + url: "https://eslint.org/docs/latest/rules/yield-star-spacing", + }, + + fixable: "whitespace", + + schema: [ + { + oneOf: [ + { + enum: ["before", "after", "both", "neither"], + }, + { + type: "object", + properties: { + before: { type: "boolean" }, + after: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, + ], + messages: { + missingBefore: "Missing space before *.", + missingAfter: "Missing space after *.", + unexpectedBefore: "Unexpected space before *.", + unexpectedAfter: "Unexpected space after *.", + }, + }, + + create(context) { + const sourceCode = context.sourceCode; + + const mode = (function (option) { + if (!option || typeof option === "string") { + return { + before: { before: true, after: false }, + after: { before: false, after: true }, + both: { before: true, after: true }, + neither: { before: false, after: false }, + }[option || "after"]; + } + return option; + })(context.options[0]); + + /** + * Checks the spacing between two tokens before or after the star token. + * @param {string} side Either "before" or "after". + * @param {Token} leftToken `function` keyword token if side is "before", or + * star token if side is "after". + * @param {Token} rightToken Star token if side is "before", or identifier + * token if side is "after". + * @returns {void} + */ + function checkSpacing(side, leftToken, rightToken) { + if ( + sourceCode.isSpaceBetweenTokens(leftToken, rightToken) !== + mode[side] + ) { + const after = leftToken.value === "*"; + const spaceRequired = mode[side]; + const node = after ? leftToken : rightToken; + let messageId; + + if (spaceRequired) { + messageId = + side === "before" ? "missingBefore" : "missingAfter"; + } else { + messageId = + side === "before" + ? "unexpectedBefore" + : "unexpectedAfter"; + } + + context.report({ + node, + messageId, + fix(fixer) { + if (spaceRequired) { + if (after) { + return fixer.insertTextAfter(node, " "); + } + return fixer.insertTextBefore(node, " "); + } + return fixer.removeRange([ + leftToken.range[1], + rightToken.range[0], + ]); + }, + }); + } + } + + /** + * Enforces the spacing around the star if node is a yield* expression. + * @param {ASTNode} node A yield expression node. + * @returns {void} + */ + function checkExpression(node) { + if (!node.delegate) { + return; + } + + const tokens = sourceCode.getFirstTokens(node, 3); + const yieldToken = tokens[0]; + const starToken = tokens[1]; + const nextToken = tokens[2]; + + checkSpacing("before", yieldToken, starToken); + checkSpacing("after", starToken, nextToken); + } + + return { + YieldExpression: checkExpression, + }; + }, }; diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js index 2f7bfff84af6..863f1feebdbd 100644 --- a/lib/rules/yoda.js +++ b/lib/rules/yoda.js @@ -20,7 +20,7 @@ const astUtils = require("./utils/ast-utils"); * @returns {boolean} Whether or not it is a comparison operator. */ function isComparisonOperator(operator) { - return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator); + return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator); } /** @@ -29,7 +29,7 @@ function isComparisonOperator(operator) { * @returns {boolean} Whether or not it is an equality operator. */ function isEqualityOperator(operator) { - return /^(==|===)$/u.test(operator); + return /^(==|===)$/u.test(operator); } /** @@ -39,7 +39,7 @@ function isEqualityOperator(operator) { * @returns {boolean} Whether the operator is used in range tests. */ function isRangeTestOperator(operator) { - return ["<", "<="].includes(operator); + return ["<", "<="].includes(operator); } /** @@ -50,12 +50,12 @@ function isRangeTestOperator(operator) { * real literal and should be treated as such. */ function isNegativeNumericLiteral(node) { - return ( - node.type === "UnaryExpression" && - node.operator === "-" && - node.prefix && - astUtils.isNumericLiteral(node.argument) - ); + return ( + node.type === "UnaryExpression" && + node.operator === "-" && + node.prefix && + astUtils.isNumericLiteral(node.argument) + ); } /** @@ -64,7 +64,9 @@ function isNegativeNumericLiteral(node) { * @returns {boolean} True if the node should be treated as a single Literal node. */ function looksLikeLiteral(node) { - return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node); + return ( + isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node) + ); } /** @@ -79,27 +81,27 @@ function looksLikeLiteral(node) { * 4. Otherwise `null`. */ function getNormalizedLiteral(node) { - if (node.type === "Literal") { - return node; - } - - if (isNegativeNumericLiteral(node)) { - return { - type: "Literal", - value: -node.argument.value, - raw: `-${node.argument.value}` - }; - } - - if (astUtils.isStaticTemplateLiteral(node)) { - return { - type: "Literal", - value: node.quasis[0].value.cooked, - raw: node.quasis[0].value.raw - }; - } - - return null; + if (node.type === "Literal") { + return node; + } + + if (isNegativeNumericLiteral(node)) { + return { + type: "Literal", + value: -node.argument.value, + raw: `-${node.argument.value}`, + }; + } + + if (astUtils.isStaticTemplateLiteral(node)) { + return { + type: "Literal", + value: node.quasis[0].value.cooked, + raw: node.quasis[0].value.raw, + }; + } + + return null; } //------------------------------------------------------------------------------ @@ -108,244 +110,253 @@ function getNormalizedLiteral(node) { /** @type {import('../shared/types').Rule} */ module.exports = { - meta: { - 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" - }, - - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - exceptRange: { - type: "boolean" - }, - onlyEquality: { - type: "boolean" - } - }, - additionalProperties: false - } - ], - - fixable: "code", - messages: { - expected: - "Expected literal to be on the {{expectedSide}} side of {{operator}}." - } - }, - - create(context) { - const [when, { exceptRange, onlyEquality }] = context.options; - const always = when === "always"; - const sourceCode = context.sourceCode; - - /** - * Determines whether node represents a range test. - * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside" - * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and - * both operators must be `<` or `<=`. Finally, the literal on the left side - * must be less than or equal to the literal on the right side so that the - * test makes any sense. - * @param {ASTNode} node LogicalExpression node to test. - * @returns {boolean} Whether node is a range test. - */ - function isRangeTest(node) { - const left = node.left, - right = node.right; - - /** - * Determines whether node is of the form `0 <= x && x < 1`. - * @returns {boolean} Whether node is a "between" range test. - */ - function isBetweenTest() { - if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) { - const leftLiteral = getNormalizedLiteral(left.left); - const rightLiteral = getNormalizedLiteral(right.right); - - if (leftLiteral === null && rightLiteral === null) { - return false; - } - - if (rightLiteral === null || leftLiteral === null) { - return true; - } - - if (leftLiteral.value <= rightLiteral.value) { - return true; - } - } - return false; - } - - /** - * Determines whether node is of the form `x < 0 || 1 <= x`. - * @returns {boolean} Whether node is an "outside" range test. - */ - function isOutsideTest() { - if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) { - const leftLiteral = getNormalizedLiteral(left.right); - const rightLiteral = getNormalizedLiteral(right.left); - - if (leftLiteral === null && rightLiteral === null) { - return false; - } - - if (rightLiteral === null || leftLiteral === null) { - return true; - } - - if (leftLiteral.value <= rightLiteral.value) { - return true; - } - } - - return false; - } - - /** - * Determines whether node is wrapped in parentheses. - * @returns {boolean} Whether node is preceded immediately by an open - * paren token and followed immediately by a close - * paren token. - */ - function isParenWrapped() { - return astUtils.isParenthesised(sourceCode, node); - } - - return ( - node.type === "LogicalExpression" && - left.type === "BinaryExpression" && - right.type === "BinaryExpression" && - isRangeTestOperator(left.operator) && - isRangeTestOperator(right.operator) && - (isBetweenTest() || isOutsideTest()) && - isParenWrapped() - ); - } - - const OPERATOR_FLIP_MAP = { - "===": "===", - "!==": "!==", - "==": "==", - "!=": "!=", - "<": ">", - ">": "<", - "<=": ">=", - ">=": "<=" - }; - - /** - * Returns a string representation of a BinaryExpression node with its sides/operator flipped around. - * @param {ASTNode} node The BinaryExpression node - * @returns {string} A string representation of the node with the sides and operator flipped - */ - function getFlippedString(node) { - const operatorToken = sourceCode.getFirstTokenBetween( - node.left, - node.right, - token => token.value === node.operator - ); - const lastLeftToken = sourceCode.getTokenBefore(operatorToken); - const firstRightToken = sourceCode.getTokenAfter(operatorToken); - - const source = sourceCode.getText(); - - const leftText = source.slice( - node.range[0], - lastLeftToken.range[1] - ); - const textBeforeOperator = source.slice( - lastLeftToken.range[1], - operatorToken.range[0] - ); - const textAfterOperator = source.slice( - operatorToken.range[1], - firstRightToken.range[0] - ); - const rightText = source.slice( - firstRightToken.range[0], - node.range[1] - ); - - const tokenBefore = sourceCode.getTokenBefore(node); - const tokenAfter = sourceCode.getTokenAfter(node); - let prefix = ""; - let suffix = ""; - - if ( - tokenBefore && - tokenBefore.range[1] === node.range[0] && - !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken) - ) { - prefix = " "; - } - - if ( - tokenAfter && - node.range[1] === tokenAfter.range[0] && - !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter) - ) { - suffix = " "; - } - - return ( - prefix + - rightText + - textBeforeOperator + - OPERATOR_FLIP_MAP[operatorToken.value] + - textAfterOperator + - leftText + - suffix - ); - } - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - return { - BinaryExpression(node) { - const expectedLiteral = always ? node.left : node.right; - const expectedNonLiteral = always ? node.right : node.left; - - // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error. - if ( - (expectedNonLiteral.type === "Literal" || - looksLikeLiteral(expectedNonLiteral)) && - !( - expectedLiteral.type === "Literal" || - looksLikeLiteral(expectedLiteral) - ) && - !(!isEqualityOperator(node.operator) && onlyEquality) && - isComparisonOperator(node.operator) && - !(exceptRange && isRangeTest(node.parent)) - ) { - context.report({ - node, - messageId: "expected", - data: { - operator: node.operator, - expectedSide: always ? "left" : "right" - }, - fix: fixer => - fixer.replaceText(node, getFlippedString(node)) - }); - } - } - }; - } + meta: { + 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", + }, + + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + exceptRange: { + type: "boolean", + }, + onlyEquality: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + + fixable: "code", + messages: { + expected: + "Expected literal to be on the {{expectedSide}} side of {{operator}}.", + }, + }, + + create(context) { + const [when, { exceptRange, onlyEquality }] = context.options; + const always = when === "always"; + const sourceCode = context.sourceCode; + + /** + * Determines whether node represents a range test. + * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside" + * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and + * both operators must be `<` or `<=`. Finally, the literal on the left side + * must be less than or equal to the literal on the right side so that the + * test makes any sense. + * @param {ASTNode} node LogicalExpression node to test. + * @returns {boolean} Whether node is a range test. + */ + function isRangeTest(node) { + const left = node.left, + right = node.right; + + /** + * Determines whether node is of the form `0 <= x && x < 1`. + * @returns {boolean} Whether node is a "between" range test. + */ + function isBetweenTest() { + if ( + node.operator === "&&" && + astUtils.isSameReference(left.right, right.left) + ) { + const leftLiteral = getNormalizedLiteral(left.left); + const rightLiteral = getNormalizedLiteral(right.right); + + if (leftLiteral === null && rightLiteral === null) { + return false; + } + + if (rightLiteral === null || leftLiteral === null) { + return true; + } + + if (leftLiteral.value <= rightLiteral.value) { + return true; + } + } + return false; + } + + /** + * Determines whether node is of the form `x < 0 || 1 <= x`. + * @returns {boolean} Whether node is an "outside" range test. + */ + function isOutsideTest() { + if ( + node.operator === "||" && + astUtils.isSameReference(left.left, right.right) + ) { + const leftLiteral = getNormalizedLiteral(left.right); + const rightLiteral = getNormalizedLiteral(right.left); + + if (leftLiteral === null && rightLiteral === null) { + return false; + } + + if (rightLiteral === null || leftLiteral === null) { + return true; + } + + if (leftLiteral.value <= rightLiteral.value) { + return true; + } + } + + return false; + } + + /** + * Determines whether node is wrapped in parentheses. + * @returns {boolean} Whether node is preceded immediately by an open + * paren token and followed immediately by a close + * paren token. + */ + function isParenWrapped() { + return astUtils.isParenthesised(sourceCode, node); + } + + return ( + node.type === "LogicalExpression" && + left.type === "BinaryExpression" && + right.type === "BinaryExpression" && + isRangeTestOperator(left.operator) && + isRangeTestOperator(right.operator) && + (isBetweenTest() || isOutsideTest()) && + isParenWrapped() + ); + } + + const OPERATOR_FLIP_MAP = { + "===": "===", + "!==": "!==", + "==": "==", + "!=": "!=", + "<": ">", + ">": "<", + "<=": ">=", + ">=": "<=", + }; + + /** + * Returns a string representation of a BinaryExpression node with its sides/operator flipped around. + * @param {ASTNode} node The BinaryExpression node + * @returns {string} A string representation of the node with the sides and operator flipped + */ + function getFlippedString(node) { + const operatorToken = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + ); + const lastLeftToken = sourceCode.getTokenBefore(operatorToken); + const firstRightToken = sourceCode.getTokenAfter(operatorToken); + + const source = sourceCode.getText(); + + const leftText = source.slice( + node.range[0], + lastLeftToken.range[1], + ); + const textBeforeOperator = source.slice( + lastLeftToken.range[1], + operatorToken.range[0], + ); + const textAfterOperator = source.slice( + operatorToken.range[1], + firstRightToken.range[0], + ); + const rightText = source.slice( + firstRightToken.range[0], + node.range[1], + ); + + const tokenBefore = sourceCode.getTokenBefore(node); + const tokenAfter = sourceCode.getTokenAfter(node); + let prefix = ""; + let suffix = ""; + + if ( + tokenBefore && + tokenBefore.range[1] === node.range[0] && + !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken) + ) { + prefix = " "; + } + + if ( + tokenAfter && + node.range[1] === tokenAfter.range[0] && + !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter) + ) { + suffix = " "; + } + + return ( + prefix + + rightText + + textBeforeOperator + + OPERATOR_FLIP_MAP[operatorToken.value] + + textAfterOperator + + leftText + + suffix + ); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + return { + BinaryExpression(node) { + const expectedLiteral = always ? node.left : node.right; + const expectedNonLiteral = always ? node.right : node.left; + + // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error. + if ( + (expectedNonLiteral.type === "Literal" || + looksLikeLiteral(expectedNonLiteral)) && + !( + expectedLiteral.type === "Literal" || + looksLikeLiteral(expectedLiteral) + ) && + !(!isEqualityOperator(node.operator) && onlyEquality) && + isComparisonOperator(node.operator) && + !(exceptRange && isRangeTest(node.parent)) + ) { + context.report({ + node, + messageId: "expected", + data: { + operator: node.operator, + expectedSide: always ? "left" : "right", + }, + fix: fixer => + fixer.replaceText(node, getFlippedString(node)), + }); + } + }, + }; + }, }; diff --git a/lib/services/parser-service.js b/lib/services/parser-service.js index c138b8314749..580bb69d5948 100644 --- a/lib/services/parser-service.js +++ b/lib/services/parser-service.js @@ -22,44 +22,44 @@ * The parser for ESLint. */ class ParserService { + /** + * Parses the given file synchronously. + * @param {VFile} file The file to parse. + * @param {{language:Language,languageOptions:LanguageOptions}} config The configuration to use. + * @returns {Object} An object with the parsed source code or errors. + * @throws {Error} If the parser returns a promise. + */ + parseSync(file, config) { + const { language, languageOptions } = config; + const result = language.parse(file, { languageOptions }); - /** - * Parses the given file synchronously. - * @param {VFile} file The file to parse. - * @param {{language:Language,languageOptions:LanguageOptions}} config The configuration to use. - * @returns {Object} An object with the parsed source code or errors. - * @throws {Error} If the parser returns a promise. - */ - parseSync(file, config) { + if (typeof result.then === "function") { + throw new Error("Unsupported: Language parser returned a promise."); + } - const { language, languageOptions } = config; - const result = language.parse(file, { languageOptions }); + if (result.ok) { + return { + ok: true, + sourceCode: language.createSourceCode(file, result, { + languageOptions, + }), + }; + } - if (typeof result.then === "function") { - throw new Error("Unsupported: Language parser returned a promise."); - } - - if (result.ok) { - return { - ok: true, - sourceCode: language.createSourceCode(file, result, { languageOptions }) - }; - } - - // if we made it to here there was an error - return { - ok: false, - errors: result.errors.map(error => ({ - ruleId: null, - nodeType: null, - fatal: true, - severity: 2, - message: `Parsing error: ${error.message}`, - line: error.line, - column: error.column - })) - }; - } + // if we made it to here there was an error + return { + ok: false, + errors: result.errors.map(error => ({ + ruleId: null, + nodeType: null, + fatal: true, + severity: 2, + message: `Parsing error: ${error.message}`, + line: error.line, + column: error.column, + })), + }; + } } module.exports = { ParserService }; diff --git a/lib/services/processor-service.js b/lib/services/processor-service.js index 403b97c1a484..28d2adf3fc52 100644 --- a/lib/services/processor-service.js +++ b/lib/services/processor-service.js @@ -31,79 +31,72 @@ const { VFile } = require("../linter/vfile.js"); * The service that applies processors to files. */ class ProcessorService { - - /** - * Preprocesses the given file synchronously. - * @param {VFile} file The file to preprocess. - * @param {{processor:Processor}} config The configuration to use. - * @returns {{ok:boolean, files?: Array, errors?: Array}} An array of preprocessed files or errors. - * @throws {Error} If the preprocessor returns a promise. - */ - preprocessSync(file, config) { - - const { processor } = config; - let blocks; - - try { - blocks = processor.preprocess(file.rawBody, file.path); - } catch (ex) { - - // If the message includes a leading line number, strip it: - const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; - - return { - ok: false, - errors: [ - { - ruleId: null, - fatal: true, - severity: 2, - message, - line: ex.lineNumber, - column: ex.column, - nodeType: null - } - ] - }; - } - - if (typeof blocks.then === "function") { - throw new Error("Unsupported: Preprocessor returned a promise."); - } - - return { - ok: true, - files: blocks.map((block, i) => { - - // Legacy behavior: return the block as a string - if (typeof block === "string") { - return block; - } - - const filePath = path.join(file.path, `${i}_${block.filename}`); - - return new VFile(filePath, block.text, { - physicalPath: file.physicalPath - }); - }) - }; - - } - - /** - * Postprocesses the given messages synchronously. - * @param {VFile} file The file to postprocess. - * @param {LintMessage[][]} messages The messages to postprocess. - * @param {{processor:Processor}} config The configuration to use. - * @returns {LintMessage[]} The postprocessed messages. - */ - postprocessSync(file, messages, config) { - - const { processor } = config; - - return processor.postprocess(messages, file.path); - } - + /** + * Preprocesses the given file synchronously. + * @param {VFile} file The file to preprocess. + * @param {{processor:Processor}} config The configuration to use. + * @returns {{ok:boolean, files?: Array, errors?: Array}} An array of preprocessed files or errors. + * @throws {Error} If the preprocessor returns a promise. + */ + preprocessSync(file, config) { + const { processor } = config; + let blocks; + + try { + blocks = processor.preprocess(file.rawBody, file.path); + } catch (ex) { + // If the message includes a leading line number, strip it: + const message = `Preprocessing error: ${ex.message.replace(/^line \d+:/iu, "").trim()}`; + + return { + ok: false, + errors: [ + { + ruleId: null, + fatal: true, + severity: 2, + message, + line: ex.lineNumber, + column: ex.column, + nodeType: null, + }, + ], + }; + } + + if (typeof blocks.then === "function") { + throw new Error("Unsupported: Preprocessor returned a promise."); + } + + return { + ok: true, + files: blocks.map((block, i) => { + // Legacy behavior: return the block as a string + if (typeof block === "string") { + return block; + } + + const filePath = path.join(file.path, `${i}_${block.filename}`); + + return new VFile(filePath, block.text, { + physicalPath: file.physicalPath, + }); + }), + }; + } + + /** + * Postprocesses the given messages synchronously. + * @param {VFile} file The file to postprocess. + * @param {LintMessage[][]} messages The messages to postprocess. + * @param {{processor:Processor}} config The configuration to use. + * @returns {LintMessage[]} The postprocessed messages. + */ + postprocessSync(file, messages, config) { + const { processor } = config; + + return processor.postprocess(messages, file.path); + } } module.exports = { ProcessorService }; diff --git a/lib/shared/ajv.js b/lib/shared/ajv.js index f4f6a62183d4..5c5c41bd7470 100644 --- a/lib/shared/ajv.js +++ b/lib/shared/ajv.js @@ -9,26 +9,26 @@ //------------------------------------------------------------------------------ const Ajv = require("ajv"), - metaSchema = require("ajv/lib/refs/json-schema-draft-04.json"); + metaSchema = require("ajv/lib/refs/json-schema-draft-04.json"); //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = (additionalOptions = {}) => { - const ajv = new Ajv({ - meta: false, - useDefaults: true, - validateSchema: false, - missingRefs: "ignore", - verbose: true, - schemaId: "auto", - ...additionalOptions - }); + const ajv = new Ajv({ + meta: false, + useDefaults: true, + validateSchema: false, + missingRefs: "ignore", + verbose: true, + schemaId: "auto", + ...additionalOptions, + }); - ajv.addMetaSchema(metaSchema); - // eslint-disable-next-line no-underscore-dangle -- Ajv's API - ajv._opts.defaultMeta = metaSchema.id; + ajv.addMetaSchema(metaSchema); + // eslint-disable-next-line no-underscore-dangle -- Ajv's API + ajv._opts.defaultMeta = metaSchema.id; - return ajv; + return ajv; }; diff --git a/lib/shared/assert.js b/lib/shared/assert.js index 00bbe07d0db3..4dcaf41f86c5 100644 --- a/lib/shared/assert.js +++ b/lib/shared/assert.js @@ -13,10 +13,9 @@ * @throws {Error} When the condition is not truthy. */ function ok(value, message = "Assertion failed.") { - if (!value) { - throw new Error(message); - } + if (!value) { + throw new Error(message); + } } - module.exports = ok; diff --git a/lib/shared/ast-utils.js b/lib/shared/ast-utils.js index 4ebd49c3ce6a..f7736828b77e 100644 --- a/lib/shared/ast-utils.js +++ b/lib/shared/ast-utils.js @@ -8,7 +8,8 @@ */ "use strict"; -const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u; +const breakableTypePattern = + /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/u; const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u; const shebangPattern = /^#!([^\r\n]+)/u; @@ -18,12 +19,12 @@ const shebangPattern = /^#!([^\r\n]+)/u; * @returns {RegExp} A global regular expression that matches line terminators */ function createGlobalLinebreakMatcher() { - return new RegExp(lineBreakPattern.source, "gu"); + return new RegExp(lineBreakPattern.source, "gu"); } module.exports = { - breakableTypePattern, - lineBreakPattern, - createGlobalLinebreakMatcher, - shebangPattern + breakableTypePattern, + lineBreakPattern, + createGlobalLinebreakMatcher, + shebangPattern, }; diff --git a/lib/shared/deep-merge-arrays.js b/lib/shared/deep-merge-arrays.js index d34149dd9318..4a8289c46e79 100644 --- a/lib/shared/deep-merge-arrays.js +++ b/lib/shared/deep-merge-arrays.js @@ -11,7 +11,7 @@ * @returns {boolean} Whether value is an object */ function isObjectNotArray(value) { - return typeof value === "object" && value !== null && !Array.isArray(value); + return typeof value === "object" && value !== null && !Array.isArray(value); } /** @@ -21,23 +21,23 @@ function isObjectNotArray(value) { * @returns {T | U | (T & U)} Merged equivalent of second on top of first. */ function deepMergeObjects(first, second) { - if (second === void 0) { - return first; - } + if (second === void 0) { + return first; + } - if (!isObjectNotArray(first) || !isObjectNotArray(second)) { - return second; - } + if (!isObjectNotArray(first) || !isObjectNotArray(second)) { + return second; + } - const result = { ...first, ...second }; + const result = { ...first, ...second }; - for (const key of Object.keys(second)) { - if (Object.prototype.propertyIsEnumerable.call(first, key)) { - result[key] = deepMergeObjects(first[key], second[key]); - } - } + for (const key of Object.keys(second)) { + if (Object.prototype.propertyIsEnumerable.call(first, key)) { + result[key] = deepMergeObjects(first[key], second[key]); + } + } - return result; + return result; } /** @@ -47,14 +47,16 @@ function deepMergeObjects(first, second) { * @returns {(T | U | (T & U))[]} Merged equivalent of second on top of first. */ function deepMergeArrays(first, second) { - if (!first || !second) { - return second || first || []; - } - - return [ - ...first.map((value, i) => deepMergeObjects(value, i < second.length ? second[i] : void 0)), - ...second.slice(first.length) - ]; + if (!first || !second) { + return second || first || []; + } + + return [ + ...first.map((value, i) => + deepMergeObjects(value, i < second.length ? second[i] : void 0), + ), + ...second.slice(first.length), + ]; } module.exports = { deepMergeArrays }; diff --git a/lib/shared/directives.js b/lib/shared/directives.js index ff67b00a553e..f1dddf597d23 100644 --- a/lib/shared/directives.js +++ b/lib/shared/directives.js @@ -8,8 +8,9 @@ */ "use strict"; -const directivesPattern = /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u; +const directivesPattern = + /^(eslint(?:-env|-enable|-disable(?:(?:-next)?-line)?)?|exported|globals?)(?:\s|$)/u; module.exports = { - directivesPattern + directivesPattern, }; diff --git a/lib/shared/flags.js b/lib/shared/flags.js index 7a232d2b1539..beeabb81d523 100644 --- a/lib/shared/flags.js +++ b/lib/shared/flags.js @@ -26,8 +26,11 @@ * @type {Map} */ const activeFlags = new Map([ - ["test_only", "Used only for testing."], - ["unstable_config_lookup_from_file", "Look up `eslint.config.js` from the file being linted."] + ["test_only", "Used only for testing."], + [ + "unstable_config_lookup_from_file", + "Look up `eslint.config.js` from the file being linted.", + ], ]); /** @@ -35,10 +38,36 @@ const activeFlags = new Map([ * @type {Map} */ const inactiveFlags = new Map([ - ["test_only_replaced", { description: "Used only for testing flags that have been replaced by other flags.", replacedBy: "test_only" }], - ["test_only_enabled_by_default", { description: "Used only for testing flags whose features have been enabled by default.", replacedBy: null }], - ["test_only_abandoned", { description: "Used only for testing flags whose features have been abandoned." }], - ["unstable_ts_config", { description: "Enable TypeScript configuration files.", replacedBy: null }] + [ + "test_only_replaced", + { + description: + "Used only for testing flags that have been replaced by other flags.", + replacedBy: "test_only", + }, + ], + [ + "test_only_enabled_by_default", + { + description: + "Used only for testing flags whose features have been enabled by default.", + replacedBy: null, + }, + ], + [ + "test_only_abandoned", + { + description: + "Used only for testing flags whose features have been abandoned.", + }, + ], + [ + "unstable_ts_config", + { + description: "Enable TypeScript configuration files.", + replacedBy: null, + }, + ], ]); /** @@ -47,20 +76,20 @@ const inactiveFlags = new Map([ * @returns {string} Message describing the reason the flag is inactive. */ function getInactivityReasonMessage({ replacedBy }) { - if (typeof replacedBy === "undefined") { - return "This feature has been abandoned."; - } + if (typeof replacedBy === "undefined") { + return "This feature has been abandoned."; + } - if (typeof replacedBy === "string") { - return `This flag has been renamed '${replacedBy}' to reflect its stabilization. Please use '${replacedBy}' instead.`; - } + if (typeof replacedBy === "string") { + return `This flag has been renamed '${replacedBy}' to reflect its stabilization. Please use '${replacedBy}' instead.`; + } - // null - return "This feature is now enabled by default."; + // null + return "This feature is now enabled by default."; } module.exports = { - activeFlags, - inactiveFlags, - getInactivityReasonMessage + activeFlags, + inactiveFlags, + getInactivityReasonMessage, }; diff --git a/lib/shared/logging.js b/lib/shared/logging.js index 9310e586c23b..3e8dfb0b071a 100644 --- a/lib/shared/logging.js +++ b/lib/shared/logging.js @@ -9,31 +9,30 @@ /* c8 ignore next */ module.exports = { + /** + * Cover for console.info + * @param {...any} args The elements to log. + * @returns {void} + */ + info(...args) { + console.log(...args); + }, - /** - * Cover for console.info - * @param {...any} args The elements to log. - * @returns {void} - */ - info(...args) { - console.log(...args); - }, + /** + * Cover for console.warn + * @param {...any} args The elements to log. + * @returns {void} + */ + warn(...args) { + console.warn(...args); + }, - /** - * Cover for console.warn - * @param {...any} args The elements to log. - * @returns {void} - */ - warn(...args) { - console.warn(...args); - }, - - /** - * Cover for console.error - * @param {...any} args The elements to log. - * @returns {void} - */ - error(...args) { - console.error(...args); - } + /** + * Cover for console.error + * @param {...any} args The elements to log. + * @returns {void} + */ + error(...args) { + console.error(...args); + }, }; diff --git a/lib/shared/option-utils.js b/lib/shared/option-utils.js index 0053a3a05351..0917ba5213fd 100644 --- a/lib/shared/option-utils.js +++ b/lib/shared/option-utils.js @@ -14,43 +14,50 @@ * @returns {boolean} Whether input includes an explicit difference. */ function containsDifferentProperty(input, original) { - if (input === original) { - return false; - } - - if ( - typeof input !== typeof original || - Array.isArray(input) !== Array.isArray(original) - ) { - return true; - } - - if (Array.isArray(input)) { - return ( - input.length !== original.length || - input.some((value, i) => - containsDifferentProperty(value, original[i])) - ); - } - - if (typeof input === "object") { - if (input === null || original === null) { - return true; - } - - const inputKeys = Object.keys(input); - const originalKeys = Object.keys(original); - - return inputKeys.length !== originalKeys.length || inputKeys.some( - inputKey => - !Object.hasOwn(original, inputKey) || - containsDifferentProperty(input[inputKey], original[inputKey]) - ); - } - - return true; + if (input === original) { + return false; + } + + if ( + typeof input !== typeof original || + Array.isArray(input) !== Array.isArray(original) + ) { + return true; + } + + if (Array.isArray(input)) { + return ( + input.length !== original.length || + input.some((value, i) => + containsDifferentProperty(value, original[i]), + ) + ); + } + + if (typeof input === "object") { + if (input === null || original === null) { + return true; + } + + const inputKeys = Object.keys(input); + const originalKeys = Object.keys(original); + + return ( + inputKeys.length !== originalKeys.length || + inputKeys.some( + inputKey => + !Object.hasOwn(original, inputKey) || + containsDifferentProperty( + input[inputKey], + original[inputKey], + ), + ) + ); + } + + return true; } module.exports = { - containsDifferentProperty + containsDifferentProperty, }; diff --git a/lib/shared/runtime-info.js b/lib/shared/runtime-info.js index 29de6fc8d840..5e5533b2a8d3 100644 --- a/lib/shared/runtime-info.js +++ b/lib/shared/runtime-info.js @@ -24,129 +24,138 @@ const packageJson = require("../../package.json"); * @returns {string} A string that contains execution environment information. */ function environment() { - const cache = new Map(); - - /** - * Checks if a path is a child of a directory. - * @param {string} parentPath The parent path to check. - * @param {string} childPath The path to check. - * @returns {boolean} Whether or not the given path is a child of a directory. - */ - function isChildOfDirectory(parentPath, childPath) { - return !path.relative(parentPath, childPath).startsWith(".."); - } - - /** - * Synchronously executes a shell command and formats the result. - * @param {string} cmd The command to execute. - * @param {Array} args The arguments to be executed with the command. - * @throws {Error} As may be collected by `cross-spawn.sync`. - * @returns {string} The version returned by the command. - */ - function execCommand(cmd, args) { - const key = [cmd, ...args].join(" "); - - if (cache.has(key)) { - return cache.get(key); - } - - const process = spawn.sync(cmd, args, { encoding: "utf8" }); - - if (process.error) { - throw process.error; - } - - const result = process.stdout.trim(); - - cache.set(key, result); - return result; - } - - /** - * Normalizes a version number. - * @param {string} versionStr The string to normalize. - * @returns {string} The normalized version number. - */ - function normalizeVersionStr(versionStr) { - return versionStr.startsWith("v") ? versionStr : `v${versionStr}`; - } - - /** - * Gets bin version. - * @param {string} bin The bin to check. - * @throws {Error} As may be collected by `cross-spawn.sync`. - * @returns {string} The normalized version returned by the command. - */ - function getBinVersion(bin) { - const binArgs = ["--version"]; - - try { - return normalizeVersionStr(execCommand(bin, binArgs)); - } catch (e) { - log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``); - throw e; - } - } - - /** - * Gets installed npm package version. - * @param {string} pkg The package to check. - * @param {boolean} global Whether to check globally or not. - * @throws {Error} As may be collected by `cross-spawn.sync`. - * @returns {string} The normalized version returned by the command. - */ - function getNpmPackageVersion(pkg, { global = false } = {}) { - const npmBinArgs = ["bin", "-g"]; - const npmLsArgs = ["ls", "--depth=0", "--json", pkg]; - - if (global) { - npmLsArgs.push("-g"); - } - - try { - const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); - - /* - * Checking globally returns an empty JSON object, while local checks - * include the name and version of the local project. - */ - if (Object.keys(parsedStdout).length === 0 || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) { - return "Not found"; - } - - const [, processBinPath] = process.argv; - let npmBinPath; - - try { - npmBinPath = execCommand("npm", npmBinArgs); - } catch (e) { - log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``); - throw e; - } - - const isGlobal = isChildOfDirectory(npmBinPath, processBinPath); - let pkgVersion = parsedStdout.dependencies.eslint.version; - - if ((global && isGlobal) || (!global && !isGlobal)) { - pkgVersion += " (Currently used)"; - } - - return normalizeVersionStr(pkgVersion); - } catch (e) { - log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``); - throw e; - } - } - - return [ - "Environment Info:", - "", - `Node version: ${getBinVersion("node")}`, - `npm version: ${getBinVersion("npm")}`, - `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`, - `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`, - `Operating System: ${os.platform()} ${os.release()}` - ].join("\n"); + const cache = new Map(); + + /** + * Checks if a path is a child of a directory. + * @param {string} parentPath The parent path to check. + * @param {string} childPath The path to check. + * @returns {boolean} Whether or not the given path is a child of a directory. + */ + function isChildOfDirectory(parentPath, childPath) { + return !path.relative(parentPath, childPath).startsWith(".."); + } + + /** + * Synchronously executes a shell command and formats the result. + * @param {string} cmd The command to execute. + * @param {Array} args The arguments to be executed with the command. + * @throws {Error} As may be collected by `cross-spawn.sync`. + * @returns {string} The version returned by the command. + */ + function execCommand(cmd, args) { + const key = [cmd, ...args].join(" "); + + if (cache.has(key)) { + return cache.get(key); + } + + const process = spawn.sync(cmd, args, { encoding: "utf8" }); + + if (process.error) { + throw process.error; + } + + const result = process.stdout.trim(); + + cache.set(key, result); + return result; + } + + /** + * Normalizes a version number. + * @param {string} versionStr The string to normalize. + * @returns {string} The normalized version number. + */ + function normalizeVersionStr(versionStr) { + return versionStr.startsWith("v") ? versionStr : `v${versionStr}`; + } + + /** + * Gets bin version. + * @param {string} bin The bin to check. + * @throws {Error} As may be collected by `cross-spawn.sync`. + * @returns {string} The normalized version returned by the command. + */ + function getBinVersion(bin) { + const binArgs = ["--version"]; + + try { + return normalizeVersionStr(execCommand(bin, binArgs)); + } catch (e) { + log.error( + `Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``, + ); + throw e; + } + } + + /** + * Gets installed npm package version. + * @param {string} pkg The package to check. + * @param {boolean} global Whether to check globally or not. + * @throws {Error} As may be collected by `cross-spawn.sync`. + * @returns {string} The normalized version returned by the command. + */ + function getNpmPackageVersion(pkg, { global = false } = {}) { + const npmBinArgs = ["bin", "-g"]; + const npmLsArgs = ["ls", "--depth=0", "--json", pkg]; + + if (global) { + npmLsArgs.push("-g"); + } + + try { + const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); + + /* + * Checking globally returns an empty JSON object, while local checks + * include the name and version of the local project. + */ + if ( + Object.keys(parsedStdout).length === 0 || + !(parsedStdout.dependencies && parsedStdout.dependencies.eslint) + ) { + return "Not found"; + } + + const [, processBinPath] = process.argv; + let npmBinPath; + + try { + npmBinPath = execCommand("npm", npmBinArgs); + } catch (e) { + log.error( + `Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``, + ); + throw e; + } + + const isGlobal = isChildOfDirectory(npmBinPath, processBinPath); + let pkgVersion = parsedStdout.dependencies.eslint.version; + + if ((global && isGlobal) || (!global && !isGlobal)) { + pkgVersion += " (Currently used)"; + } + + return normalizeVersionStr(pkgVersion); + } catch (e) { + log.error( + `Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``, + ); + throw e; + } + } + + return [ + "Environment Info:", + "", + `Node version: ${getBinVersion("node")}`, + `npm version: ${getBinVersion("npm")}`, + `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`, + `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}`, + `Operating System: ${os.platform()} ${os.release()}`, + ].join("\n"); } /** @@ -154,7 +163,7 @@ function environment() { * @returns {string} The version from the currently executing ESLint's package.json. */ function version() { - return `v${packageJson.version}`; + return `v${packageJson.version}`; } //------------------------------------------------------------------------------ @@ -162,7 +171,7 @@ function version() { //------------------------------------------------------------------------------ module.exports = { - __esModule: true, // Indicate intent for imports, remove ambiguity for Knip (see: https://github.com/eslint/eslint/pull/18005#discussion_r1484422616) - environment, - version + __esModule: true, // Indicate intent for imports, remove ambiguity for Knip (see: https://github.com/eslint/eslint/pull/18005#discussion_r1484422616) + environment, + version, }; diff --git a/lib/shared/serialization.js b/lib/shared/serialization.js index 69f5710f2a30..2d9795a42caa 100644 --- a/lib/shared/serialization.js +++ b/lib/shared/serialization.js @@ -12,14 +12,14 @@ * @private */ function isSerializablePrimitiveOrPlainObject(val) { - return ( - val === null || - typeof val === "string" || - typeof val === "boolean" || - typeof val === "number" || - (typeof val === "object" && val.constructor === Object) || - Array.isArray(val) - ); + return ( + val === null || + typeof val === "string" || + typeof val === "boolean" || + typeof val === "number" || + (typeof val === "object" && val.constructor === Object) || + Array.isArray(val) + ); } /** @@ -30,26 +30,26 @@ function isSerializablePrimitiveOrPlainObject(val) { * @returns {boolean} true if the value is serializable */ function isSerializable(val) { - if (!isSerializablePrimitiveOrPlainObject(val)) { - return false; - } - if (typeof val === "object") { - for (const property in val) { - if (Object.hasOwn(val, property)) { - if (!isSerializablePrimitiveOrPlainObject(val[property])) { - return false; - } - if (typeof val[property] === "object") { - if (!isSerializable(val[property])) { - return false; - } - } - } - } - } - return true; + if (!isSerializablePrimitiveOrPlainObject(val)) { + return false; + } + if (typeof val === "object") { + for (const property in val) { + if (Object.hasOwn(val, property)) { + if (!isSerializablePrimitiveOrPlainObject(val[property])) { + return false; + } + if (typeof val[property] === "object") { + if (!isSerializable(val[property])) { + return false; + } + } + } + } + } + return true; } module.exports = { - isSerializable + isSerializable, }; diff --git a/lib/shared/severity.js b/lib/shared/severity.js index 6b21469a9aaf..382704b5a66f 100644 --- a/lib/shared/severity.js +++ b/lib/shared/severity.js @@ -12,16 +12,16 @@ * @returns {string} severity string */ function normalizeSeverityToString(severity) { - if ([2, "2", "error"].includes(severity)) { - return "error"; - } - if ([1, "1", "warn"].includes(severity)) { - return "warn"; - } - if ([0, "0", "off"].includes(severity)) { - return "off"; - } - throw new Error(`Invalid severity value: ${severity}`); + if ([2, "2", "error"].includes(severity)) { + return "error"; + } + if ([1, "1", "warn"].includes(severity)) { + return "warn"; + } + if ([0, "0", "off"].includes(severity)) { + return "off"; + } + throw new Error(`Invalid severity value: ${severity}`); } /** @@ -31,19 +31,19 @@ function normalizeSeverityToString(severity) { * @returns {number} severity number */ function normalizeSeverityToNumber(severity) { - if ([2, "2", "error"].includes(severity)) { - return 2; - } - if ([1, "1", "warn"].includes(severity)) { - return 1; - } - if ([0, "0", "off"].includes(severity)) { - return 0; - } - throw new Error(`Invalid severity value: ${severity}`); + if ([2, "2", "error"].includes(severity)) { + return 2; + } + if ([1, "1", "warn"].includes(severity)) { + return 1; + } + if ([0, "0", "off"].includes(severity)) { + return 0; + } + throw new Error(`Invalid severity value: ${severity}`); } module.exports = { - normalizeSeverityToString, - normalizeSeverityToNumber + normalizeSeverityToString, + normalizeSeverityToNumber, }; diff --git a/lib/shared/stats.js b/lib/shared/stats.js index c5d4d1885d41..791b77083301 100644 --- a/lib/shared/stats.js +++ b/lib/shared/stats.js @@ -10,7 +10,7 @@ * @returns {[number, number]} t variable for tracking time */ function startTime() { - return process.hrtime(); + return process.hrtime(); } /** @@ -19,12 +19,12 @@ function startTime() { * @returns {number} The measured time in milliseconds */ function endTime(t) { - const time = process.hrtime(t); + const time = process.hrtime(t); - return time[0] * 1e3 + time[1] / 1e6; + return time[0] * 1e3 + time[1] / 1e6; } module.exports = { - startTime, - endTime + startTime, + endTime, }; diff --git a/lib/shared/string-utils.js b/lib/shared/string-utils.js index 31d0df982ca4..954c5bfff4ae 100644 --- a/lib/shared/string-utils.js +++ b/lib/shared/string-utils.js @@ -25,10 +25,10 @@ let segmenter; * @returns {string} The converted string */ function upperCaseFirst(string) { - if (string.length <= 1) { - return string.toUpperCase(); - } - return string[0].toUpperCase() + string.slice(1); + if (string.length <= 1) { + return string.toUpperCase(); + } + return string[0].toUpperCase() + string.slice(1); } /** @@ -37,22 +37,22 @@ function upperCaseFirst(string) { * @returns {number} The number of graphemes in `value`. */ function getGraphemeCount(value) { - if (ASCII_REGEX.test(value)) { - return value.length; - } + if (ASCII_REGEX.test(value)) { + return value.length; + } - segmenter ??= new Intl.Segmenter("en-US"); // en-US locale should be supported everywhere - let graphemeCount = 0; + segmenter ??= new Intl.Segmenter("en-US"); // en-US locale should be supported everywhere + let graphemeCount = 0; - // eslint-disable-next-line no-unused-vars -- for-of needs a variable - for (const unused of segmenter.segment(value)) { - graphemeCount++; - } + // eslint-disable-next-line no-unused-vars -- for-of needs a variable + for (const unused of segmenter.segment(value)) { + graphemeCount++; + } - return graphemeCount; + return graphemeCount; } module.exports = { - upperCaseFirst, - getGraphemeCount + upperCaseFirst, + getGraphemeCount, }; diff --git a/lib/shared/text-table.js b/lib/shared/text-table.js index ddd137d6ef7a..1dfd48166b50 100644 --- a/lib/shared/text-table.js +++ b/lib/shared/text-table.js @@ -32,36 +32,37 @@ "use strict"; -module.exports = function(rows_, opts) { - const hsep = " "; - const align = opts.align; - const stringLength = opts.stringLength; +module.exports = function (rows_, opts) { + const hsep = " "; + const align = opts.align; + const stringLength = opts.stringLength; - const sizes = rows_.reduce((acc, row) => { - row.forEach((c, ix) => { - const n = stringLength(c); + const sizes = rows_.reduce((acc, row) => { + row.forEach((c, ix) => { + const n = stringLength(c); - if (!acc[ix] || n > acc[ix]) { - acc[ix] = n; - } - }); - return acc; - }, []); + if (!acc[ix] || n > acc[ix]) { + acc[ix] = n; + } + }); + return acc; + }, []); - return rows_ - .map(row => - row - .map((c, ix) => { - const n = sizes[ix] - stringLength(c) || 0; - const s = Array(Math.max(n + 1, 1)).join(" "); + return rows_ + .map(row => + row + .map((c, ix) => { + const n = sizes[ix] - stringLength(c) || 0; + const s = Array(Math.max(n + 1, 1)).join(" "); - if (align[ix] === "r") { - return s + c; - } + if (align[ix] === "r") { + return s + c; + } - return c + s; - }) - .join(hsep) - .trimEnd()) - .join("\n"); + return c + s; + }) + .join(hsep) + .trimEnd(), + ) + .join("\n"); }; diff --git a/lib/shared/traverser.js b/lib/shared/traverser.js index 38b4e215132e..e13b2cfa5e01 100644 --- a/lib/shared/traverser.js +++ b/lib/shared/traverser.js @@ -21,8 +21,7 @@ const debug = require("debug")("eslint:traverser"); * @returns {void} */ function noop() { - - // do nothing. + // do nothing. } /** @@ -31,7 +30,7 @@ function noop() { * @returns {boolean} `true` if the value is an ASTNode. */ function isNode(x) { - return x !== null && typeof x === "object" && typeof x.type === "string"; + return x !== null && typeof x === "object" && typeof x.type === "string"; } /** @@ -41,155 +40,163 @@ function isNode(x) { * @returns {string[]} The visitor keys of the node. */ function getVisitorKeys(visitorKeys, node) { - let keys = visitorKeys[node.type]; - - if (!keys) { - keys = vk.getKeys(node); - debug("Unknown node type \"%s\": Estimated visitor keys %j", node.type, keys); - } - - return keys; + let keys = visitorKeys[node.type]; + + if (!keys) { + keys = vk.getKeys(node); + debug( + 'Unknown node type "%s": Estimated visitor keys %j', + node.type, + keys, + ); + } + + return keys; } /** * The traverser class to traverse AST trees. */ class Traverser { - constructor() { - this._current = null; - this._parents = []; - this._skipped = false; - this._broken = false; - this._visitorKeys = null; - this._enter = null; - this._leave = null; - } - - /** - * Gives current node. - * @returns {ASTNode} The current node. - */ - current() { - return this._current; - } - - /** - * Gives a copy of the ancestor nodes. - * @returns {ASTNode[]} The ancestor nodes. - */ - parents() { - return this._parents.slice(0); - } - - /** - * Break the current traversal. - * @returns {void} - */ - break() { - this._broken = true; - } - - /** - * Skip child nodes for the current traversal. - * @returns {void} - */ - skip() { - this._skipped = true; - } - - /** - * Traverse the given AST tree. - * @param {ASTNode} node The root node to traverse. - * @param {Object} options The option object. - * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. - * @param {Function} [options.enter=noop] The callback function which is called on entering each node. - * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. - * @returns {void} - */ - traverse(node, options) { - this._current = null; - this._parents = []; - this._skipped = false; - this._broken = false; - this._visitorKeys = options.visitorKeys || vk.KEYS; - this._enter = options.enter || noop; - this._leave = options.leave || noop; - this._traverse(node, null); - } - - /** - * Traverse the given AST tree recursively. - * @param {ASTNode} node The current node. - * @param {ASTNode|null} parent The parent node. - * @returns {void} - * @private - */ - _traverse(node, parent) { - if (!isNode(node)) { - return; - } - - this._current = node; - this._skipped = false; - this._enter(node, parent); - - if (!this._skipped && !this._broken) { - const keys = getVisitorKeys(this._visitorKeys, node); - - if (keys.length >= 1) { - this._parents.push(node); - for (let i = 0; i < keys.length && !this._broken; ++i) { - const child = node[keys[i]]; - - if (Array.isArray(child)) { - for (let j = 0; j < child.length && !this._broken; ++j) { - this._traverse(child[j], node); - } - } else { - this._traverse(child, node); - } - } - this._parents.pop(); - } - } - - if (!this._broken) { - this._leave(node, parent); - } - - this._current = parent; - } - - /** - * Calculates the keys to use for traversal. - * @param {ASTNode} node The node to read keys from. - * @returns {string[]} An array of keys to visit on the node. - * @private - */ - static getKeys(node) { - return vk.getKeys(node); - } - - /** - * Traverse the given AST tree. - * @param {ASTNode} node The root node to traverse. - * @param {Object} options The option object. - * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. - * @param {Function} [options.enter=noop] The callback function which is called on entering each node. - * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. - * @returns {void} - */ - static traverse(node, options) { - new Traverser().traverse(node, options); - } - - /** - * The default visitor keys. - * @type {Object} - */ - static get DEFAULT_VISITOR_KEYS() { - return vk.KEYS; - } + constructor() { + this._current = null; + this._parents = []; + this._skipped = false; + this._broken = false; + this._visitorKeys = null; + this._enter = null; + this._leave = null; + } + + /** + * Gives current node. + * @returns {ASTNode} The current node. + */ + current() { + return this._current; + } + + /** + * Gives a copy of the ancestor nodes. + * @returns {ASTNode[]} The ancestor nodes. + */ + parents() { + return this._parents.slice(0); + } + + /** + * Break the current traversal. + * @returns {void} + */ + break() { + this._broken = true; + } + + /** + * Skip child nodes for the current traversal. + * @returns {void} + */ + skip() { + this._skipped = true; + } + + /** + * Traverse the given AST tree. + * @param {ASTNode} node The root node to traverse. + * @param {Object} options The option object. + * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. + * @param {Function} [options.enter=noop] The callback function which is called on entering each node. + * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. + * @returns {void} + */ + traverse(node, options) { + this._current = null; + this._parents = []; + this._skipped = false; + this._broken = false; + this._visitorKeys = options.visitorKeys || vk.KEYS; + this._enter = options.enter || noop; + this._leave = options.leave || noop; + this._traverse(node, null); + } + + /** + * Traverse the given AST tree recursively. + * @param {ASTNode} node The current node. + * @param {ASTNode|null} parent The parent node. + * @returns {void} + * @private + */ + _traverse(node, parent) { + if (!isNode(node)) { + return; + } + + this._current = node; + this._skipped = false; + this._enter(node, parent); + + if (!this._skipped && !this._broken) { + const keys = getVisitorKeys(this._visitorKeys, node); + + if (keys.length >= 1) { + this._parents.push(node); + for (let i = 0; i < keys.length && !this._broken; ++i) { + const child = node[keys[i]]; + + if (Array.isArray(child)) { + for ( + let j = 0; + j < child.length && !this._broken; + ++j + ) { + this._traverse(child[j], node); + } + } else { + this._traverse(child, node); + } + } + this._parents.pop(); + } + } + + if (!this._broken) { + this._leave(node, parent); + } + + this._current = parent; + } + + /** + * Calculates the keys to use for traversal. + * @param {ASTNode} node The node to read keys from. + * @returns {string[]} An array of keys to visit on the node. + * @private + */ + static getKeys(node) { + return vk.getKeys(node); + } + + /** + * Traverse the given AST tree. + * @param {ASTNode} node The root node to traverse. + * @param {Object} options The option object. + * @param {Object} [options.visitorKeys=DEFAULT_VISITOR_KEYS] The keys of each node types to traverse child nodes. Default is `./default-visitor-keys.json`. + * @param {Function} [options.enter=noop] The callback function which is called on entering each node. + * @param {Function} [options.leave=noop] The callback function which is called on leaving each node. + * @returns {void} + */ + static traverse(node, options) { + new Traverser().traverse(node, options); + } + + /** + * The default visitor keys. + * @type {Object} + */ + static get DEFAULT_VISITOR_KEYS() { + return vk.KEYS; + } } module.exports = Traverser; diff --git a/lib/types/index.d.ts b/lib/types/index.d.ts index 3d2a5eb16ae5..806abc4ac9ff 100644 --- a/lib/types/index.d.ts +++ b/lib/types/index.d.ts @@ -27,1388 +27,1819 @@ import * as ESTree from "estree"; import type { - RuleVisitor, - TextSourceCode, - Language, - SourceRange, - TraversalStep, - LanguageOptions as GenericLanguageOptions, - RuleDefinition, - RuleContext as CoreRuleContext, - RuleContextTypeOptions, - DeprecatedInfo + RuleVisitor, + TextSourceCode, + Language, + SourceRange, + TraversalStep, + LanguageOptions as GenericLanguageOptions, + RuleDefinition, + RuleContext as CoreRuleContext, + RuleContextTypeOptions, + DeprecatedInfo, } from "@eslint/core"; import { JSONSchema4 } from "json-schema"; import { LegacyESLint } from "./use-at-your-own-risk.js"; -/* - * Need to extend the `RuleContext` interface to include the - * deprecated methods that have not yet been removed. - * TODO: Remove in v10.0.0. - */ -declare module "@eslint/core" { - interface RuleContext { - - /** @deprecated Use `sourceCode.getAncestors()` instead */ - getAncestors(): ESTree.Node[]; - - /** @deprecated Use `sourceCode.getDeclaredVariables()` instead */ - getDeclaredVariables(node: ESTree.Node): Scope.Variable[]; - - /** @deprecated Use `sourceCode.getScope()` instead */ - getScope(): Scope.Scope; - - /** @deprecated Use `sourceCode.markVariableAsUsed()` instead */ - markVariableAsUsed(name: string): boolean; - } -} - export namespace AST { - type TokenType = - | "Boolean" - | "Null" - | "Identifier" - | "Keyword" - | "Punctuator" - | "JSXIdentifier" - | "JSXText" - | "Numeric" - | "String" - | "RegularExpression"; - - interface Token { - type: TokenType; - value: string; - range: Range; - loc: SourceLocation; - } - - interface SourceLocation { - start: ESTree.Position; - end: ESTree.Position; - } - - type Range = [number, number]; - - interface Program extends ESTree.Program { - comments: ESTree.Comment[]; - tokens: Token[]; - loc: SourceLocation; - range: Range; - } + type TokenType = + | "Boolean" + | "Null" + | "Identifier" + | "Keyword" + | "Punctuator" + | "JSXIdentifier" + | "JSXText" + | "Numeric" + | "String" + | "RegularExpression"; + + interface Token { + type: TokenType; + value: string; + range: Range; + loc: SourceLocation; + } + + interface SourceLocation { + start: ESTree.Position; + end: ESTree.Position; + } + + type Range = [number, number]; + + interface Program extends ESTree.Program { + comments: ESTree.Comment[]; + tokens: Token[]; + loc: SourceLocation; + range: Range; + } } export namespace Scope { - interface ScopeManager { - scopes: Scope[]; - globalScope: Scope | null; - - acquire(node: ESTree.Node, inner?: boolean): Scope | null; - - getDeclaredVariables(node: ESTree.Node): Variable[]; - } - - interface Scope { - type: - | "block" - | "catch" - | "class" - | "for" - | "function" - | "function-expression-name" - | "global" - | "module" - | "switch" - | "with" - | "TDZ"; - isStrict: boolean; - upper: Scope | null; - childScopes: Scope[]; - variableScope: Scope; - block: ESTree.Node; - variables: Variable[]; - set: Map; - references: Reference[]; - through: Reference[]; - functionExpressionScope: boolean; - } - - interface Variable { - name: string; - scope: Scope; - identifiers: ESTree.Identifier[]; - references: Reference[]; - defs: Definition[]; - } - - interface Reference { - identifier: ESTree.Identifier; - from: Scope; - resolved: Variable | null; - writeExpr: ESTree.Node | null; - init: boolean; - - isWrite(): boolean; - - isRead(): boolean; - - isWriteOnly(): boolean; - - isReadOnly(): boolean; - - isReadWrite(): boolean; - } - - type DefinitionType = - | { type: "CatchClause"; node: ESTree.CatchClause; parent: null } - | { type: "ClassName"; node: ESTree.ClassDeclaration | ESTree.ClassExpression; parent: null } - | { type: "FunctionName"; node: ESTree.FunctionDeclaration | ESTree.FunctionExpression; parent: null } - | { type: "ImplicitGlobalVariable"; node: ESTree.Program; parent: null } - | { - type: "ImportBinding"; - node: ESTree.ImportSpecifier | ESTree.ImportDefaultSpecifier | ESTree.ImportNamespaceSpecifier; - parent: ESTree.ImportDeclaration; - } - | { - type: "Parameter"; - node: ESTree.FunctionDeclaration | ESTree.FunctionExpression | ESTree.ArrowFunctionExpression; - parent: null; - } - | { type: "TDZ"; node: any; parent: null } - | { type: "Variable"; node: ESTree.VariableDeclarator; parent: ESTree.VariableDeclaration }; - - type Definition = DefinitionType & { name: ESTree.Identifier }; + interface ScopeManager { + scopes: Scope[]; + globalScope: Scope | null; + + acquire(node: ESTree.Node, inner?: boolean): Scope | null; + + getDeclaredVariables(node: ESTree.Node): Variable[]; + } + + interface Scope { + type: + | "block" + | "catch" + | "class" + | "for" + | "function" + | "function-expression-name" + | "global" + | "module" + | "switch" + | "with" + | "TDZ"; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: ESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + functionExpressionScope: boolean; + } + + interface Variable { + name: string; + scope: Scope; + identifiers: ESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + } + + interface Reference { + identifier: ESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: ESTree.Node | null; + init: boolean; + + isWrite(): boolean; + + isRead(): boolean; + + isWriteOnly(): boolean; + + isReadOnly(): boolean; + + isReadWrite(): boolean; + } + + type DefinitionType = + | { type: "CatchClause"; node: ESTree.CatchClause; parent: null } + | { + type: "ClassName"; + node: ESTree.ClassDeclaration | ESTree.ClassExpression; + parent: null; + } + | { + type: "FunctionName"; + node: ESTree.FunctionDeclaration | ESTree.FunctionExpression; + parent: null; + } + | { type: "ImplicitGlobalVariable"; node: ESTree.Program; parent: null } + | { + type: "ImportBinding"; + node: + | ESTree.ImportSpecifier + | ESTree.ImportDefaultSpecifier + | ESTree.ImportNamespaceSpecifier; + parent: ESTree.ImportDeclaration; + } + | { + type: "Parameter"; + node: + | ESTree.FunctionDeclaration + | ESTree.FunctionExpression + | ESTree.ArrowFunctionExpression; + parent: null; + } + | { type: "TDZ"; node: any; parent: null } + | { + type: "Variable"; + node: ESTree.VariableDeclarator; + parent: ESTree.VariableDeclaration; + }; + + type Definition = DefinitionType & { name: ESTree.Identifier }; } // #region SourceCode -export class SourceCode implements TextSourceCode<{ - LangOptions: Linter.LanguageOptions; - RootNode: AST.Program; - SyntaxElementWithLoc: AST.Token | ESTree.Node; - ConfigNode: ESTree.Comment; -}> { - text: string; - ast: AST.Program; - lines: string[]; - hasBOM: boolean; - parserServices: SourceCode.ParserServices; - scopeManager: Scope.ScopeManager; - visitorKeys: SourceCode.VisitorKeys; +export class SourceCode + implements + TextSourceCode<{ + LangOptions: Linter.LanguageOptions; + RootNode: AST.Program; + SyntaxElementWithLoc: AST.Token | ESTree.Node; + ConfigNode: ESTree.Comment; + }> +{ + text: string; + ast: AST.Program; + lines: string[]; + hasBOM: boolean; + parserServices: SourceCode.ParserServices; + scopeManager: Scope.ScopeManager; + visitorKeys: SourceCode.VisitorKeys; - constructor(text: string, ast: AST.Program); - constructor(config: SourceCode.Config); + constructor(text: string, ast: AST.Program); + constructor(config: SourceCode.Config); - static splitLines(text: string): string[]; + static splitLines(text: string): string[]; - getLoc(syntaxElement: AST.Token | ESTree.Node): ESTree.SourceLocation; - getRange(syntaxElement: AST.Token | ESTree.Node): SourceRange; + getLoc(syntaxElement: AST.Token | ESTree.Node): ESTree.SourceLocation; + getRange(syntaxElement: AST.Token | ESTree.Node): SourceRange; - getText(node?: ESTree.Node, beforeCount?: number, afterCount?: number): string; + getText( + node?: ESTree.Node, + beforeCount?: number, + afterCount?: number, + ): string; - getLines(): string[]; + getLines(): string[]; - getAllComments(): ESTree.Comment[]; + getAllComments(): ESTree.Comment[]; - getAncestors(node: ESTree.Node): ESTree.Node[]; + getAncestors(node: ESTree.Node): ESTree.Node[]; - getDeclaredVariables(node: ESTree.Node): Scope.Variable[]; + getDeclaredVariables(node: ESTree.Node): Scope.Variable[]; - getJSDocComment(node: ESTree.Node): ESTree.Comment | null; + getJSDocComment(node: ESTree.Node): ESTree.Comment | null; - getNodeByRangeIndex(index: number): ESTree.Node | null; + getNodeByRangeIndex(index: number): ESTree.Node | null; - isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean; + isSpaceBetweenTokens(first: AST.Token, second: AST.Token): boolean; - getLocFromIndex(index: number): ESTree.Position; + getLocFromIndex(index: number): ESTree.Position; - getIndexFromLoc(location: ESTree.Position): number; + getIndexFromLoc(location: ESTree.Position): number; - // Inherited methods from TokenStore - // --------------------------------- + // Inherited methods from TokenStore + // --------------------------------- - getTokenByRangeStart(offset: number, options?: { includeComments: false }): AST.Token | null; - getTokenByRangeStart(offset: number, options: { includeComments: boolean }): AST.Token | ESTree.Comment | null; + getTokenByRangeStart( + offset: number, + options?: { includeComments: false }, + ): AST.Token | null; + getTokenByRangeStart( + offset: number, + options: { includeComments: boolean }, + ): AST.Token | ESTree.Comment | null; - getFirstToken: SourceCode.UnaryNodeCursorWithSkipOptions; + getFirstToken: SourceCode.UnaryNodeCursorWithSkipOptions; - getFirstTokens: SourceCode.UnaryNodeCursorWithCountOptions; + getFirstTokens: SourceCode.UnaryNodeCursorWithCountOptions; - getLastToken: SourceCode.UnaryNodeCursorWithSkipOptions; + getLastToken: SourceCode.UnaryNodeCursorWithSkipOptions; - getLastTokens: SourceCode.UnaryNodeCursorWithCountOptions; + getLastTokens: SourceCode.UnaryNodeCursorWithCountOptions; - getTokenBefore: SourceCode.UnaryCursorWithSkipOptions; + getTokenBefore: SourceCode.UnaryCursorWithSkipOptions; - getTokensBefore: SourceCode.UnaryCursorWithCountOptions; + getTokensBefore: SourceCode.UnaryCursorWithCountOptions; - getTokenAfter: SourceCode.UnaryCursorWithSkipOptions; + getTokenAfter: SourceCode.UnaryCursorWithSkipOptions; - getTokensAfter: SourceCode.UnaryCursorWithCountOptions; + getTokensAfter: SourceCode.UnaryCursorWithCountOptions; - getFirstTokenBetween: SourceCode.BinaryCursorWithSkipOptions; + getFirstTokenBetween: SourceCode.BinaryCursorWithSkipOptions; - getFirstTokensBetween: SourceCode.BinaryCursorWithCountOptions; + getFirstTokensBetween: SourceCode.BinaryCursorWithCountOptions; - getLastTokenBetween: SourceCode.BinaryCursorWithSkipOptions; + getLastTokenBetween: SourceCode.BinaryCursorWithSkipOptions; - getLastTokensBetween: SourceCode.BinaryCursorWithCountOptions; + getLastTokensBetween: SourceCode.BinaryCursorWithCountOptions; - getTokensBetween: SourceCode.BinaryCursorWithCountOptions; + getTokensBetween: SourceCode.BinaryCursorWithCountOptions; - getTokens: - & ((node: ESTree.Node, beforeCount?: number, afterCount?: number) => AST.Token[]) - & SourceCode.UnaryNodeCursorWithCountOptions; + getTokens: (( + node: ESTree.Node, + beforeCount?: number, + afterCount?: number, + ) => AST.Token[]) & + SourceCode.UnaryNodeCursorWithCountOptions; - commentsExistBetween( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - ): boolean; + commentsExistBetween( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + ): boolean; - getCommentsBefore(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; + getCommentsBefore(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; - getCommentsAfter(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; + getCommentsAfter(nodeOrToken: ESTree.Node | AST.Token): ESTree.Comment[]; - getCommentsInside(node: ESTree.Node): ESTree.Comment[]; + getCommentsInside(node: ESTree.Node): ESTree.Comment[]; - getScope(node: ESTree.Node): Scope.Scope; + getScope(node: ESTree.Node): Scope.Scope; - isSpaceBetween( - first: ESTree.Node | AST.Token, - second: ESTree.Node | AST.Token, - ): boolean; + isSpaceBetween( + first: ESTree.Node | AST.Token, + second: ESTree.Node | AST.Token, + ): boolean; - markVariableAsUsed(name: string, refNode?: ESTree.Node): boolean; + markVariableAsUsed(name: string, refNode?: ESTree.Node): boolean; - traverse(): Iterable; + traverse(): Iterable; } export namespace SourceCode { - interface Config { - text: string; - ast: AST.Program; - parserServices?: ParserServices | undefined; - scopeManager?: Scope.ScopeManager | undefined; - visitorKeys?: VisitorKeys | undefined; - } - - type ParserServices = any; - - interface VisitorKeys { - [nodeType: string]: string[]; - } - - interface UnaryNodeCursorWithSkipOptions { - ( - node: ESTree.Node, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - skip?: number | undefined; - }, - ): T | null; - ( - node: ESTree.Node, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - skip?: number | undefined; - }, - ): T | null; - ( - node: ESTree.Node, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - skip?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token | null; - ( - node: ESTree.Node, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - skip?: number | undefined; - }, - ): AST.Token | ESTree.Comment | null; - } - - interface UnaryNodeCursorWithCountOptions { - ( - node: ESTree.Node, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - count?: number | undefined; - }, - ): T[]; - ( - node: ESTree.Node, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - count?: number | undefined; - }, - ): T[]; - ( - node: ESTree.Node, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - count?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token[]; - ( - node: ESTree.Node, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - count?: number | undefined; - }, - ): Array; - } - - interface UnaryCursorWithSkipOptions { - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - skip?: number | undefined; - }, - ): T | null; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - skip?: number | undefined; - }, - ): T | null; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - skip?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token | null; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - skip?: number | undefined; - }, - ): AST.Token | ESTree.Comment | null; - } - - interface UnaryCursorWithCountOptions { - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - count?: number | undefined; - }, - ): T[]; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - count?: number | undefined; - }, - ): T[]; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - count?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token[]; - ( - node: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - count?: number | undefined; - }, - ): Array; - } - - interface BinaryCursorWithSkipOptions { - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - skip?: number | undefined; - }, - ): T | null; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - skip?: number | undefined; - }, - ): T | null; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - skip?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token | null; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - skip?: number | undefined; - }, - ): AST.Token | ESTree.Comment | null; - } - - interface BinaryCursorWithCountOptions { - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: - | ((token: AST.Token) => token is T) - | { - filter: (token: AST.Token) => token is T; - includeComments?: false | undefined; - count?: number | undefined; - }, - ): T[]; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter: (tokenOrComment: AST.Token | ESTree.Comment) => tokenOrComment is T; - includeComments: boolean; - count?: number | undefined; - }, - ): T[]; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options?: - | { - filter?: ((token: AST.Token) => boolean) | undefined; - includeComments?: false | undefined; - count?: number | undefined; - } - | ((token: AST.Token) => boolean) - | number, - ): AST.Token[]; - ( - left: ESTree.Node | AST.Token | ESTree.Comment, - right: ESTree.Node | AST.Token | ESTree.Comment, - options: { - filter?: ((token: AST.Token | ESTree.Comment) => boolean) | undefined; - includeComments: boolean; - count?: number | undefined; - }, - ): Array; - } + interface Config { + text: string; + ast: AST.Program; + parserServices?: ParserServices | undefined; + scopeManager?: Scope.ScopeManager | undefined; + visitorKeys?: VisitorKeys | undefined; + } + + type ParserServices = any; + + interface VisitorKeys { + [nodeType: string]: string[]; + } + + interface UnaryNodeCursorWithSkipOptions { + ( + node: ESTree.Node, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + skip?: number | undefined; + }, + ): T | null; + ( + node: ESTree.Node, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + skip?: number | undefined; + }, + ): T | null; + ( + node: ESTree.Node, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + skip?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token | null; + ( + node: ESTree.Node, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + skip?: number | undefined; + }, + ): AST.Token | ESTree.Comment | null; + } + + interface UnaryNodeCursorWithCountOptions { + ( + node: ESTree.Node, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + count?: number | undefined; + }, + ): T[]; + ( + node: ESTree.Node, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + count?: number | undefined; + }, + ): T[]; + ( + node: ESTree.Node, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + count?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token[]; + ( + node: ESTree.Node, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + count?: number | undefined; + }, + ): Array; + } + + interface UnaryCursorWithSkipOptions { + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + skip?: number | undefined; + }, + ): T | null; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + skip?: number | undefined; + }, + ): T | null; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + skip?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token | null; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + skip?: number | undefined; + }, + ): AST.Token | ESTree.Comment | null; + } + + interface UnaryCursorWithCountOptions { + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + count?: number | undefined; + }, + ): T[]; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + count?: number | undefined; + }, + ): T[]; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + count?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token[]; + ( + node: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + count?: number | undefined; + }, + ): Array; + } + + interface BinaryCursorWithSkipOptions { + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + skip?: number | undefined; + }, + ): T | null; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + skip?: number | undefined; + }, + ): T | null; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + skip?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token | null; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + skip?: number | undefined; + }, + ): AST.Token | ESTree.Comment | null; + } + + interface BinaryCursorWithCountOptions { + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: + | ((token: AST.Token) => token is T) + | { + filter: (token: AST.Token) => token is T; + includeComments?: false | undefined; + count?: number | undefined; + }, + ): T[]; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter: ( + tokenOrComment: AST.Token | ESTree.Comment, + ) => tokenOrComment is T; + includeComments: boolean; + count?: number | undefined; + }, + ): T[]; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options?: + | { + filter?: ((token: AST.Token) => boolean) | undefined; + includeComments?: false | undefined; + count?: number | undefined; + } + | ((token: AST.Token) => boolean) + | number, + ): AST.Token[]; + ( + left: ESTree.Node | AST.Token | ESTree.Comment, + right: ESTree.Node | AST.Token | ESTree.Comment, + options: { + filter?: + | ((token: AST.Token | ESTree.Comment) => boolean) + | undefined; + includeComments: boolean; + count?: number | undefined; + }, + ): Array; + } } // #endregion export namespace Rule { - - type RuleModule = RuleDefinition<{ - LangOptions: Linter.LanguageOptions, - Code: SourceCode, - RuleOptions: any[], - Visitor: NodeListener, - Node: ESTree.Node, - MessageIds: string, - ExtRuleDocs: {} - }>; - - type NodeTypes = ESTree.Node["type"]; - interface NodeListener extends RuleVisitor { - ArrayExpression?: ((node: ESTree.ArrayExpression & NodeParentExtension) => void) | undefined; - "ArrayExpression:exit"?: ((node: ESTree.ArrayExpression & NodeParentExtension) => void) | undefined; - ArrayPattern?: ((node: ESTree.ArrayPattern & NodeParentExtension) => void) | undefined; - "ArrayPattern:exit"?: ((node: ESTree.ArrayPattern & NodeParentExtension) => void) | undefined; - ArrowFunctionExpression?: ((node: ESTree.ArrowFunctionExpression & NodeParentExtension) => void) | undefined; - "ArrowFunctionExpression:exit"?: ((node: ESTree.ArrowFunctionExpression & NodeParentExtension) => void) | undefined; - AssignmentExpression?: ((node: ESTree.AssignmentExpression & NodeParentExtension) => void) | undefined; - "AssignmentExpression:exit"?: ((node: ESTree.AssignmentExpression & NodeParentExtension) => void) | undefined; - AssignmentPattern?: ((node: ESTree.AssignmentPattern & NodeParentExtension) => void) | undefined; - "AssignmentPattern:exit"?: ((node: ESTree.AssignmentPattern & NodeParentExtension) => void) | undefined; - AwaitExpression?: ((node: ESTree.AwaitExpression & NodeParentExtension) => void) | undefined; - "AwaitExpression:exit"?: ((node: ESTree.AwaitExpression & NodeParentExtension) => void) | undefined; - BinaryExpression?: ((node: ESTree.BinaryExpression & NodeParentExtension) => void) | undefined; - "BinaryExpression:exit"?: ((node: ESTree.BinaryExpression & NodeParentExtension) => void) | undefined; - BlockStatement?: ((node: ESTree.BlockStatement & NodeParentExtension) => void) | undefined; - "BlockStatement:exit"?: ((node: ESTree.BlockStatement & NodeParentExtension) => void) | undefined; - BreakStatement?: ((node: ESTree.BreakStatement & NodeParentExtension) => void) | undefined; - "BreakStatement:exit"?: ((node: ESTree.BreakStatement & NodeParentExtension) => void) | undefined; - CallExpression?: ((node: ESTree.CallExpression & NodeParentExtension) => void) | undefined; - "CallExpression:exit"?: ((node: ESTree.CallExpression & NodeParentExtension) => void) | undefined; - CatchClause?: ((node: ESTree.CatchClause & NodeParentExtension) => void) | undefined; - "CatchClause:exit"?: ((node: ESTree.CatchClause & NodeParentExtension) => void) | undefined; - ChainExpression?: ((node: ESTree.ChainExpression & NodeParentExtension) => void) | undefined; - "ChainExpression:exit"?: ((node: ESTree.ChainExpression & NodeParentExtension) => void) | undefined; - ClassBody?: ((node: ESTree.ClassBody & NodeParentExtension) => void) | undefined; - "ClassBody:exit"?: ((node: ESTree.ClassBody & NodeParentExtension) => void) | undefined; - ClassDeclaration?: ((node: ESTree.ClassDeclaration & NodeParentExtension) => void) | undefined; - "ClassDeclaration:exit"?: ((node: ESTree.ClassDeclaration & NodeParentExtension) => void) | undefined; - ClassExpression?: ((node: ESTree.ClassExpression & NodeParentExtension) => void) | undefined; - "ClassExpression:exit"?: ((node: ESTree.ClassExpression & NodeParentExtension) => void) | undefined; - ConditionalExpression?: ((node: ESTree.ConditionalExpression & NodeParentExtension) => void) | undefined; - "ConditionalExpression:exit"?: ((node: ESTree.ConditionalExpression & NodeParentExtension) => void) | undefined; - ContinueStatement?: ((node: ESTree.ContinueStatement & NodeParentExtension) => void) | undefined; - "ContinueStatement:exit"?: ((node: ESTree.ContinueStatement & NodeParentExtension) => void) | undefined; - DebuggerStatement?: ((node: ESTree.DebuggerStatement & NodeParentExtension) => void) | undefined; - "DebuggerStatement:exit"?: ((node: ESTree.DebuggerStatement & NodeParentExtension) => void) | undefined; - DoWhileStatement?: ((node: ESTree.DoWhileStatement & NodeParentExtension) => void) | undefined; - "DoWhileStatement:exit"?: ((node: ESTree.DoWhileStatement & NodeParentExtension) => void) | undefined; - EmptyStatement?: ((node: ESTree.EmptyStatement & NodeParentExtension) => void) | undefined; - "EmptyStatement:exit"?: ((node: ESTree.EmptyStatement & NodeParentExtension) => void) | undefined; - ExportAllDeclaration?: ((node: ESTree.ExportAllDeclaration & NodeParentExtension) => void) | undefined; - "ExportAllDeclaration:exit"?: ((node: ESTree.ExportAllDeclaration & NodeParentExtension) => void) | undefined; - ExportDefaultDeclaration?: ((node: ESTree.ExportDefaultDeclaration & NodeParentExtension) => void) | undefined; - "ExportDefaultDeclaration:exit"?: ((node: ESTree.ExportDefaultDeclaration & NodeParentExtension) => void) | undefined; - ExportNamedDeclaration?: ((node: ESTree.ExportNamedDeclaration & NodeParentExtension) => void) | undefined; - "ExportNamedDeclaration:exit"?: ((node: ESTree.ExportNamedDeclaration & NodeParentExtension) => void) | undefined; - ExportSpecifier?: ((node: ESTree.ExportSpecifier & NodeParentExtension) => void) | undefined; - "ExportSpecifier:exit"?: ((node: ESTree.ExportSpecifier & NodeParentExtension) => void) | undefined; - ExpressionStatement?: ((node: ESTree.ExpressionStatement & NodeParentExtension) => void) | undefined; - "ExpressionStatement:exit"?: ((node: ESTree.ExpressionStatement & NodeParentExtension) => void) | undefined; - ForInStatement?: ((node: ESTree.ForInStatement & NodeParentExtension) => void) | undefined; - "ForInStatement:exit"?: ((node: ESTree.ForInStatement & NodeParentExtension) => void) | undefined; - ForOfStatement?: ((node: ESTree.ForOfStatement & NodeParentExtension) => void) | undefined; - "ForOfStatement:exit"?: ((node: ESTree.ForOfStatement & NodeParentExtension) => void) | undefined; - ForStatement?: ((node: ESTree.ForStatement & NodeParentExtension) => void) | undefined; - "ForStatement:exit"?: ((node: ESTree.ForStatement & NodeParentExtension) => void) | undefined; - FunctionDeclaration?: ((node: ESTree.FunctionDeclaration & NodeParentExtension) => void) | undefined; - "FunctionDeclaration:exit"?: ((node: ESTree.FunctionDeclaration & NodeParentExtension) => void) | undefined; - FunctionExpression?: ((node: ESTree.FunctionExpression & NodeParentExtension) => void) | undefined; - "FunctionExpression:exit"?: ((node: ESTree.FunctionExpression & NodeParentExtension) => void) | undefined; - Identifier?: ((node: ESTree.Identifier & NodeParentExtension) => void) | undefined; - "Identifier:exit"?: ((node: ESTree.Identifier & NodeParentExtension) => void) | undefined; - IfStatement?: ((node: ESTree.IfStatement & NodeParentExtension) => void) | undefined; - "IfStatement:exit"?: ((node: ESTree.IfStatement & NodeParentExtension) => void) | undefined; - ImportDeclaration?: ((node: ESTree.ImportDeclaration & NodeParentExtension) => void) | undefined; - "ImportDeclaration:exit"?: ((node: ESTree.ImportDeclaration & NodeParentExtension) => void) | undefined; - ImportDefaultSpecifier?: ((node: ESTree.ImportDefaultSpecifier & NodeParentExtension) => void) | undefined; - "ImportDefaultSpecifier:exit"?: ((node: ESTree.ImportDefaultSpecifier & NodeParentExtension) => void) | undefined; - ImportExpression?: ((node: ESTree.ImportExpression & NodeParentExtension) => void) | undefined; - "ImportExpression:exit"?: ((node: ESTree.ImportExpression & NodeParentExtension) => void) | undefined; - ImportNamespaceSpecifier?: ((node: ESTree.ImportNamespaceSpecifier & NodeParentExtension) => void) | undefined; - "ImportNamespaceSpecifier:exit"?: ((node: ESTree.ImportNamespaceSpecifier & NodeParentExtension) => void) | undefined; - ImportSpecifier?: ((node: ESTree.ImportSpecifier & NodeParentExtension) => void) | undefined; - "ImportSpecifier:exit"?: ((node: ESTree.ImportSpecifier & NodeParentExtension) => void) | undefined; - LabeledStatement?: ((node: ESTree.LabeledStatement & NodeParentExtension) => void) | undefined; - "LabeledStatement:exit"?: ((node: ESTree.LabeledStatement & NodeParentExtension) => void) | undefined; - Literal?: ((node: ESTree.Literal & NodeParentExtension) => void) | undefined; - "Literal:exit"?: ((node: ESTree.Literal & NodeParentExtension) => void) | undefined; - LogicalExpression?: ((node: ESTree.LogicalExpression & NodeParentExtension) => void) | undefined; - "LogicalExpression:exit"?: ((node: ESTree.LogicalExpression & NodeParentExtension) => void) | undefined; - MemberExpression?: ((node: ESTree.MemberExpression & NodeParentExtension) => void) | undefined; - "MemberExpression:exit"?: ((node: ESTree.MemberExpression & NodeParentExtension) => void) | undefined; - MetaProperty?: ((node: ESTree.MetaProperty & NodeParentExtension) => void) | undefined; - "MetaProperty:exit"?: ((node: ESTree.MetaProperty & NodeParentExtension) => void) | undefined; - MethodDefinition?: ((node: ESTree.MethodDefinition & NodeParentExtension) => void) | undefined; - "MethodDefinition:exit"?: ((node: ESTree.MethodDefinition & NodeParentExtension) => void) | undefined; - NewExpression?: ((node: ESTree.NewExpression & NodeParentExtension) => void) | undefined; - "NewExpression:exit"?: ((node: ESTree.NewExpression & NodeParentExtension) => void) | undefined; - ObjectExpression?: ((node: ESTree.ObjectExpression & NodeParentExtension) => void) | undefined; - "ObjectExpression:exit"?: ((node: ESTree.ObjectExpression & NodeParentExtension) => void) | undefined; - ObjectPattern?: ((node: ESTree.ObjectPattern & NodeParentExtension) => void) | undefined; - "ObjectPattern:exit"?: ((node: ESTree.ObjectPattern & NodeParentExtension) => void) | undefined; - PrivateIdentifier?: ((node: ESTree.PrivateIdentifier & NodeParentExtension) => void) | undefined; - "PrivateIdentifier:exit"?: ((node: ESTree.PrivateIdentifier & NodeParentExtension) => void) | undefined; - Program?: ((node: ESTree.Program) => void) | undefined; - "Program:exit"?: ((node: ESTree.Program) => void) | undefined; - Property?: ((node: ESTree.Property & NodeParentExtension) => void) | undefined; - "Property:exit"?: ((node: ESTree.Property & NodeParentExtension) => void) | undefined; - PropertyDefinition?: ((node: ESTree.PropertyDefinition & NodeParentExtension) => void) | undefined; - "PropertyDefinition:exit"?: ((node: ESTree.PropertyDefinition & NodeParentExtension) => void) | undefined; - RestElement?: ((node: ESTree.RestElement & NodeParentExtension) => void) | undefined; - "RestElement:exit"?: ((node: ESTree.RestElement & NodeParentExtension) => void) | undefined; - ReturnStatement?: ((node: ESTree.ReturnStatement & NodeParentExtension) => void) | undefined; - "ReturnStatement:exit"?: ((node: ESTree.ReturnStatement & NodeParentExtension) => void) | undefined; - SequenceExpression?: ((node: ESTree.SequenceExpression & NodeParentExtension) => void) | undefined; - "SequenceExpression:exit"?: ((node: ESTree.SequenceExpression & NodeParentExtension) => void) | undefined; - SpreadElement?: ((node: ESTree.SpreadElement & NodeParentExtension) => void) | undefined; - "SpreadElement:exit"?: ((node: ESTree.SpreadElement & NodeParentExtension) => void) | undefined; - StaticBlock?: ((node: ESTree.StaticBlock & NodeParentExtension) => void) | undefined; - "StaticBlock:exit"?: ((node: ESTree.StaticBlock & NodeParentExtension) => void) | undefined; - Super?: ((node: ESTree.Super & NodeParentExtension) => void) | undefined; - "Super:exit"?: ((node: ESTree.Super & NodeParentExtension) => void) | undefined; - SwitchCase?: ((node: ESTree.SwitchCase & NodeParentExtension) => void) | undefined; - "SwitchCase:exit"?: ((node: ESTree.SwitchCase & NodeParentExtension) => void) | undefined; - SwitchStatement?: ((node: ESTree.SwitchStatement & NodeParentExtension) => void) | undefined; - "SwitchStatement:exit"?: ((node: ESTree.SwitchStatement & NodeParentExtension) => void) | undefined; - TaggedTemplateExpression?: ((node: ESTree.TaggedTemplateExpression & NodeParentExtension) => void) | undefined; - "TaggedTemplateExpression:exit"?: ((node: ESTree.TaggedTemplateExpression & NodeParentExtension) => void) | undefined; - TemplateElement?: ((node: ESTree.TemplateElement & NodeParentExtension) => void) | undefined; - "TemplateElement:exit"?: ((node: ESTree.TemplateElement & NodeParentExtension) => void) | undefined; - TemplateLiteral?: ((node: ESTree.TemplateLiteral & NodeParentExtension) => void) | undefined; - "TemplateLiteral:exit"?: ((node: ESTree.TemplateLiteral & NodeParentExtension) => void) | undefined; - ThisExpression?: ((node: ESTree.ThisExpression & NodeParentExtension) => void) | undefined; - "ThisExpression:exit"?: ((node: ESTree.ThisExpression & NodeParentExtension) => void) | undefined; - ThrowStatement?: ((node: ESTree.ThrowStatement & NodeParentExtension) => void) | undefined; - "ThrowStatement:exit"?: ((node: ESTree.ThrowStatement & NodeParentExtension) => void) | undefined; - TryStatement?: ((node: ESTree.TryStatement & NodeParentExtension) => void) | undefined; - "TryStatement:exit"?: ((node: ESTree.TryStatement & NodeParentExtension) => void) | undefined; - UnaryExpression?: ((node: ESTree.UnaryExpression & NodeParentExtension) => void) | undefined; - "UnaryExpression:exit"?: ((node: ESTree.UnaryExpression & NodeParentExtension) => void) | undefined; - UpdateExpression?: ((node: ESTree.UpdateExpression & NodeParentExtension) => void) | undefined; - "UpdateExpression:exit"?: ((node: ESTree.UpdateExpression & NodeParentExtension) => void) | undefined; - VariableDeclaration?: ((node: ESTree.VariableDeclaration & NodeParentExtension) => void) | undefined; - "VariableDeclaration:exit"?: ((node: ESTree.VariableDeclaration & NodeParentExtension) => void) | undefined; - VariableDeclarator?: ((node: ESTree.VariableDeclarator & NodeParentExtension) => void) | undefined; - "VariableDeclarator:exit"?: ((node: ESTree.VariableDeclarator & NodeParentExtension) => void) | undefined; - WhileStatement?: ((node: ESTree.WhileStatement & NodeParentExtension) => void) | undefined; - "WhileStatement:exit"?: ((node: ESTree.WhileStatement & NodeParentExtension) => void) | undefined; - WithStatement?: ((node: ESTree.WithStatement & NodeParentExtension) => void) | undefined; - "WithStatement:exit"?: ((node: ESTree.WithStatement & NodeParentExtension) => void) | undefined; - YieldExpression?: ((node: ESTree.YieldExpression & NodeParentExtension) => void) | undefined; - "YieldExpression:exit"?: ((node: ESTree.YieldExpression & NodeParentExtension) => void) | undefined; - } - - interface NodeParentExtension { - parent: Node; - } - type Node = ESTree.Node & NodeParentExtension; - - interface RuleListener extends NodeListener { - onCodePathStart?(codePath: CodePath, node: Node): void; - - onCodePathEnd?(codePath: CodePath, node: Node): void; - - onCodePathSegmentStart?(segment: CodePathSegment, node: Node): void; - - onCodePathSegmentEnd?(segment: CodePathSegment, node: Node): void; - - onCodePathSegmentLoop?(fromSegment: CodePathSegment, toSegment: CodePathSegment, node: Node): void; - - [key: string]: - | ((codePath: CodePath, node: Node) => void) - | ((segment: CodePathSegment, node: Node) => void) - | ((fromSegment: CodePathSegment, toSegment: CodePathSegment, node: Node) => void) - | ((node: Node) => void) - | NodeListener[keyof NodeListener] - | undefined; - } - - type CodePathOrigin = "program" | "function" | "class-field-initializer" | "class-static-block"; - - interface CodePath { - id: string; - origin: CodePathOrigin; - initialSegment: CodePathSegment; - finalSegments: CodePathSegment[]; - returnedSegments: CodePathSegment[]; - thrownSegments: CodePathSegment[]; - upper: CodePath | null; - childCodePaths: CodePath[]; - } - - interface CodePathSegment { - id: string; - nextSegments: CodePathSegment[]; - prevSegments: CodePathSegment[]; - reachable: boolean; - } - - interface RuleMetaData { - /** Properties often used for documentation generation and tooling. */ - docs?: { - /** Provides a short description of the rule. Commonly used when generating lists of rules. */ - description?: string | undefined; - /** Historically used by some plugins that divide rules into categories in their documentation. */ - category?: string | undefined; - /** Historically used by some plugins to indicate a rule belongs in their `recommended` configuration. */ - recommended?: boolean | undefined; - /** 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. */ - url?: string | undefined; - } | undefined; - /** Violation and suggestion messages. */ - messages?: { [messageId: string]: string } | undefined; - /** - * Specifies if the `--fix` option on the command line automatically fixes problems reported by the rule. - * Mandatory for fixable rules. - */ - fixable?: "code" | "whitespace" | undefined; - /** - * Specifies the [options](https://eslint.org/docs/latest/extend/custom-rules#options-schemas) - * so ESLint can prevent invalid [rule configurations](https://eslint.org/docs/latest/use/configure/rules#configuring-rules). - * Mandatory for rules with options. - */ - schema?: JSONSchema4 | JSONSchema4[] | false | undefined; - - /** Any default options to be recursively merged on top of any user-provided options. */ - defaultOptions?: unknown[]; - - /** Indicates whether the rule has been deprecated or provides additional metadata about the deprecation. Omit if not deprecated. */ - deprecated?: boolean | DeprecatedInfo | undefined; - /** - * @deprecated Use deprecated.replacedBy instead. - * The name of the rule(s) this rule was replaced by, if it was deprecated. - */ - replacedBy?: readonly string[]; - - /** - * Indicates the type of rule: - * - `"problem"` means 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"` means 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"` means 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. - */ - type?: "problem" | "suggestion" | "layout" | undefined; - /** - * Specifies whether the rule can return suggestions (defaults to `false` if omitted). - * Mandatory for rules that provide suggestions. - */ - hasSuggestions?: boolean | undefined; - } - - interface RuleContext extends CoreRuleContext { - // report(descriptor: ReportDescriptor): void; - } - - type ReportFixer = (fixer: RuleFixer) => null | Fix | IterableIterator | Fix[]; - - interface ReportDescriptorOptionsBase { - data?: { [key: string]: string }; - - fix?: null | ReportFixer; - } - - interface SuggestionReportOptions { - data?: { [key: string]: string }; - - fix: ReportFixer; - } - - type SuggestionDescriptorMessage = { desc: string } | { messageId: string }; - type SuggestionReportDescriptor = SuggestionDescriptorMessage & SuggestionReportOptions; - - interface ReportDescriptorOptions extends ReportDescriptorOptionsBase { - suggest?: SuggestionReportDescriptor[] | null | undefined; - } - - type ReportDescriptor = ReportDescriptorMessage & ReportDescriptorLocation & ReportDescriptorOptions; - type ReportDescriptorMessage = { message: string } | { messageId: string }; - type ReportDescriptorLocation = - | { node: ESTree.Node } - | { loc: AST.SourceLocation | { line: number; column: number } }; - - interface RuleFixer { - insertTextAfter(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; - - insertTextAfterRange(range: AST.Range, text: string): Fix; - - insertTextBefore(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; - - insertTextBeforeRange(range: AST.Range, text: string): Fix; - - remove(nodeOrToken: ESTree.Node | AST.Token): Fix; - - removeRange(range: AST.Range): Fix; - - replaceText(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; - - replaceTextRange(range: AST.Range, text: string): Fix; - } - - interface Fix { - range: AST.Range; - text: string; - } + interface RuleModule + extends RuleDefinition<{ + LangOptions: Linter.LanguageOptions; + Code: SourceCode; + RuleOptions: any[]; + Visitor: NodeListener; + Node: ESTree.Node; + MessageIds: string; + ExtRuleDocs: {}; + }> { + create(context: RuleContext): NodeListener; + } + + type NodeTypes = ESTree.Node["type"]; + interface NodeListener extends RuleVisitor { + ArrayExpression?: + | ((node: ESTree.ArrayExpression & NodeParentExtension) => void) + | undefined; + "ArrayExpression:exit"?: + | ((node: ESTree.ArrayExpression & NodeParentExtension) => void) + | undefined; + ArrayPattern?: + | ((node: ESTree.ArrayPattern & NodeParentExtension) => void) + | undefined; + "ArrayPattern:exit"?: + | ((node: ESTree.ArrayPattern & NodeParentExtension) => void) + | undefined; + ArrowFunctionExpression?: + | (( + node: ESTree.ArrowFunctionExpression & NodeParentExtension, + ) => void) + | undefined; + "ArrowFunctionExpression:exit"?: + | (( + node: ESTree.ArrowFunctionExpression & NodeParentExtension, + ) => void) + | undefined; + AssignmentExpression?: + | (( + node: ESTree.AssignmentExpression & NodeParentExtension, + ) => void) + | undefined; + "AssignmentExpression:exit"?: + | (( + node: ESTree.AssignmentExpression & NodeParentExtension, + ) => void) + | undefined; + AssignmentPattern?: + | ((node: ESTree.AssignmentPattern & NodeParentExtension) => void) + | undefined; + "AssignmentPattern:exit"?: + | ((node: ESTree.AssignmentPattern & NodeParentExtension) => void) + | undefined; + AwaitExpression?: + | ((node: ESTree.AwaitExpression & NodeParentExtension) => void) + | undefined; + "AwaitExpression:exit"?: + | ((node: ESTree.AwaitExpression & NodeParentExtension) => void) + | undefined; + BinaryExpression?: + | ((node: ESTree.BinaryExpression & NodeParentExtension) => void) + | undefined; + "BinaryExpression:exit"?: + | ((node: ESTree.BinaryExpression & NodeParentExtension) => void) + | undefined; + BlockStatement?: + | ((node: ESTree.BlockStatement & NodeParentExtension) => void) + | undefined; + "BlockStatement:exit"?: + | ((node: ESTree.BlockStatement & NodeParentExtension) => void) + | undefined; + BreakStatement?: + | ((node: ESTree.BreakStatement & NodeParentExtension) => void) + | undefined; + "BreakStatement:exit"?: + | ((node: ESTree.BreakStatement & NodeParentExtension) => void) + | undefined; + CallExpression?: + | ((node: ESTree.CallExpression & NodeParentExtension) => void) + | undefined; + "CallExpression:exit"?: + | ((node: ESTree.CallExpression & NodeParentExtension) => void) + | undefined; + CatchClause?: + | ((node: ESTree.CatchClause & NodeParentExtension) => void) + | undefined; + "CatchClause:exit"?: + | ((node: ESTree.CatchClause & NodeParentExtension) => void) + | undefined; + ChainExpression?: + | ((node: ESTree.ChainExpression & NodeParentExtension) => void) + | undefined; + "ChainExpression:exit"?: + | ((node: ESTree.ChainExpression & NodeParentExtension) => void) + | undefined; + ClassBody?: + | ((node: ESTree.ClassBody & NodeParentExtension) => void) + | undefined; + "ClassBody:exit"?: + | ((node: ESTree.ClassBody & NodeParentExtension) => void) + | undefined; + ClassDeclaration?: + | ((node: ESTree.ClassDeclaration & NodeParentExtension) => void) + | undefined; + "ClassDeclaration:exit"?: + | ((node: ESTree.ClassDeclaration & NodeParentExtension) => void) + | undefined; + ClassExpression?: + | ((node: ESTree.ClassExpression & NodeParentExtension) => void) + | undefined; + "ClassExpression:exit"?: + | ((node: ESTree.ClassExpression & NodeParentExtension) => void) + | undefined; + ConditionalExpression?: + | (( + node: ESTree.ConditionalExpression & NodeParentExtension, + ) => void) + | undefined; + "ConditionalExpression:exit"?: + | (( + node: ESTree.ConditionalExpression & NodeParentExtension, + ) => void) + | undefined; + ContinueStatement?: + | ((node: ESTree.ContinueStatement & NodeParentExtension) => void) + | undefined; + "ContinueStatement:exit"?: + | ((node: ESTree.ContinueStatement & NodeParentExtension) => void) + | undefined; + DebuggerStatement?: + | ((node: ESTree.DebuggerStatement & NodeParentExtension) => void) + | undefined; + "DebuggerStatement:exit"?: + | ((node: ESTree.DebuggerStatement & NodeParentExtension) => void) + | undefined; + DoWhileStatement?: + | ((node: ESTree.DoWhileStatement & NodeParentExtension) => void) + | undefined; + "DoWhileStatement:exit"?: + | ((node: ESTree.DoWhileStatement & NodeParentExtension) => void) + | undefined; + EmptyStatement?: + | ((node: ESTree.EmptyStatement & NodeParentExtension) => void) + | undefined; + "EmptyStatement:exit"?: + | ((node: ESTree.EmptyStatement & NodeParentExtension) => void) + | undefined; + ExportAllDeclaration?: + | (( + node: ESTree.ExportAllDeclaration & NodeParentExtension, + ) => void) + | undefined; + "ExportAllDeclaration:exit"?: + | (( + node: ESTree.ExportAllDeclaration & NodeParentExtension, + ) => void) + | undefined; + ExportDefaultDeclaration?: + | (( + node: ESTree.ExportDefaultDeclaration & NodeParentExtension, + ) => void) + | undefined; + "ExportDefaultDeclaration:exit"?: + | (( + node: ESTree.ExportDefaultDeclaration & NodeParentExtension, + ) => void) + | undefined; + ExportNamedDeclaration?: + | (( + node: ESTree.ExportNamedDeclaration & NodeParentExtension, + ) => void) + | undefined; + "ExportNamedDeclaration:exit"?: + | (( + node: ESTree.ExportNamedDeclaration & NodeParentExtension, + ) => void) + | undefined; + ExportSpecifier?: + | ((node: ESTree.ExportSpecifier & NodeParentExtension) => void) + | undefined; + "ExportSpecifier:exit"?: + | ((node: ESTree.ExportSpecifier & NodeParentExtension) => void) + | undefined; + ExpressionStatement?: + | ((node: ESTree.ExpressionStatement & NodeParentExtension) => void) + | undefined; + "ExpressionStatement:exit"?: + | ((node: ESTree.ExpressionStatement & NodeParentExtension) => void) + | undefined; + ForInStatement?: + | ((node: ESTree.ForInStatement & NodeParentExtension) => void) + | undefined; + "ForInStatement:exit"?: + | ((node: ESTree.ForInStatement & NodeParentExtension) => void) + | undefined; + ForOfStatement?: + | ((node: ESTree.ForOfStatement & NodeParentExtension) => void) + | undefined; + "ForOfStatement:exit"?: + | ((node: ESTree.ForOfStatement & NodeParentExtension) => void) + | undefined; + ForStatement?: + | ((node: ESTree.ForStatement & NodeParentExtension) => void) + | undefined; + "ForStatement:exit"?: + | ((node: ESTree.ForStatement & NodeParentExtension) => void) + | undefined; + FunctionDeclaration?: + | ((node: ESTree.FunctionDeclaration & NodeParentExtension) => void) + | undefined; + "FunctionDeclaration:exit"?: + | ((node: ESTree.FunctionDeclaration & NodeParentExtension) => void) + | undefined; + FunctionExpression?: + | ((node: ESTree.FunctionExpression & NodeParentExtension) => void) + | undefined; + "FunctionExpression:exit"?: + | ((node: ESTree.FunctionExpression & NodeParentExtension) => void) + | undefined; + Identifier?: + | ((node: ESTree.Identifier & NodeParentExtension) => void) + | undefined; + "Identifier:exit"?: + | ((node: ESTree.Identifier & NodeParentExtension) => void) + | undefined; + IfStatement?: + | ((node: ESTree.IfStatement & NodeParentExtension) => void) + | undefined; + "IfStatement:exit"?: + | ((node: ESTree.IfStatement & NodeParentExtension) => void) + | undefined; + ImportDeclaration?: + | ((node: ESTree.ImportDeclaration & NodeParentExtension) => void) + | undefined; + "ImportDeclaration:exit"?: + | ((node: ESTree.ImportDeclaration & NodeParentExtension) => void) + | undefined; + ImportDefaultSpecifier?: + | (( + node: ESTree.ImportDefaultSpecifier & NodeParentExtension, + ) => void) + | undefined; + "ImportDefaultSpecifier:exit"?: + | (( + node: ESTree.ImportDefaultSpecifier & NodeParentExtension, + ) => void) + | undefined; + ImportExpression?: + | ((node: ESTree.ImportExpression & NodeParentExtension) => void) + | undefined; + "ImportExpression:exit"?: + | ((node: ESTree.ImportExpression & NodeParentExtension) => void) + | undefined; + ImportNamespaceSpecifier?: + | (( + node: ESTree.ImportNamespaceSpecifier & NodeParentExtension, + ) => void) + | undefined; + "ImportNamespaceSpecifier:exit"?: + | (( + node: ESTree.ImportNamespaceSpecifier & NodeParentExtension, + ) => void) + | undefined; + ImportSpecifier?: + | ((node: ESTree.ImportSpecifier & NodeParentExtension) => void) + | undefined; + "ImportSpecifier:exit"?: + | ((node: ESTree.ImportSpecifier & NodeParentExtension) => void) + | undefined; + LabeledStatement?: + | ((node: ESTree.LabeledStatement & NodeParentExtension) => void) + | undefined; + "LabeledStatement:exit"?: + | ((node: ESTree.LabeledStatement & NodeParentExtension) => void) + | undefined; + Literal?: + | ((node: ESTree.Literal & NodeParentExtension) => void) + | undefined; + "Literal:exit"?: + | ((node: ESTree.Literal & NodeParentExtension) => void) + | undefined; + LogicalExpression?: + | ((node: ESTree.LogicalExpression & NodeParentExtension) => void) + | undefined; + "LogicalExpression:exit"?: + | ((node: ESTree.LogicalExpression & NodeParentExtension) => void) + | undefined; + MemberExpression?: + | ((node: ESTree.MemberExpression & NodeParentExtension) => void) + | undefined; + "MemberExpression:exit"?: + | ((node: ESTree.MemberExpression & NodeParentExtension) => void) + | undefined; + MetaProperty?: + | ((node: ESTree.MetaProperty & NodeParentExtension) => void) + | undefined; + "MetaProperty:exit"?: + | ((node: ESTree.MetaProperty & NodeParentExtension) => void) + | undefined; + MethodDefinition?: + | ((node: ESTree.MethodDefinition & NodeParentExtension) => void) + | undefined; + "MethodDefinition:exit"?: + | ((node: ESTree.MethodDefinition & NodeParentExtension) => void) + | undefined; + NewExpression?: + | ((node: ESTree.NewExpression & NodeParentExtension) => void) + | undefined; + "NewExpression:exit"?: + | ((node: ESTree.NewExpression & NodeParentExtension) => void) + | undefined; + ObjectExpression?: + | ((node: ESTree.ObjectExpression & NodeParentExtension) => void) + | undefined; + "ObjectExpression:exit"?: + | ((node: ESTree.ObjectExpression & NodeParentExtension) => void) + | undefined; + ObjectPattern?: + | ((node: ESTree.ObjectPattern & NodeParentExtension) => void) + | undefined; + "ObjectPattern:exit"?: + | ((node: ESTree.ObjectPattern & NodeParentExtension) => void) + | undefined; + PrivateIdentifier?: + | ((node: ESTree.PrivateIdentifier & NodeParentExtension) => void) + | undefined; + "PrivateIdentifier:exit"?: + | ((node: ESTree.PrivateIdentifier & NodeParentExtension) => void) + | undefined; + Program?: ((node: ESTree.Program) => void) | undefined; + "Program:exit"?: ((node: ESTree.Program) => void) | undefined; + Property?: + | ((node: ESTree.Property & NodeParentExtension) => void) + | undefined; + "Property:exit"?: + | ((node: ESTree.Property & NodeParentExtension) => void) + | undefined; + PropertyDefinition?: + | ((node: ESTree.PropertyDefinition & NodeParentExtension) => void) + | undefined; + "PropertyDefinition:exit"?: + | ((node: ESTree.PropertyDefinition & NodeParentExtension) => void) + | undefined; + RestElement?: + | ((node: ESTree.RestElement & NodeParentExtension) => void) + | undefined; + "RestElement:exit"?: + | ((node: ESTree.RestElement & NodeParentExtension) => void) + | undefined; + ReturnStatement?: + | ((node: ESTree.ReturnStatement & NodeParentExtension) => void) + | undefined; + "ReturnStatement:exit"?: + | ((node: ESTree.ReturnStatement & NodeParentExtension) => void) + | undefined; + SequenceExpression?: + | ((node: ESTree.SequenceExpression & NodeParentExtension) => void) + | undefined; + "SequenceExpression:exit"?: + | ((node: ESTree.SequenceExpression & NodeParentExtension) => void) + | undefined; + SpreadElement?: + | ((node: ESTree.SpreadElement & NodeParentExtension) => void) + | undefined; + "SpreadElement:exit"?: + | ((node: ESTree.SpreadElement & NodeParentExtension) => void) + | undefined; + StaticBlock?: + | ((node: ESTree.StaticBlock & NodeParentExtension) => void) + | undefined; + "StaticBlock:exit"?: + | ((node: ESTree.StaticBlock & NodeParentExtension) => void) + | undefined; + Super?: + | ((node: ESTree.Super & NodeParentExtension) => void) + | undefined; + "Super:exit"?: + | ((node: ESTree.Super & NodeParentExtension) => void) + | undefined; + SwitchCase?: + | ((node: ESTree.SwitchCase & NodeParentExtension) => void) + | undefined; + "SwitchCase:exit"?: + | ((node: ESTree.SwitchCase & NodeParentExtension) => void) + | undefined; + SwitchStatement?: + | ((node: ESTree.SwitchStatement & NodeParentExtension) => void) + | undefined; + "SwitchStatement:exit"?: + | ((node: ESTree.SwitchStatement & NodeParentExtension) => void) + | undefined; + TaggedTemplateExpression?: + | (( + node: ESTree.TaggedTemplateExpression & NodeParentExtension, + ) => void) + | undefined; + "TaggedTemplateExpression:exit"?: + | (( + node: ESTree.TaggedTemplateExpression & NodeParentExtension, + ) => void) + | undefined; + TemplateElement?: + | ((node: ESTree.TemplateElement & NodeParentExtension) => void) + | undefined; + "TemplateElement:exit"?: + | ((node: ESTree.TemplateElement & NodeParentExtension) => void) + | undefined; + TemplateLiteral?: + | ((node: ESTree.TemplateLiteral & NodeParentExtension) => void) + | undefined; + "TemplateLiteral:exit"?: + | ((node: ESTree.TemplateLiteral & NodeParentExtension) => void) + | undefined; + ThisExpression?: + | ((node: ESTree.ThisExpression & NodeParentExtension) => void) + | undefined; + "ThisExpression:exit"?: + | ((node: ESTree.ThisExpression & NodeParentExtension) => void) + | undefined; + ThrowStatement?: + | ((node: ESTree.ThrowStatement & NodeParentExtension) => void) + | undefined; + "ThrowStatement:exit"?: + | ((node: ESTree.ThrowStatement & NodeParentExtension) => void) + | undefined; + TryStatement?: + | ((node: ESTree.TryStatement & NodeParentExtension) => void) + | undefined; + "TryStatement:exit"?: + | ((node: ESTree.TryStatement & NodeParentExtension) => void) + | undefined; + UnaryExpression?: + | ((node: ESTree.UnaryExpression & NodeParentExtension) => void) + | undefined; + "UnaryExpression:exit"?: + | ((node: ESTree.UnaryExpression & NodeParentExtension) => void) + | undefined; + UpdateExpression?: + | ((node: ESTree.UpdateExpression & NodeParentExtension) => void) + | undefined; + "UpdateExpression:exit"?: + | ((node: ESTree.UpdateExpression & NodeParentExtension) => void) + | undefined; + VariableDeclaration?: + | ((node: ESTree.VariableDeclaration & NodeParentExtension) => void) + | undefined; + "VariableDeclaration:exit"?: + | ((node: ESTree.VariableDeclaration & NodeParentExtension) => void) + | undefined; + VariableDeclarator?: + | ((node: ESTree.VariableDeclarator & NodeParentExtension) => void) + | undefined; + "VariableDeclarator:exit"?: + | ((node: ESTree.VariableDeclarator & NodeParentExtension) => void) + | undefined; + WhileStatement?: + | ((node: ESTree.WhileStatement & NodeParentExtension) => void) + | undefined; + "WhileStatement:exit"?: + | ((node: ESTree.WhileStatement & NodeParentExtension) => void) + | undefined; + WithStatement?: + | ((node: ESTree.WithStatement & NodeParentExtension) => void) + | undefined; + "WithStatement:exit"?: + | ((node: ESTree.WithStatement & NodeParentExtension) => void) + | undefined; + YieldExpression?: + | ((node: ESTree.YieldExpression & NodeParentExtension) => void) + | undefined; + "YieldExpression:exit"?: + | ((node: ESTree.YieldExpression & NodeParentExtension) => void) + | undefined; + } + + interface NodeParentExtension { + parent: Node; + } + type Node = ESTree.Node & NodeParentExtension; + + interface RuleListener extends NodeListener { + onCodePathStart?(codePath: CodePath, node: Node): void; + + onCodePathEnd?(codePath: CodePath, node: Node): void; + + onCodePathSegmentStart?(segment: CodePathSegment, node: Node): void; + + onCodePathSegmentEnd?(segment: CodePathSegment, node: Node): void; + + onCodePathSegmentLoop?( + fromSegment: CodePathSegment, + toSegment: CodePathSegment, + node: Node, + ): void; + + [key: string]: + | ((codePath: CodePath, node: Node) => void) + | ((segment: CodePathSegment, node: Node) => void) + | (( + fromSegment: CodePathSegment, + toSegment: CodePathSegment, + node: Node, + ) => void) + | ((node: Node) => void) + | NodeListener[keyof NodeListener] + | undefined; + } + + type CodePathOrigin = + | "program" + | "function" + | "class-field-initializer" + | "class-static-block"; + + interface CodePath { + id: string; + origin: CodePathOrigin; + initialSegment: CodePathSegment; + finalSegments: CodePathSegment[]; + returnedSegments: CodePathSegment[]; + thrownSegments: CodePathSegment[]; + upper: CodePath | null; + childCodePaths: CodePath[]; + } + + interface CodePathSegment { + id: string; + nextSegments: CodePathSegment[]; + prevSegments: CodePathSegment[]; + reachable: boolean; + } + + interface RuleMetaData { + /** Properties often used for documentation generation and tooling. */ + docs?: + | { + /** Provides a short description of the rule. Commonly used when generating lists of rules. */ + description?: string | undefined; + /** Historically used by some plugins that divide rules into categories in their documentation. */ + category?: string | undefined; + /** Historically used by some plugins to indicate a rule belongs in their `recommended` configuration. */ + recommended?: boolean | undefined; + /** 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. */ + url?: string | undefined; + } + | undefined; + /** Violation and suggestion messages. */ + messages?: { [messageId: string]: string } | undefined; + /** + * Specifies if the `--fix` option on the command line automatically fixes problems reported by the rule. + * Mandatory for fixable rules. + */ + fixable?: "code" | "whitespace" | undefined; + /** + * Specifies the [options](https://eslint.org/docs/latest/extend/custom-rules#options-schemas) + * so ESLint can prevent invalid [rule configurations](https://eslint.org/docs/latest/use/configure/rules#configuring-rules). + * Mandatory for rules with options. + */ + schema?: JSONSchema4 | JSONSchema4[] | false | undefined; + + /** Any default options to be recursively merged on top of any user-provided options. */ + defaultOptions?: unknown[]; + + /** Indicates whether the rule has been deprecated or provides additional metadata about the deprecation. Omit if not deprecated. */ + deprecated?: boolean | DeprecatedInfo | undefined; + /** + * @deprecated Use deprecated.replacedBy instead. + * The name of the rule(s) this rule was replaced by, if it was deprecated. + */ + replacedBy?: readonly string[]; + + /** + * Indicates the type of rule: + * - `"problem"` means 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"` means 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"` means 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. + */ + type?: "problem" | "suggestion" | "layout" | undefined; + /** + * Specifies whether the rule can return suggestions (defaults to `false` if omitted). + * Mandatory for rules that provide suggestions. + */ + hasSuggestions?: boolean | undefined; + } + + interface RuleContext + extends CoreRuleContext< + RuleContextTypeOptions & { + LangOptions: Linter.LanguageOptions; + Code: SourceCode; + Node: ESTree.Node; + } + > { + /* + * Need to extend the `RuleContext` interface to include the + * deprecated methods that have not yet been removed. + * TODO: Remove in v10.0.0. + */ + + /** @deprecated Use `sourceCode.getAncestors()` instead */ + getAncestors(): ESTree.Node[]; + + /** @deprecated Use `sourceCode.getDeclaredVariables()` instead */ + getDeclaredVariables(node: ESTree.Node): Scope.Variable[]; + + /** @deprecated Use `sourceCode.getScope()` instead */ + getScope(): Scope.Scope; + + /** @deprecated Use `sourceCode.markVariableAsUsed()` instead */ + markVariableAsUsed(name: string): boolean; + } + + type ReportFixer = ( + fixer: RuleFixer, + ) => null | Fix | IterableIterator | Fix[]; + + interface ReportDescriptorOptionsBase { + data?: { [key: string]: string }; + + fix?: null | ReportFixer; + } + + interface SuggestionReportOptions { + data?: { [key: string]: string }; + + fix: ReportFixer; + } + + type SuggestionDescriptorMessage = { desc: string } | { messageId: string }; + type SuggestionReportDescriptor = SuggestionDescriptorMessage & + SuggestionReportOptions; + + interface ReportDescriptorOptions extends ReportDescriptorOptionsBase { + suggest?: SuggestionReportDescriptor[] | null | undefined; + } + + type ReportDescriptor = ReportDescriptorMessage & + ReportDescriptorLocation & + ReportDescriptorOptions; + type ReportDescriptorMessage = { message: string } | { messageId: string }; + type ReportDescriptorLocation = + | { node: ESTree.Node } + | { loc: AST.SourceLocation | { line: number; column: number } }; + + interface RuleFixer { + insertTextAfter( + nodeOrToken: ESTree.Node | AST.Token, + text: string, + ): Fix; + + insertTextAfterRange(range: AST.Range, text: string): Fix; + + insertTextBefore( + nodeOrToken: ESTree.Node | AST.Token, + text: string, + ): Fix; + + insertTextBeforeRange(range: AST.Range, text: string): Fix; + + remove(nodeOrToken: ESTree.Node | AST.Token): Fix; + + removeRange(range: AST.Range): Fix; + + replaceText(nodeOrToken: ESTree.Node | AST.Token, text: string): Fix; + + replaceTextRange(range: AST.Range, text: string): Fix; + } + + interface Fix { + range: AST.Range; + text: string; + } } // #region Linter export class Linter { - static readonly version: string; + static readonly version: string; - version: string; + version: string; - constructor(options?: { cwd?: string | undefined; configType?: "flat" | "eslintrc" }); + constructor(options?: { + cwd?: string | undefined; + configType?: "flat" | "eslintrc"; + }); - verify( - code: SourceCode | string, - config: Linter.LegacyConfig | Linter.Config | Linter.Config[], - filename?: string, - ): Linter.LintMessage[]; - verify( - code: SourceCode | string, - config: Linter.LegacyConfig | Linter.Config | Linter.Config[], - options: Linter.LintOptions, - ): Linter.LintMessage[]; + verify( + code: SourceCode | string, + config: Linter.LegacyConfig | Linter.Config | Linter.Config[], + filename?: string, + ): Linter.LintMessage[]; + verify( + code: SourceCode | string, + config: Linter.LegacyConfig | Linter.Config | Linter.Config[], + options: Linter.LintOptions, + ): Linter.LintMessage[]; - verifyAndFix( - code: string, - config: Linter.LegacyConfig | Linter.Config | Linter.Config[], - filename?: string, - ): Linter.FixReport; - verifyAndFix( - code: string, - config: Linter.LegacyConfig | Linter.Config | Linter.Config[], - options: Linter.FixOptions, - ): Linter.FixReport; + verifyAndFix( + code: string, + config: Linter.LegacyConfig | Linter.Config | Linter.Config[], + filename?: string, + ): Linter.FixReport; + verifyAndFix( + code: string, + config: Linter.LegacyConfig | Linter.Config | Linter.Config[], + options: Linter.FixOptions, + ): Linter.FixReport; - getSourceCode(): SourceCode; + getSourceCode(): SourceCode; - defineRule(name: string, rule: Rule.RuleModule): void; + defineRule(name: string, rule: Rule.RuleModule): void; - defineRules(rules: { [name: string]: Rule.RuleModule }): void; + defineRules(rules: { [name: string]: Rule.RuleModule }): void; - getRules(): Map; + getRules(): Map; - defineParser(name: string, parser: Linter.Parser): void; + defineParser(name: string, parser: Linter.Parser): void; - getTimes(): Linter.Stats["times"]; + getTimes(): Linter.Stats["times"]; - getFixPassCount(): Linter.Stats["fixPasses"]; + getFixPassCount(): Linter.Stats["fixPasses"]; } export namespace Linter { - /** - * The numeric severity level for a rule. - * - * - `0` means off. - * - `1` means warn. - * - `2` means error. - * - * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) - */ - type Severity = 0 | 1 | 2; - - /** - * The human readable severity level for a rule. - * - * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) - */ - type StringSeverity = "off" | "warn" | "error"; - - /** - * The numeric or human readable severity level for a rule. - * - * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) - */ - type RuleSeverity = Severity | StringSeverity; - - /** - * An array containing the rule severity level, followed by the rule options. - * - * @see [Rules](https://eslint.org/docs/latest/use/configure/rules) - */ - type RuleSeverityAndOptions = [RuleSeverity, ...Partial]; - - /** - * The severity level for the rule or an array containing the rule severity level, followed by the rule options. - * - * @see [Rules](https://eslint.org/docs/latest/use/configure/rules) - */ - type RuleEntry = RuleSeverity | RuleSeverityAndOptions; - - /** - * The rules config object is a key/value map of rule names and their severity and options. - */ - interface RulesRecord { - [rule: string]: RuleEntry; - } - - /** - * A configuration object that may have a `rules` block. - */ - interface HasRules { - rules?: Partial | undefined; - } - - /** - * The ECMAScript version of the code being linted. - */ - type EcmaVersion = - | 3 - | 5 - | 6 - | 7 - | 8 - | 9 - | 10 - | 11 - | 12 - | 13 - | 14 - | 15 - | 16 - | 2015 - | 2016 - | 2017 - | 2018 - | 2019 - | 2020 - | 2021 - | 2022 - | 2023 - | 2024 - | 2025 - | "latest"; - - /** - * The type of JavaScript source code. - */ - type SourceType = "script" | "module" | "commonjs"; - - /** - * ESLint legacy configuration. - * - * @see [ESLint Legacy Configuration](https://eslint.org/docs/latest/use/configure/) - */ - interface BaseConfig - extends HasRules { - $schema?: string | undefined; - - /** - * An environment provides predefined global variables. - * - * @see [Environments](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-environments) - */ - env?: { [name: string]: boolean } | undefined; - - /** - * Extending configuration files. - * - * @see [Extends](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#extending-configuration-files) - */ - extends?: string | string[] | undefined; - - /** - * Specifying globals. - * - * @see [Globals](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-globals) - */ - globals?: Linter.Globals | undefined; - - /** - * Disable processing of inline comments. - * - * @see [Disabling Inline Comments](https://eslint.org/docs/latest/use/configure/rules-deprecated#disabling-inline-comments) - */ - noInlineConfig?: boolean | undefined; - - /** - * Overrides can be used to use a differing configuration for matching sub-directories and files. - * - * @see [How do overrides work](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#how-do-overrides-work) - */ - overrides?: Array> | undefined; - - /** - * Parser. - * - * @see [Working with Custom Parsers](https://eslint.org/docs/latest/extend/custom-parsers) - * @see [Specifying Parser](https://eslint.org/docs/latest/use/configure/parser-deprecated) - */ - parser?: string | undefined; - - /** - * Parser options. - * - * @see [Working with Custom Parsers](https://eslint.org/docs/latest/extend/custom-parsers) - * @see [Specifying Parser Options](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options) - */ - parserOptions?: ParserOptions | undefined; - - /** - * Which third-party plugins define additional rules, environments, configs, etc. for ESLint to use. - * - * @see [Configuring Plugins](https://eslint.org/docs/latest/use/configure/plugins-deprecated#configure-plugins) - */ - plugins?: string[] | undefined; - - /** - * Specifying processor. - * - * @see [processor](https://eslint.org/docs/latest/use/configure/plugins-deprecated#specify-a-processor) - */ - processor?: string | undefined; - - /** - * Report unused eslint-disable comments as warning. - * - * @see [Report unused eslint-disable comments](https://eslint.org/docs/latest/use/configure/rules-deprecated#report-unused-eslint-disable-comments) - */ - reportUnusedDisableDirectives?: boolean | undefined; - - /** - * Settings. - * - * @see [Settings](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#adding-shared-settings) - */ - settings?: { [name: string]: any } | undefined; - } - - /** - * The overwrites that apply more differing configuration to specific files or directories. - */ - interface ConfigOverride extends BaseConfig { - /** - * The glob patterns for excluded files. - */ - excludedFiles?: string | string[] | undefined; - - /** - * The glob patterns for target files. - */ - files: string | string[]; - } - - /** - * ESLint legacy configuration. - * - * @see [ESLint Legacy Configuration](https://eslint.org/docs/latest/use/configure/) - */ - // https://github.com/eslint/eslint/blob/v8.57.0/conf/config-schema.js - interface LegacyConfig - extends BaseConfig { - /** - * Tell ESLint to ignore specific files and directories. - * - * @see [Ignore Patterns](https://eslint.org/docs/latest/use/configure/ignore-deprecated#ignorepatterns-in-config-files) - */ - ignorePatterns?: string | string[] | undefined; - - /** - * @see [Using Configuration Files](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#using-configuration-files) - */ - root?: boolean | undefined; - } - - /** - * Parser options. - * - * @see [Specifying Parser Options](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options) - */ - interface ParserOptions { - /** - * Accepts any valid ECMAScript version number or `'latest'`: - * - * - A version: es3, es5, es6, es7, es8, es9, es10, es11, es12, es13, es14, ..., or - * - A year: es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, ..., or - * - `'latest'` - * - * When it's a version or a year, the value must be a number - so do not include the `es` prefix. - * - * Specifies the version of ECMAScript syntax you want to use. This is used by the parser to determine how to perform scope analysis, and it affects the default - * - * @default 5 - */ - ecmaVersion?: EcmaVersion | undefined; - - /** - * 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 'script' - * - * @see https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options - */ - sourceType?: SourceType | undefined; - - /** - * An object indicating which additional language features you'd like to use. - * - * @see https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options - */ - ecmaFeatures?: { - globalReturn?: boolean | undefined; - impliedStrict?: boolean | undefined; - jsx?: boolean | undefined; - experimentalObjectRestSpread?: boolean | undefined; - [key: string]: any; - } | undefined; - [key: string]: any; - } - - interface LintOptions { - filename?: string | undefined; - preprocess?: ((code: string) => string[]) | undefined; - postprocess?: ((problemLists: LintMessage[][]) => LintMessage[]) | undefined; - filterCodeBlock?: boolean | undefined; - disableFixes?: boolean | undefined; - allowInlineConfig?: boolean | undefined; - reportUnusedDisableDirectives?: boolean | undefined; - } - - interface LintSuggestion { - desc: string; - fix: Rule.Fix; - messageId?: string | undefined; - } - - interface LintMessage { - column: number; - line: number; - endColumn?: number | undefined; - endLine?: number | undefined; - ruleId: string | null; - message: string; - messageId?: string | undefined; - /** - * @deprecated `nodeType` is deprecated and will be removed in the next major version. - */ - nodeType?: string | undefined; - fatal?: true | undefined; - severity: Exclude; - fix?: Rule.Fix | undefined; - suggestions?: LintSuggestion[] | undefined; - } - - interface LintSuppression { - kind: string; - justification: string; - } - - interface SuppressedLintMessage extends LintMessage { - suppressions: LintSuppression[]; - } - - interface FixOptions extends LintOptions { - fix?: boolean | undefined; - } - - interface FixReport { - fixed: boolean; - output: string; - messages: LintMessage[]; - } - - // Temporarily loosen type for just flat config files (see #68232) - type NonESTreeParser = - & Omit - & ({ - parse(text: string, options?: any): unknown; - } | { - parseForESLint(text: string, options?: any): Omit & { - ast: unknown; - scopeManager?: unknown; - }; - }); - - type ESTreeParser = - & ESLint.ObjectMetaProperties - & ( - | { parse(text: string, options?: any): AST.Program } - | { parseForESLint(text: string, options?: any): ESLintParseResult } - ); - - type Parser = NonESTreeParser | ESTreeParser; - - interface ESLintParseResult { - ast: AST.Program; - parserServices?: SourceCode.ParserServices | undefined; - scopeManager?: Scope.ScopeManager | undefined; - visitorKeys?: SourceCode.VisitorKeys | undefined; - } - - interface ProcessorFile { - text: string; - filename: string; - } - - // https://eslint.org/docs/latest/extend/plugins#processors-in-plugins - interface Processor extends ESLint.ObjectMetaProperties { - supportsAutofix?: boolean | undefined; - preprocess?(text: string, filename: string): T[]; - postprocess?(messages: LintMessage[][], filename: string): LintMessage[]; - } - - interface Config { - /** - * An string to identify the configuration object. Used in error messages and - * inspection tools. - */ - name?: string; - - /** - * 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 - */ - files?: Array; - - /** - * 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 - */ - ignores?: string[]; - - /** - * The name of the language used for linting. This is used to determine the - * parser and other language-specific settings. - * @since 9.7.0 - */ - language?: string; - - /** - * An object containing settings related to how JavaScript is configured for - * linting. - */ - languageOptions?: LanguageOptions; - - /** - * An object containing settings related to the linting process - */ - linterOptions?: LinterOptions; - - /** - * 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"). - */ - processor?: string | Processor; - - /** - * 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. - */ - plugins?: Record; - - /** - * An object containing the configured rules. When files or ignores are specified, - * these rule configurations are only available to the matching files. - */ - rules?: Partial; - - /** - * An object containing name-value pairs of information that should be - * available to all rules. - */ - settings?: Record; - } - - /** @deprecated Use `Config` instead of `FlatConfig` */ - type FlatConfig = Config; - - type GlobalConf = boolean | "off" | "readable" | "readonly" | "writable" | "writeable"; - - interface Globals { - [name: string]: GlobalConf; - } - - interface LanguageOptions extends GenericLanguageOptions { - /** - * 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" - */ - ecmaVersion?: EcmaVersion | undefined; - - /** - * 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) - */ - sourceType?: SourceType | undefined; - - /** - * An object specifying additional objects that should be added to the - * global scope during linting. - */ - globals?: Globals | undefined; - - /** - * An object containing a parse() or parseForESLint() method. - * If not configured, the default ESLint parser (Espree) will be used. - */ - parser?: Parser | undefined; - - /** - * An object specifying additional options that are passed directly to the - * parser() method on the parser. The available options are parser-dependent - */ - parserOptions?: Linter.ParserOptions | undefined; - } - - interface LinterOptions { - /** - * A boolean value indicating if inline configuration is allowed. - */ - noInlineConfig?: boolean; - - /** - * A severity value indicating if and how unused disable directives should be - * tracked and reported. - */ - reportUnusedDisableDirectives?: Severity | StringSeverity | boolean; - - /** - * A severity value indicating if and how unused inline configs should be - * tracked and reported. - */ - reportUnusedInlineConfigs?: Severity | StringSeverity; - } - - interface Stats { - /** - * The number of times ESLint has applied at least one fix after linting. - */ - fixPasses: number; - - /** - * The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule. - */ - times: { passes: TimePass[] }; - } - - interface TimePass { - parse: { total: number }; - rules?: Record; - fix: { total: number }; - total: number; - } + /** + * The numeric severity level for a rule. + * + * - `0` means off. + * - `1` means warn. + * - `2` means error. + * + * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) + */ + type Severity = 0 | 1 | 2; + + /** + * The human readable severity level for a rule. + * + * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) + */ + type StringSeverity = "off" | "warn" | "error"; + + /** + * The numeric or human readable severity level for a rule. + * + * @see [Rule Severities](https://eslint.org/docs/latest/use/configure/rules#rule-severities) + */ + type RuleSeverity = Severity | StringSeverity; + + /** + * An array containing the rule severity level, followed by the rule options. + * + * @see [Rules](https://eslint.org/docs/latest/use/configure/rules) + */ + type RuleSeverityAndOptions = [ + RuleSeverity, + ...Partial, + ]; + + /** + * The severity level for the rule or an array containing the rule severity level, followed by the rule options. + * + * @see [Rules](https://eslint.org/docs/latest/use/configure/rules) + */ + type RuleEntry = + | RuleSeverity + | RuleSeverityAndOptions; + + /** + * The rules config object is a key/value map of rule names and their severity and options. + */ + interface RulesRecord { + [rule: string]: RuleEntry; + } + + /** + * A configuration object that may have a `rules` block. + */ + interface HasRules { + rules?: Partial | undefined; + } + + /** + * The ECMAScript version of the code being linted. + */ + type EcmaVersion = + | 3 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 2015 + | 2016 + | 2017 + | 2018 + | 2019 + | 2020 + | 2021 + | 2022 + | 2023 + | 2024 + | 2025 + | "latest"; + + /** + * The type of JavaScript source code. + */ + type SourceType = "script" | "module" | "commonjs"; + + /** + * ESLint legacy configuration. + * + * @see [ESLint Legacy Configuration](https://eslint.org/docs/latest/use/configure/) + */ + interface BaseConfig< + Rules extends RulesRecord = RulesRecord, + OverrideRules extends RulesRecord = Rules, + > extends HasRules { + $schema?: string | undefined; + + /** + * An environment provides predefined global variables. + * + * @see [Environments](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-environments) + */ + env?: { [name: string]: boolean } | undefined; + + /** + * Extending configuration files. + * + * @see [Extends](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#extending-configuration-files) + */ + extends?: string | string[] | undefined; + + /** + * Specifying globals. + * + * @see [Globals](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-globals) + */ + globals?: Linter.Globals | undefined; + + /** + * Disable processing of inline comments. + * + * @see [Disabling Inline Comments](https://eslint.org/docs/latest/use/configure/rules-deprecated#disabling-inline-comments) + */ + noInlineConfig?: boolean | undefined; + + /** + * Overrides can be used to use a differing configuration for matching sub-directories and files. + * + * @see [How do overrides work](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#how-do-overrides-work) + */ + overrides?: Array> | undefined; + + /** + * Parser. + * + * @see [Working with Custom Parsers](https://eslint.org/docs/latest/extend/custom-parsers) + * @see [Specifying Parser](https://eslint.org/docs/latest/use/configure/parser-deprecated) + */ + parser?: string | undefined; + + /** + * Parser options. + * + * @see [Working with Custom Parsers](https://eslint.org/docs/latest/extend/custom-parsers) + * @see [Specifying Parser Options](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options) + */ + parserOptions?: ParserOptions | undefined; + + /** + * Which third-party plugins define additional rules, environments, configs, etc. for ESLint to use. + * + * @see [Configuring Plugins](https://eslint.org/docs/latest/use/configure/plugins-deprecated#configure-plugins) + */ + plugins?: string[] | undefined; + + /** + * Specifying processor. + * + * @see [processor](https://eslint.org/docs/latest/use/configure/plugins-deprecated#specify-a-processor) + */ + processor?: string | undefined; + + /** + * Report unused eslint-disable comments as warning. + * + * @see [Report unused eslint-disable comments](https://eslint.org/docs/latest/use/configure/rules-deprecated#report-unused-eslint-disable-comments) + */ + reportUnusedDisableDirectives?: boolean | undefined; + + /** + * Settings. + * + * @see [Settings](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#adding-shared-settings) + */ + settings?: { [name: string]: any } | undefined; + } + + /** + * The overwrites that apply more differing configuration to specific files or directories. + */ + interface ConfigOverride + extends BaseConfig { + /** + * The glob patterns for excluded files. + */ + excludedFiles?: string | string[] | undefined; + + /** + * The glob patterns for target files. + */ + files: string | string[]; + } + + /** + * ESLint legacy configuration. + * + * @see [ESLint Legacy Configuration](https://eslint.org/docs/latest/use/configure/) + */ + // https://github.com/eslint/eslint/blob/v8.57.0/conf/config-schema.js + interface LegacyConfig< + Rules extends RulesRecord = RulesRecord, + OverrideRules extends RulesRecord = Rules, + > extends BaseConfig { + /** + * Tell ESLint to ignore specific files and directories. + * + * @see [Ignore Patterns](https://eslint.org/docs/latest/use/configure/ignore-deprecated#ignorepatterns-in-config-files) + */ + ignorePatterns?: string | string[] | undefined; + + /** + * @see [Using Configuration Files](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated#using-configuration-files) + */ + root?: boolean | undefined; + } + + /** + * Parser options. + * + * @see [Specifying Parser Options](https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options) + */ + interface ParserOptions { + /** + * Accepts any valid ECMAScript version number or `'latest'`: + * + * - A version: es3, es5, es6, es7, es8, es9, es10, es11, es12, es13, es14, ..., or + * - A year: es2015, es2016, es2017, es2018, es2019, es2020, es2021, es2022, es2023, ..., or + * - `'latest'` + * + * When it's a version or a year, the value must be a number - so do not include the `es` prefix. + * + * Specifies the version of ECMAScript syntax you want to use. This is used by the parser to determine how to perform scope analysis, and it affects the default + * + * @default 5 + */ + ecmaVersion?: EcmaVersion | undefined; + + /** + * 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 'script' + * + * @see https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options + */ + sourceType?: SourceType | undefined; + + /** + * An object indicating which additional language features you'd like to use. + * + * @see https://eslint.org/docs/latest/use/configure/language-options-deprecated#specifying-parser-options + */ + ecmaFeatures?: + | { + globalReturn?: boolean | undefined; + impliedStrict?: boolean | undefined; + jsx?: boolean | undefined; + experimentalObjectRestSpread?: boolean | undefined; + [key: string]: any; + } + | undefined; + [key: string]: any; + } + + interface LintOptions { + filename?: string | undefined; + preprocess?: ((code: string) => string[]) | undefined; + postprocess?: + | ((problemLists: LintMessage[][]) => LintMessage[]) + | undefined; + filterCodeBlock?: boolean | undefined; + disableFixes?: boolean | undefined; + allowInlineConfig?: boolean | undefined; + reportUnusedDisableDirectives?: boolean | undefined; + } + + interface LintSuggestion { + desc: string; + fix: Rule.Fix; + messageId?: string | undefined; + } + + interface LintMessage { + column: number; + line: number; + endColumn?: number | undefined; + endLine?: number | undefined; + ruleId: string | null; + message: string; + messageId?: string | undefined; + /** + * @deprecated `nodeType` is deprecated and will be removed in the next major version. + */ + nodeType?: string | undefined; + fatal?: true | undefined; + severity: Exclude; + fix?: Rule.Fix | undefined; + suggestions?: LintSuggestion[] | undefined; + } + + interface LintSuppression { + kind: string; + justification: string; + } + + interface SuppressedLintMessage extends LintMessage { + suppressions: LintSuppression[]; + } + + interface FixOptions extends LintOptions { + fix?: boolean | undefined; + } + + interface FixReport { + fixed: boolean; + output: string; + messages: LintMessage[]; + } + + // Temporarily loosen type for just flat config files (see #68232) + type NonESTreeParser = Omit & + ( + | { + parse(text: string, options?: any): unknown; + } + | { + parseForESLint( + text: string, + options?: any, + ): Omit & { + ast: unknown; + scopeManager?: unknown; + }; + } + ); + + type ESTreeParser = ESLint.ObjectMetaProperties & + ( + | { parse(text: string, options?: any): AST.Program } + | { parseForESLint(text: string, options?: any): ESLintParseResult } + ); + + type Parser = NonESTreeParser | ESTreeParser; + + interface ESLintParseResult { + ast: AST.Program; + parserServices?: SourceCode.ParserServices | undefined; + scopeManager?: Scope.ScopeManager | undefined; + visitorKeys?: SourceCode.VisitorKeys | undefined; + } + + interface ProcessorFile { + text: string; + filename: string; + } + + // https://eslint.org/docs/latest/extend/plugins#processors-in-plugins + interface Processor< + T extends string | ProcessorFile = string | ProcessorFile, + > extends ESLint.ObjectMetaProperties { + supportsAutofix?: boolean | undefined; + preprocess?(text: string, filename: string): T[]; + postprocess?( + messages: LintMessage[][], + filename: string, + ): LintMessage[]; + } + + interface Config { + /** + * An string to identify the configuration object. Used in error messages and + * inspection tools. + */ + name?: string; + + /** + * 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 + */ + files?: Array; + + /** + * 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 + */ + ignores?: string[]; + + /** + * The name of the language used for linting. This is used to determine the + * parser and other language-specific settings. + * @since 9.7.0 + */ + language?: string; + + /** + * An object containing settings related to how JavaScript is configured for + * linting. + */ + languageOptions?: LanguageOptions; + + /** + * An object containing settings related to the linting process + */ + linterOptions?: LinterOptions; + + /** + * 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"). + */ + processor?: string | Processor; + + /** + * 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. + */ + plugins?: Record; + + /** + * An object containing the configured rules. When files or ignores are specified, + * these rule configurations are only available to the matching files. + */ + rules?: Partial; + + /** + * An object containing name-value pairs of information that should be + * available to all rules. + */ + settings?: Record; + } + + /** @deprecated Use `Config` instead of `FlatConfig` */ + type FlatConfig = Config; + + type GlobalConf = + | boolean + | "off" + | "readable" + | "readonly" + | "writable" + | "writeable"; + + interface Globals { + [name: string]: GlobalConf; + } + + interface LanguageOptions extends GenericLanguageOptions { + /** + * 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" + */ + ecmaVersion?: EcmaVersion | undefined; + + /** + * 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) + */ + sourceType?: SourceType | undefined; + + /** + * An object specifying additional objects that should be added to the + * global scope during linting. + */ + globals?: Globals | undefined; + + /** + * An object containing a parse() or parseForESLint() method. + * If not configured, the default ESLint parser (Espree) will be used. + */ + parser?: Parser | undefined; + + /** + * An object specifying additional options that are passed directly to the + * parser() method on the parser. The available options are parser-dependent + */ + parserOptions?: Linter.ParserOptions | undefined; + } + + interface LinterOptions { + /** + * A boolean value indicating if inline configuration is allowed. + */ + noInlineConfig?: boolean; + + /** + * A severity value indicating if and how unused disable directives should be + * tracked and reported. + */ + reportUnusedDisableDirectives?: Severity | StringSeverity | boolean; + + /** + * A severity value indicating if and how unused inline configs should be + * tracked and reported. + */ + reportUnusedInlineConfigs?: Severity | StringSeverity; + } + + interface Stats { + /** + * The number of times ESLint has applied at least one fix after linting. + */ + fixPasses: number; + + /** + * The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule. + */ + times: { passes: TimePass[] }; + } + + interface TimePass { + parse: { total: number }; + rules?: Record; + fix: { total: number }; + total: number; + } } // #endregion @@ -1416,286 +1847,306 @@ export namespace Linter { // #region ESLint export class ESLint { - static configType: "flat"; + static configType: "flat"; - static readonly version: string; + static readonly version: string; - /** - * 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. - */ - static readonly defaultConfig: Linter.Config[]; + /** + * 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. + */ + static readonly defaultConfig: Linter.Config[]; - static outputFixes(results: ESLint.LintResult[]): Promise; + static outputFixes(results: ESLint.LintResult[]): Promise; - static getErrorResults(results: ESLint.LintResult[]): ESLint.LintResult[]; + static getErrorResults(results: ESLint.LintResult[]): ESLint.LintResult[]; - constructor(options?: ESLint.Options); + constructor(options?: ESLint.Options); - lintFiles(patterns: string | string[]): Promise; + lintFiles(patterns: string | string[]): Promise; - lintText( - code: string, - options?: { filePath?: string | undefined; warnIgnored?: boolean | undefined }, - ): Promise; + lintText( + code: string, + options?: { + filePath?: string | undefined; + warnIgnored?: boolean | undefined; + }, + ): Promise; - getRulesMetaForResults(results: ESLint.LintResult[]): ESLint.LintResultData["rulesMeta"]; + getRulesMetaForResults( + results: ESLint.LintResult[], + ): ESLint.LintResultData["rulesMeta"]; - hasFlag(flag: string): boolean; + hasFlag(flag: string): boolean; - calculateConfigForFile(filePath: string): Promise; + calculateConfigForFile(filePath: string): Promise; - findConfigFile(): Promise; + findConfigFile(): Promise; - isPathIgnored(filePath: string): Promise; + isPathIgnored(filePath: string): Promise; - loadFormatter(nameOrPath?: string): Promise; + loadFormatter(nameOrPath?: string): Promise; } export namespace ESLint { - type ConfigData = Omit< - Linter.LegacyConfig, - "$schema" - >; - - interface Environment { - globals?: Linter.Globals | undefined; - parserOptions?: Linter.ParserOptions | undefined; - } - - interface ObjectMetaProperties { - /** @deprecated Use `meta.name` instead. */ - name?: string | undefined; - - /** @deprecated Use `meta.version` instead. */ - version?: string | undefined; - - meta?: { - name?: string | undefined; - version?: string | undefined; - }; - } - - interface Plugin extends ObjectMetaProperties { - configs?: Record | undefined; - environments?: Record | undefined; - languages?: Record | undefined; - processors?: Record | undefined; - rules?: Record | undefined; - } - - type FixType = "directive" | "problem" | "suggestion" | "layout"; - - type CacheStrategy = "content" | "metadata"; - - interface Options { - // File enumeration - cwd?: string | undefined; - errorOnUnmatchedPattern?: boolean | undefined; - globInputPaths?: boolean | undefined; - ignore?: boolean | undefined; - ignorePatterns?: string[] | null | undefined; - passOnNoPatterns?: boolean | undefined; - warnIgnored?: boolean | undefined; - - // Linting - allowInlineConfig?: boolean | undefined; - baseConfig?: Linter.Config | Linter.Config[] | null | undefined; - overrideConfig?: Linter.Config | Linter.Config[] | null | undefined; - overrideConfigFile?: string | true | null | undefined; - plugins?: Record | null | undefined; - ruleFilter?: ((arg: { ruleId: string; severity: Exclude }) => boolean) | undefined; - stats?: boolean | undefined; - - // Autofix - fix?: boolean | ((message: Linter.LintMessage) => boolean) | undefined; - fixTypes?: FixType[] | undefined; - - // Cache-related - cache?: boolean | undefined; - cacheLocation?: string | undefined; - cacheStrategy?: CacheStrategy | undefined; - - // Other Options - flags?: string[] | undefined; - } - - interface LegacyOptions { - // File enumeration - cwd?: string | undefined; - errorOnUnmatchedPattern?: boolean | undefined; - extensions?: string[] | undefined; - globInputPaths?: boolean | undefined; - ignore?: boolean | undefined; - ignorePath?: string | undefined; - - // Linting - allowInlineConfig?: boolean | undefined; - baseConfig?: Linter.LegacyConfig | undefined; - overrideConfig?: Linter.LegacyConfig | undefined; - overrideConfigFile?: string | undefined; - plugins?: Record | undefined; - reportUnusedDisableDirectives?: Linter.StringSeverity | undefined; - resolvePluginsRelativeTo?: string | undefined; - rulePaths?: string[] | undefined; - useEslintrc?: boolean | undefined; - - // Autofix - fix?: boolean | ((message: Linter.LintMessage) => boolean) | undefined; - fixTypes?: FixType[] | undefined; - - // Cache-related - cache?: boolean | undefined; - cacheLocation?: string | undefined; - cacheStrategy?: CacheStrategy | undefined; - - // Other Options - flags?: string[] | undefined; - } - - interface LintResult { - filePath: string; - messages: Linter.LintMessage[]; - suppressedMessages: Linter.SuppressedLintMessage[]; - errorCount: number; - fatalErrorCount: number; - warningCount: number; - fixableErrorCount: number; - fixableWarningCount: number; - output?: string | undefined; - source?: string | undefined; - stats?: Linter.Stats | undefined; - usedDeprecatedRules: DeprecatedRuleUse[]; - } - - interface MaxWarningsExceeded { - - /** - * Number of warnings to trigger nonzero exit code. - */ - maxWarnings: number; - - /** - * Number of warnings found while linting. - */ - foundWarnings: number; - } - - interface LintResultData { - cwd: string; - maxWarningsExceeded?: MaxWarningsExceeded | undefined; - rulesMeta: { - [ruleId: string]: Rule.RuleMetaData; - }; - } - - interface DeprecatedRuleUse { - ruleId: string; - replacedBy: string[]; - } - - interface ResultsMeta { - maxWarningsExceeded?: MaxWarningsExceeded | undefined; - } - - /** The type of an object resolved by {@link ESLint.loadFormatter}. */ - interface LoadedFormatter { - - /** - * Used to call the underlying formatter. - * @param results An array of lint results to format. - * @param resultsMeta An object with an optional `maxWarningsExceeded` property that will be - * passed to the underlying formatter function along with other properties set by ESLint. - * This argument can be omitted if `maxWarningsExceeded` is not needed. - * @return The formatter output. - */ - format(results: LintResult[], resultsMeta?: ResultsMeta): string | Promise; - } - - // The documented type name is `LoadedFormatter`, but `Formatter` has been historically more used. - type Formatter = LoadedFormatter; - - /** - * The expected signature of a custom formatter. - * @param results An array of lint results to format. - * @param context Additional information for the formatter. - * @return The formatter output. - */ - type FormatterFunction = - (results: LintResult[], context: LintResultData) => string | Promise; - - // Docs reference the types by those name - type EditInfo = Rule.Fix; + type ConfigData = + Omit, "$schema">; + + interface Environment { + globals?: Linter.Globals | undefined; + parserOptions?: Linter.ParserOptions | undefined; + } + + interface ObjectMetaProperties { + /** @deprecated Use `meta.name` instead. */ + name?: string | undefined; + + /** @deprecated Use `meta.version` instead. */ + version?: string | undefined; + + meta?: { + name?: string | undefined; + version?: string | undefined; + }; + } + + interface Plugin extends ObjectMetaProperties { + configs?: + | Record< + string, + Linter.LegacyConfig | Linter.Config | Linter.Config[] + > + | undefined; + environments?: Record | undefined; + languages?: Record | undefined; + processors?: Record | undefined; + rules?: Record | undefined; + } + + type FixType = "directive" | "problem" | "suggestion" | "layout"; + + type CacheStrategy = "content" | "metadata"; + + interface Options { + // File enumeration + cwd?: string | undefined; + errorOnUnmatchedPattern?: boolean | undefined; + globInputPaths?: boolean | undefined; + ignore?: boolean | undefined; + ignorePatterns?: string[] | null | undefined; + passOnNoPatterns?: boolean | undefined; + warnIgnored?: boolean | undefined; + + // Linting + allowInlineConfig?: boolean | undefined; + baseConfig?: Linter.Config | Linter.Config[] | null | undefined; + overrideConfig?: Linter.Config | Linter.Config[] | null | undefined; + overrideConfigFile?: string | true | null | undefined; + plugins?: Record | null | undefined; + ruleFilter?: + | ((arg: { + ruleId: string; + severity: Exclude; + }) => boolean) + | undefined; + stats?: boolean | undefined; + + // Autofix + fix?: boolean | ((message: Linter.LintMessage) => boolean) | undefined; + fixTypes?: FixType[] | undefined; + + // Cache-related + cache?: boolean | undefined; + cacheLocation?: string | undefined; + cacheStrategy?: CacheStrategy | undefined; + + // Other Options + flags?: string[] | undefined; + } + + interface LegacyOptions { + // File enumeration + cwd?: string | undefined; + errorOnUnmatchedPattern?: boolean | undefined; + extensions?: string[] | undefined; + globInputPaths?: boolean | undefined; + ignore?: boolean | undefined; + ignorePath?: string | undefined; + + // Linting + allowInlineConfig?: boolean | undefined; + baseConfig?: Linter.LegacyConfig | undefined; + overrideConfig?: Linter.LegacyConfig | undefined; + overrideConfigFile?: string | undefined; + plugins?: Record | undefined; + reportUnusedDisableDirectives?: Linter.StringSeverity | undefined; + resolvePluginsRelativeTo?: string | undefined; + rulePaths?: string[] | undefined; + useEslintrc?: boolean | undefined; + + // Autofix + fix?: boolean | ((message: Linter.LintMessage) => boolean) | undefined; + fixTypes?: FixType[] | undefined; + + // Cache-related + cache?: boolean | undefined; + cacheLocation?: string | undefined; + cacheStrategy?: CacheStrategy | undefined; + + // Other Options + flags?: string[] | undefined; + } + + interface LintResult { + filePath: string; + messages: Linter.LintMessage[]; + suppressedMessages: Linter.SuppressedLintMessage[]; + errorCount: number; + fatalErrorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + output?: string | undefined; + source?: string | undefined; + stats?: Linter.Stats | undefined; + usedDeprecatedRules: DeprecatedRuleUse[]; + } + + interface MaxWarningsExceeded { + /** + * Number of warnings to trigger nonzero exit code. + */ + maxWarnings: number; + + /** + * Number of warnings found while linting. + */ + foundWarnings: number; + } + + interface LintResultData { + cwd: string; + maxWarningsExceeded?: MaxWarningsExceeded | undefined; + rulesMeta: { + [ruleId: string]: Rule.RuleMetaData; + }; + } + + interface DeprecatedRuleUse { + ruleId: string; + replacedBy: string[]; + } + + interface ResultsMeta { + maxWarningsExceeded?: MaxWarningsExceeded | undefined; + } + + /** The type of an object resolved by {@link ESLint.loadFormatter}. */ + interface LoadedFormatter { + /** + * Used to call the underlying formatter. + * @param results An array of lint results to format. + * @param resultsMeta An object with an optional `maxWarningsExceeded` property that will be + * passed to the underlying formatter function along with other properties set by ESLint. + * This argument can be omitted if `maxWarningsExceeded` is not needed. + * @return The formatter output. + */ + format( + results: LintResult[], + resultsMeta?: ResultsMeta, + ): string | Promise; + } + + // The documented type name is `LoadedFormatter`, but `Formatter` has been historically more used. + type Formatter = LoadedFormatter; + + /** + * The expected signature of a custom formatter. + * @param results An array of lint results to format. + * @param context Additional information for the formatter. + * @return The formatter output. + */ + type FormatterFunction = ( + results: LintResult[], + context: LintResultData, + ) => string | Promise; + + // Docs reference the types by those name + type EditInfo = Rule.Fix; } // #endregion -export function loadESLint(options: { useFlatConfig: true }): Promise; -export function loadESLint(options: { useFlatConfig: false }): Promise; -export function loadESLint( - options?: { useFlatConfig?: boolean | undefined }, -): Promise; +export function loadESLint(options: { + useFlatConfig: true; +}): Promise; +export function loadESLint(options: { + useFlatConfig: false; +}): Promise; +export function loadESLint(options?: { + useFlatConfig?: boolean | undefined; +}): Promise; // #region RuleTester export class RuleTester { - static describe: ((...args: any) => any) | null; - static it: ((...args: any) => any) | null; - static itOnly: ((...args: any) => any) | null; - - constructor(config?: Linter.Config); - - run( - name: string, - rule: Rule.RuleModule, - tests: { - valid: Array; - invalid: RuleTester.InvalidTestCase[]; - }, - ): void; - - static only( - item: string | RuleTester.ValidTestCase | RuleTester.InvalidTestCase, - ): RuleTester.ValidTestCase | RuleTester.InvalidTestCase; + static describe: ((...args: any) => any) | null; + static it: ((...args: any) => any) | null; + static itOnly: ((...args: any) => any) | null; + + constructor(config?: Linter.Config); + + run( + name: string, + rule: Rule.RuleModule, + tests: { + valid: Array; + invalid: RuleTester.InvalidTestCase[]; + }, + ): void; + + static only( + item: string | RuleTester.ValidTestCase | RuleTester.InvalidTestCase, + ): RuleTester.ValidTestCase | RuleTester.InvalidTestCase; } export namespace RuleTester { - interface ValidTestCase { - name?: string; - code: string; - options?: any; - filename?: string | undefined; - only?: boolean; - languageOptions?: Linter.LanguageOptions | undefined; - settings?: { [name: string]: any } | undefined; - } - - interface SuggestionOutput { - messageId?: string; - desc?: string; - data?: Record | undefined; - output: string; - } - - interface InvalidTestCase extends ValidTestCase { - errors: number | Array; - output?: string | null | undefined; - } - - interface TestCaseError { - message?: string | RegExp; - messageId?: string; - /** - * @deprecated `type` is deprecated and will be removed in the next major version. - */ - type?: string | undefined; - data?: any; - line?: number | undefined; - column?: number | undefined; - endLine?: number | undefined; - endColumn?: number | undefined; - suggestions?: SuggestionOutput[] | undefined; - } + interface ValidTestCase { + name?: string; + code: string; + options?: any; + filename?: string | undefined; + only?: boolean; + languageOptions?: Linter.LanguageOptions | undefined; + settings?: { [name: string]: any } | undefined; + } + + interface SuggestionOutput { + messageId?: string; + desc?: string; + data?: Record | undefined; + output: string; + } + + interface InvalidTestCase extends ValidTestCase { + errors: number | Array; + output?: string | null | undefined; + } + + interface TestCaseError { + message?: string | RegExp; + messageId?: string; + /** + * @deprecated `type` is deprecated and will be removed in the next major version. + */ + type?: string | undefined; + data?: any; + line?: number | undefined; + column?: number | undefined; + endLine?: number | undefined; + endColumn?: number | undefined; + suggestions?: SuggestionOutput[] | undefined; + } } // #endregion diff --git a/lib/types/rules.d.ts b/lib/types/rules.d.ts index dbaff580d839..27b9ce1dbdf7 100644 --- a/lib/types/rules.d.ts +++ b/lib/types/rules.d.ts @@ -36,5164 +36,5277 @@ import { Linter } from "./index"; //----------------------------------------------------------------------------- interface NoRestrictedImportPathCommonOptions { - name: string; - message?: string; + name: string; + message?: string; } type EitherImportNamesOrAllowImportName = - | { importNames?: string[]; allowImportNames?: never } - | { allowImportNames?: string[]; importNames?: never } + | { importNames?: string[]; allowImportNames?: never } + | { allowImportNames?: string[]; importNames?: never }; -type ValidNoRestrictedImportPathOptions = NoRestrictedImportPathCommonOptions & EitherImportNamesOrAllowImportName; +type ValidNoRestrictedImportPathOptions = NoRestrictedImportPathCommonOptions & + EitherImportNamesOrAllowImportName; interface NoRestrictedImportPatternCommonOptions { - message?: string; - caseSensitive?: boolean; + message?: string; + caseSensitive?: boolean; } // Base type for group or regex constraint, ensuring mutual exclusivity type EitherGroupOrRegEx = - | { group: string[]; regex?: never } - | { regex: string; group?: never }; + | { group: string[]; regex?: never } + | { regex: string; group?: never }; // Base type for import name specifiers, ensuring mutual exclusivity -type EitherNameSpecifiers = - | { importNames: string[]; allowImportNames?: never; importNamePattern?: never; allowImportNamePattern?: never } - | { importNamePattern: string; allowImportNames?: never; importNames?: never; allowImportNamePattern?: never } - | { allowImportNames: string[]; importNames?: never; importNamePattern?: never; allowImportNamePattern?: never } - | { allowImportNamePattern: string; importNames?: never; allowImportNames?: never; importNamePattern?: never } +type EitherNameSpecifiers = + | { + importNames: string[]; + allowImportNames?: never; + importNamePattern?: never; + allowImportNamePattern?: never; + } + | { + importNamePattern: string; + allowImportNames?: never; + importNames?: never; + allowImportNamePattern?: never; + } + | { + allowImportNames: string[]; + importNames?: never; + importNamePattern?: never; + allowImportNamePattern?: never; + } + | { + allowImportNamePattern: string; + importNames?: never; + allowImportNames?: never; + importNamePattern?: never; + }; // Adds oneOf and not constraints, ensuring group or regex are present and mutually exclusive sets for importNames, allowImportNames, etc., as per the schema. -type ValidNoRestrictedImportPatternOptions = NoRestrictedImportPatternCommonOptions & EitherGroupOrRegEx & EitherNameSpecifiers; +type ValidNoRestrictedImportPatternOptions = + NoRestrictedImportPatternCommonOptions & + EitherGroupOrRegEx & + EitherNameSpecifiers; //----------------------------------------------------------------------------- // Public types //----------------------------------------------------------------------------- export interface ESLintRules extends Linter.RulesRecord { - - /** - * Rule to enforce getter and setter pairs in objects and classes. - * - * @since 0.22.0 - * @see https://eslint.org/docs/latest/rules/accessor-pairs - */ - "accessor-pairs": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - setWithoutGet: boolean; - /** - * @default false - */ - getWithoutSet: boolean; - /** - * @default true - */ - enforceForClassMembers: boolean; - }>, - ] - >; - - /** - * Rule to enforce linebreaks after opening and before closing array brackets. - * - * @since 4.0.0-alpha.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`array-bracket-newline`](https://eslint.style/rules/js/array-bracket-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/array-bracket-newline - */ - "array-bracket-newline": Linter.RuleEntry< - [ - | "always" - | "never" - | "consistent" - | Partial<{ - /** - * @default true - */ - multiline: boolean; - /** - * @default null - */ - minItems: number | null; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing inside array brackets. - * - * @since 0.24.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`array-bracket-spacing`](https://eslint.style/rules/js/array-bracket-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/array-bracket-spacing - */ - "array-bracket-spacing": - | Linter.RuleEntry< - [ - "never", - Partial<{ - /** - * @default false - */ - singleValue: boolean; - /** - * @default false - */ - objectsInArrays: boolean; - /** - * @default false - */ - arraysInArrays: boolean; - }>, - ] - > - | Linter.RuleEntry< - [ - "always", - Partial<{ - /** - * @default true - */ - singleValue: boolean; - /** - * @default true - */ - objectsInArrays: boolean; - /** - * @default true - */ - arraysInArrays: boolean; - }>, - ] - >; - - /** - * Rule to enforce `return` statements in callbacks of array methods. - * - * @since 2.0.0-alpha-1 - * @see https://eslint.org/docs/latest/rules/array-callback-return - */ - "array-callback-return": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowImplicit: boolean; - /** - * @default false - */ - checkForEach: boolean; - /** - * @default false - */ - allowVoid: boolean; - }>, - ] - >; - - /** - * Rule to enforce line breaks after each array element. - * - * @since 4.0.0-rc.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`array-element-newline`](https://eslint.style/rules/js/array-element-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/array-element-newline - */ - "array-element-newline": Linter.RuleEntry< - [ - | "always" - | "never" - | "consistent" - | Partial<{ - /** - * @default true - */ - multiline: boolean; - /** - * @default null - */ - minItems: number | null; - }>, - ] - >; - - /** - * Rule to require braces around arrow function bodies. - * - * @since 1.8.0 - * @see https://eslint.org/docs/latest/rules/arrow-body-style - */ - "arrow-body-style": - | Linter.RuleEntry< - [ - "as-needed", - Partial<{ - /** - * @default false - */ - requireReturnForObjectLiteral: boolean; - }>, - ] - > - | Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to require parentheses around arrow function arguments. - * - * @since 1.0.0-rc-1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`arrow-parens`](https://eslint.style/rules/js/arrow-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/arrow-parens - */ - "arrow-parens": - | Linter.RuleEntry<["always"]> - | Linter.RuleEntry< - [ - "as-needed", - Partial<{ - /** - * @default false - */ - requireForBlockBody: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing before and after the arrow in arrow functions. - * - * @since 1.0.0-rc-1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`arrow-spacing`](https://eslint.style/rules/js/arrow-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/arrow-spacing - */ - "arrow-spacing": Linter.RuleEntry<[]>; - - /** - * Rule to enforce the use of variables within the scope they are defined. - * - * @since 0.1.0 - * @see https://eslint.org/docs/latest/rules/block-scoped-var - */ - "block-scoped-var": Linter.RuleEntry<[]>; - - /** - * Rule to disallow or enforce spaces inside of blocks after opening block and before closing block. - * - * @since 1.2.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`block-spacing`](https://eslint.style/rules/js/block-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/block-spacing - */ - "block-spacing": Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to enforce consistent brace style for blocks. - * - * @since 0.0.7 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`brace-style`](https://eslint.style/rules/js/brace-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/brace-style - */ - "brace-style": Linter.RuleEntry< - [ - "1tbs" | "stroustrup" | "allman", - Partial<{ - /** - * @default false - */ - allowSingleLine: boolean; - }>, - ] - >; - - /** - * Rule to require `return` statements after callbacks. - * - * @since 1.0.0-rc-1 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`callback-return`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/callback-return.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/callback-return - */ - "callback-return": Linter.RuleEntry<[string[]]>; - - /** - * Rule to enforce camelcase naming convention. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/camelcase - */ - camelcase: Linter.RuleEntry< - [ - Partial<{ - /** - * @default 'always' - */ - properties: "always" | "never"; - /** - * @default false - */ - ignoreDestructuring: boolean; - /** - * @since 6.7.0 - * @default false - */ - ignoreImports: boolean; - /** - * @since 7.4.0 - * @default false - */ - ignoreGlobals: boolean; - /** - * @remarks - * Also accept for regular expression patterns - */ - allow: string[]; - }>, - ] - >; - - /** - * Rule to enforce or disallow capitalization of the first letter of a comment. - * - * @since 3.11.0 - * @see https://eslint.org/docs/latest/rules/capitalized-comments - */ - "capitalized-comments": Linter.RuleEntry< - [ - "always" | "never", - Partial<{ - ignorePattern: string; - /** - * @default false - */ - ignoreInlineComments: boolean; - /** - * @default false - */ - ignoreConsecutiveComments: boolean; - }>, - ] - >; - - /** - * Rule to enforce that class methods utilize `this`. - * - * @since 3.4.0 - * @see https://eslint.org/docs/latest/rules/class-methods-use-this - */ - "class-methods-use-this": Linter.RuleEntry< - [ - Partial<{ - exceptMethods: string[]; - }>, - ] - >; - - /** - * Rule to require or disallow trailing commas. - * - * @since 0.16.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`comma-dangle`](https://eslint.style/rules/js/comma-dangle) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/comma-dangle - */ - "comma-dangle": Linter.RuleEntry< - [ - | "never" - | "always" - | "always-multiline" - | "only-multiline" - | Partial<{ - /** - * @default 'never' - */ - arrays: "never" | "always" | "always-multiline" | "only-multiline"; - /** - * @default 'never' - */ - objects: "never" | "always" | "always-multiline" | "only-multiline"; - /** - * @default 'never' - */ - imports: "never" | "always" | "always-multiline" | "only-multiline"; - /** - * @default 'never' - */ - exports: "never" | "always" | "always-multiline" | "only-multiline"; - /** - * @default 'never' - */ - functions: "never" | "always" | "always-multiline" | "only-multiline"; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing before and after commas. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`comma-spacing`](https://eslint.style/rules/js/comma-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/comma-spacing - */ - "comma-spacing": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - before: boolean; - /** - * @default true - */ - after: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent comma style. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`comma-style`](https://eslint.style/rules/js/comma-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/comma-style - */ - "comma-style": Linter.RuleEntry< - [ - "last" | "first", - Partial<{ - exceptions: Record; - }>, - ] - >; - - /** - * Rule to enforce a maximum cyclomatic complexity allowed in a program. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/complexity - */ - complexity: Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 20 - */ - max: number; - /** - * @deprecated - * @default 20 - */ - maximum: number; - /** - * @default "classic" - * @since 9.12.0 - */ - variant: "classic" | "modified"; - }> - | number, - ] - >; - - /** - * Rule to enforce consistent spacing inside computed property brackets. - * - * @since 0.23.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`computed-property-spacing`](https://eslint.style/rules/js/computed-property-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/computed-property-spacing - */ - "computed-property-spacing": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require `return` statements to either always or never specify values. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/consistent-return - */ - "consistent-return": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - treatUndefinedAsUnspecified: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent naming when capturing the current execution context. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/consistent-this - */ - "consistent-this": Linter.RuleEntry<[...string[]]>; - - /** - * Rule to require `super()` calls in constructors. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.24.0 - * @see https://eslint.org/docs/latest/rules/constructor-super - */ - "constructor-super": Linter.RuleEntry<[]>; - - /** - * Rule to enforce consistent brace style for all control statements. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/curly - */ - curly: Linter.RuleEntry<["all" | "multi" | "multi-line" | "multi-or-nest" | "consistent"]>; - - /** - * Rule to require `default` cases in `switch` statements. - * - * @since 0.6.0 - * @see https://eslint.org/docs/latest/rules/default-case - */ - "default-case": Linter.RuleEntry< - [ - Partial<{ - /** - * @default '^no default$' - */ - commentPattern: string; - }>, - ] - >; - - /** - * Rule to enforce `default` clauses in `switch` statements to be last. - * - * @since 7.0.0-alpha.0 - * @see https://eslint.org/docs/latest/rules/default-case-last - */ - "default-case-last": Linter.RuleEntry<[]>; - - /** - * Rule to enforce default parameters to be last. - * - * @since 6.4.0 - * @see https://eslint.org/docs/latest/rules/default-param-last - */ - "default-param-last": Linter.RuleEntry<[]>; - - /** - * Rule to enforce consistent newlines before and after dots. - * - * @since 0.21.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`dot-location`](https://eslint.style/rules/js/dot-location) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/dot-location - */ - "dot-location": Linter.RuleEntry<["object" | "property"]>; - - /** - * Rule to enforce dot notation whenever possible. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/dot-notation - */ - "dot-notation": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - allowKeywords: boolean; - allowPattern: string; - }>, - ] - >; - - /** - * Rule to require or disallow newline at the end of files. - * - * @since 0.7.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`eol-last`](https://eslint.style/rules/js/eol-last) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/eol-last - */ - "eol-last": Linter.RuleEntry< - [ - "always" | "never", // | 'unix' | 'windows' - ] - >; - - /** - * Rule to require the use of `===` and `!==`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/eqeqeq - */ - eqeqeq: - | Linter.RuleEntry< - [ - "always", - Partial<{ - /** - * @default 'always' - */ - null: "always" | "never" | "ignore"; - }>, - ] - > - | Linter.RuleEntry<["smart" | "allow-null"]>; - - /** - * Rule to enforce `for` loop update clause moving the counter in the right direction. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 4.0.0-beta.0 - * @see https://eslint.org/docs/latest/rules/for-direction - */ - "for-direction": Linter.RuleEntry<[]>; - - /** - * Rule to require or disallow spacing between function identifiers and their invocations. - * - * @since 3.3.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`function-call-spacing`](https://eslint.style/rules/js/function-call-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/func-call-spacing - */ - "func-call-spacing": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require function names to match the name of the variable or property to which they are assigned. - * - * @since 3.8.0 - * @see https://eslint.org/docs/latest/rules/func-name-matching - */ - "func-name-matching": - | Linter.RuleEntry< - [ - "always" | "never", - Partial<{ - /** - * @default false - */ - considerPropertyDescriptor: boolean; - /** - * @default false - */ - includeCommonJSModuleExports: boolean; - }>, - ] - > - | Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - considerPropertyDescriptor: boolean; - /** - * @default false - */ - includeCommonJSModuleExports: boolean; - }>, - ] - >; - - /** - * Rule to require or disallow named `function` expressions. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/func-names - */ - "func-names": Linter.RuleEntry< - [ - "always" | "as-needed" | "never", - Partial<{ - generators: "always" | "as-needed" | "never"; - }>, - ] - >; - - /** - * Rule to enforce the consistent use of either `function` declarations or expressions assigned to variables. - * - * @since 0.2.0 - * @see https://eslint.org/docs/latest/rules/func-style - */ - "func-style": Linter.RuleEntry< - [ - "expression" | "declaration", - Partial<{ - /** - * @default false - */ - allowArrowFunctions: boolean; - overrides: { - namedExports: "declaration" | "expression" | "ignore"; - } - }>, - ] - >; - - /** - * Rule to enforce line breaks between arguments of a function call. - * - * @since 6.2.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`function-call-argument-newline`](https://eslint.style/rules/js/function-call-argument-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/function-call-argument-newline - */ - "function-call-argument-newline": Linter.RuleEntry< - [ - /** - * @default "always" - */ - "always" | "never" | "consistent" - ] - >; - - /** - * Rule to enforce consistent line breaks inside function parentheses. - * - * @since 4.6.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`function-paren-newline`](https://eslint.style/rules/js/function-paren-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/function-paren-newline - */ - "function-paren-newline": Linter.RuleEntry< - [ - | "always" - | "never" - | "multiline" - | "multiline-arguments" - | "consistent" - | Partial<{ - minItems: number; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing around `*` operators in generator functions. - * - * @since 0.17.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`generator-star-spacing`](https://eslint.style/rules/js/generator-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/generator-star-spacing - */ - "generator-star-spacing": Linter.RuleEntry< - [ - | Partial<{ - before: boolean; - after: boolean; - named: - | Partial<{ - before: boolean; - after: boolean; - }> - | "before" - | "after" - | "both" - | "neither"; - anonymous: - | Partial<{ - before: boolean; - after: boolean; - }> - | "before" - | "after" - | "both" - | "neither"; - method: - | Partial<{ - before: boolean; - after: boolean; - }> - | "before" - | "after" - | "both" - | "neither"; - }> - | "before" - | "after" - | "both" - | "neither", - ] - >; - - /** - * Rule to enforce `return` statements in getters. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 4.2.0 - * @see https://eslint.org/docs/latest/rules/getter-return - */ - "getter-return": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowImplicit: boolean; - }>, - ] - >; - - /** - * Rule to require `require()` calls to be placed at top-level module scope. - * - * @since 1.4.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`global-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/global-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/global-require - */ - "global-require": Linter.RuleEntry<[]>; - - /** - * Rule to require grouped accessor pairs in object literals and classes. - * - * @since 6.7.0 - * @see https://eslint.org/docs/latest/rules/grouped-accessor-pairs - */ - "grouped-accessor-pairs": Linter.RuleEntry<["anyOrder" | "getBeforeSet" | "setBeforeGet"]>; - - /** - * Rule to require `for-in` loops to include an `if` statement. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/guard-for-in - */ - "guard-for-in": Linter.RuleEntry<[]>; - - /** - * Rule to require error handling in callbacks. - * - * @since 0.4.5 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`handle-callback-err`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/handle-callback-err.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/handle-callback-err - */ - "handle-callback-err": Linter.RuleEntry<[string]>; - - /** - * Rule to disallow specified identifiers. - * - * @since 2.0.0-beta.2 - * @deprecated since 7.5.0. - * The rule was renamed. - * Please, use [`id-denylist`](https://eslint.org/docs/rules/id-denylist). - * @see https://eslint.org/docs/latest/rules/id-blacklist - */ - "id-blacklist": Linter.RuleEntry<[...string[]]>; - - /** - * Rule to disallow specified identifiers. - * - * @since 7.4.0 - * @see https://eslint.org/docs/latest/rules/id-denylist - */ - "id-denylist": Linter.RuleEntry; - - /** - * Rule to enforce minimum and maximum identifier lengths. - * - * @since 1.0.0 - * @see https://eslint.org/docs/latest/rules/id-length - */ - "id-length": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 2 - */ - min: number; - /** - * @default Infinity - */ - max: number; - /** - * @default 'always' - */ - properties: "always" | "never"; - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to require identifiers to match a specified regular expression. - * - * @since 1.0.0 - * @see https://eslint.org/docs/latest/rules/id-match - */ - "id-match": Linter.RuleEntry< - [ - string, - Partial<{ - /** - * @default false - */ - properties: boolean; - /** - * @default false - */ - onlyDeclarations: boolean; - /** - * @default false - */ - ignoreDestructuring: boolean; - }>, - ] - >; - - /** - * Rule to enforce the location of arrow function bodies. - * - * @since 4.12.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`implicit-arrow-linebreak`](https://eslint.style/rules/js/implicit-arrow-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/implicit-arrow-linebreak - */ - "implicit-arrow-linebreak": Linter.RuleEntry<["beside" | "below"]>; - - /** - * Rule to enforce consistent indentation. - * - * @since 0.14.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`indent`](https://eslint.style/rules/js/indent) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/indent - */ - indent: Linter.RuleEntry< - [ - number | "tab", - Partial<{ - /** - * @default 0 - */ - SwitchCase: number; - /** - * @default 1 - */ - VariableDeclarator: - | Partial<{ - /** - * @default 1 - */ - var: number | "first"; - /** - * @default 1 - */ - let: number | "first"; - /** - * @default 1 - */ - const: number | "first"; - }> - | number - | "first"; - /** - * @default 1 - */ - outerIIFEBody: number; - /** - * @default 1 - */ - MemberExpression: number | "off"; - /** - * @default { parameters: 1, body: 1 } - */ - FunctionDeclaration: Partial<{ - /** - * @default 1 - */ - parameters: number | "first" | "off"; - /** - * @default 1 - */ - body: number; - }>; - /** - * @default { parameters: 1, body: 1 } - */ - FunctionExpression: Partial<{ - /** - * @default 1 - */ - parameters: number | "first" | "off"; - /** - * @default 1 - */ - body: number; - }>; - /** - * @default { arguments: 1 } - */ - CallExpression: Partial<{ - /** - * @default 1 - */ - arguments: number | "first" | "off"; - }>; - /** - * @default 1 - */ - ArrayExpression: number | "first" | "off"; - /** - * @default 1 - */ - ObjectExpression: number | "first" | "off"; - /** - * @default 1 - */ - ImportDeclaration: number | "first" | "off"; - /** - * @default false - */ - flatTernaryExpressions: boolean; - ignoredNodes: string[]; - /** - * @default false - */ - ignoreComments: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent indentation. - * - * @since 4.0.0-alpha.0 - * @deprecated since 4.0.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`indent`](https://eslint.style/rules/js/indent) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/indent-legacy - */ - "indent-legacy": Linter.RuleEntry< - [ - number | "tab", - Partial<{ - /** - * @default 0 - */ - SwitchCase: number; - /** - * @default 1 - */ - VariableDeclarator: - | Partial<{ - /** - * @default 1 - */ - var: number | "first"; - /** - * @default 1 - */ - let: number | "first"; - /** - * @default 1 - */ - const: number | "first"; - }> - | number - | "first"; - /** - * @default 1 - */ - outerIIFEBody: number; - /** - * @default 1 - */ - MemberExpression: number | "off"; - /** - * @default { parameters: 1, body: 1 } - */ - FunctionDeclaration: Partial<{ - /** - * @default 1 - */ - parameters: number | "first" | "off"; - /** - * @default 1 - */ - body: number; - }>; - /** - * @default { parameters: 1, body: 1 } - */ - FunctionExpression: Partial<{ - /** - * @default 1 - */ - parameters: number | "first" | "off"; - /** - * @default 1 - */ - body: number; - }>; - /** - * @default { arguments: 1 } - */ - CallExpression: Partial<{ - /** - * @default 1 - */ - arguments: number | "first" | "off"; - }>; - /** - * @default 1 - */ - ArrayExpression: number | "first" | "off"; - /** - * @default 1 - */ - ObjectExpression: number | "first" | "off"; - /** - * @default 1 - */ - ImportDeclaration: number | "first" | "off"; - /** - * @default false - */ - flatTernaryExpressions: boolean; - ignoredNodes: string[]; - /** - * @default false - */ - ignoreComments: boolean; - }>, - ] - >; - - /** - * Rule to require or disallow initialization in variable declarations. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/init-declarations - */ - "init-declarations": - | Linter.RuleEntry<["always"]> - | Linter.RuleEntry< - [ - "never", - Partial<{ - ignoreForLoopInit: boolean; - }>, - ] - >; - - /** - * Rule to enforce the consistent use of either double or single quotes in JSX attributes. - * - * @since 1.4.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`jsx-quotes`](https://eslint.style/rules/js/jsx-quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/jsx-quotes - */ - "jsx-quotes": Linter.RuleEntry<["prefer-double" | "prefer-single"]>; - - /** - * Rule to enforce consistent spacing between keys and values in object literal properties. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`key-spacing`](https://eslint.style/rules/js/key-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/key-spacing - */ - "key-spacing": Linter.RuleEntry< - [ - | Partial< - | { - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - align: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'colon' - */ - on: "value" | "colon"; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }> - | "value" - | "colon"; - } - | { - singleLine?: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }> - | undefined; - multiLine?: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - align: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'colon' - */ - on: "value" | "colon"; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }> - | "value" - | "colon"; - }> - | undefined; - } - > - | { - align: Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'colon' - */ - on: "value" | "colon"; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }>; - singleLine?: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }> - | undefined; - multiLine?: - | Partial<{ - /** - * @default false - */ - beforeColon: boolean; - /** - * @default true - */ - afterColon: boolean; - /** - * @default 'strict' - */ - mode: "strict" | "minimum"; - }> - | undefined; - }, - ] - >; - - /** - * Rule to enforce consistent spacing before and after keywords. - * - * @since 2.0.0-beta.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`keyword-spacing`](https://eslint.style/rules/js/keyword-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/keyword-spacing - */ - "keyword-spacing": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - before: boolean; - /** - * @default true - */ - after: boolean; - overrides: Record< - string, - Partial<{ - before: boolean; - after: boolean; - }> - >; - }>, - ] - >; - - /** - * Rule to enforce position of line comments. - * - * @since 3.5.0 - * @deprecated since 9.3.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`line-comment-position`](https://eslint.style/rules/js/line-comment-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/line-comment-position - */ - "line-comment-position": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 'above' - */ - position: "above" | "beside"; - ignorePattern: string; - /** - * @default true - */ - applyDefaultIgnorePatterns: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent linebreak style. - * - * @since 0.21.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`linebreak-style`](https://eslint.style/rules/js/linebreak-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/linebreak-style - */ - "linebreak-style": Linter.RuleEntry<["unix" | "windows"]>; - - /** - * Rule to require empty lines around comments. - * - * @since 0.22.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`lines-around-comment`](https://eslint.style/rules/js/lines-around-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/lines-around-comment - */ - "lines-around-comment": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - beforeBlockComment: boolean; - /** - * @default false - */ - afterBlockComment: boolean; - /** - * @default false - */ - beforeLineComment: boolean; - /** - * @default false - */ - afterLineComment: boolean; - /** - * @default false - */ - allowBlockStart: boolean; - /** - * @default false - */ - allowBlockEnd: boolean; - /** - * @default false - */ - allowObjectStart: boolean; - /** - * @default false - */ - allowObjectEnd: boolean; - /** - * @default false - */ - allowArrayStart: boolean; - /** - * @default false - */ - allowArrayEnd: boolean; - /** - * @default false - */ - allowClassStart: boolean; - /** - * @default false - */ - allowClassEnd: boolean; - ignorePattern: string; - /** - * @default true - */ - applyDefaultIgnorePatterns: boolean; - }>, - ] - >; - - /** - * Rule to require or disallow newlines around directives. - * - * @since 3.5.0 - * @deprecated since 4.0.0. - * The rule was replaced with a more general rule. - * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/lines-around-directive - */ - "lines-around-directive": Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to require or disallow an empty line between class members. - * - * @since 4.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`lines-between-class-members`](https://eslint.style/rules/js/lines-between-class-members) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/lines-between-class-members - */ - "lines-between-class-members": Linter.RuleEntry< - [ - "always" | "never" | { - enforce: Array< - { - blankLine: "always" | "never"; - prev: "method" | "field" | "*"; - next: "method" | "field" | "*"; - } - > - }, - Partial<{ - /** - * @default false - */ - exceptAfterSingleLine: boolean; - }>, - ] - >; - - /** - * Rule to require or disallow logical assignment operator shorthand. - * - * @since 8.24.0 - * @see https://eslint.org/docs/latest/rules/logical-assignment-operators - */ - "logical-assignment-operators": - | Linter.RuleEntry< - [ - "always", - Partial<{ - /** - * @default false - */ - enforceForIfStatements: boolean; - }>, - ] - > - | Linter.RuleEntry<["never"]>; - - /** - * Rule to enforce a maximum number of classes per file. - * - * @since 5.0.0-alpha.3 - * @see https://eslint.org/docs/latest/rules/max-classes-per-file - */ - "max-classes-per-file": Linter.RuleEntry<[number]>; - - /** - * Rule to enforce a maximum depth that blocks can be nested. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/max-depth - */ - "max-depth": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 4 - */ - max: number; - }>, - ] - >; - - /** - * Rule to enforce a maximum line length. - * - * @since 0.0.9 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`max-len`](https://eslint.style/rules/js/max-len) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/max-len - */ - "max-len": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 80 - */ - code: number; - /** - * @default 4 - */ - tabWidth: number; - comments: number; - ignorePattern: string; - /** - * @default false - */ - ignoreComments: boolean; - /** - * @default false - */ - ignoreTrailingComments: boolean; - /** - * @default false - */ - ignoreUrls: boolean; - /** - * @default false - */ - ignoreStrings: boolean; - /** - * @default false - */ - ignoreTemplateLiterals: boolean; - /** - * @default false - */ - ignoreRegExpLiterals: boolean; - }>, - ] - >; - - /** - * Rule to enforce a maximum number of lines per file. - * - * @since 2.12.0 - * @see https://eslint.org/docs/latest/rules/max-lines - */ - "max-lines": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 300 - */ - max: number; - /** - * @default false - */ - skipBlankLines: boolean; - /** - * @default false - */ - skipComments: boolean; - }> - | number, - ] - >; - - /** - * Rule to enforce a maximum number of lines of code in a function. - * - * @since 5.0.0 - * @see https://eslint.org/docs/latest/rules/max-lines-per-function - */ - "max-lines-per-function": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 50 - */ - max: number; - /** - * @default false - */ - skipBlankLines: boolean; - /** - * @default false - */ - skipComments: boolean; - /** - * @default false - */ - IIFEs: boolean; - }>, - ] - >; - - /** - * Rule to enforce a maximum depth that callbacks can be nested. - * - * @since 0.2.0 - * @see https://eslint.org/docs/latest/rules/max-nested-callbacks - */ - "max-nested-callbacks": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 10 - */ - max: number; - }> - | number, - ] - >; - - /** - * Rule to enforce a maximum number of parameters in function definitions. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/max-params - */ - "max-params": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 3 - */ - max: number; - }> - | number, - ] - >; - - /** - * Rule to enforce a maximum number of statements allowed in function blocks. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/max-statements - */ - "max-statements": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 10 - */ - max: number; - /** - * @default false - */ - ignoreTopLevelFunctions: boolean; - }> - | number, - ] - >; - - /** - * Rule to enforce a maximum number of statements allowed per line. - * - * @since 2.5.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`max-statements-per-line`](https://eslint.style/rules/js/max-statements-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/max-statements-per-line - */ - "max-statements-per-line": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 1 - */ - max: number; - }> - | number, - ] - >; - - /** - * Rule to enforce a particular style for multiline comments. - * - * @since 4.10.0 - * @deprecated since 9.3.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`multiline-comment-style`](https://eslint.style/rules/js/multiline-comment-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/multiline-comment-style - */ - "multiline-comment-style": Linter.RuleEntry<["starred-block" | "bare-block" | "separate-lines"]>; - - /** - * Rule to enforce newlines between operands of ternary expressions. - * - * @since 3.1.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`multiline-ternary`](https://eslint.style/rules/js/multiline-ternary) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/multiline-ternary - */ - "multiline-ternary": Linter.RuleEntry<["always" | "always-multiline" | "never"]>; - - /** - * Rule to require constructor names to begin with a capital letter. - * - * @since 0.0.3-0 - * @see https://eslint.org/docs/latest/rules/new-cap - */ - "new-cap": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - newIsCap: boolean; - /** - * @default true - */ - capIsNew: boolean; - newIsCapExceptions: string[]; - newIsCapExceptionPattern: string; - capIsNewExceptions: string[]; - capIsNewExceptionPattern: string; - /** - * @default true - */ - properties: boolean; - }>, - ] - >; - - /** - * Rule to enforce or disallow parentheses when invoking a constructor with no arguments. - * - * @since 0.0.6 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`new-parens`](https://eslint.style/rules/js/new-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/new-parens - */ - "new-parens": Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to require or disallow an empty line after variable declarations. - * - * @since 0.18.0 - * @deprecated since 4.0.0. - * The rule was replaced with a more general rule. - * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/newline-after-var - */ - "newline-after-var": Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to require an empty line before `return` statements. - * - * @since 2.3.0 - * @deprecated since 4.0.0. - * The rule was replaced with a more general rule. - * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/newline-before-return - */ - "newline-before-return": Linter.RuleEntry<[]>; - - /** - * Rule to require a newline after each call in a method chain. - * - * @since 2.0.0-rc.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`newline-per-chained-call`](https://eslint.style/rules/js/newline-per-chained-call) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/newline-per-chained-call - */ - "newline-per-chained-call": Linter.RuleEntry< - [ - { - /** - * @default 2 - */ - ignoreChainWithDepth: number; - }, - ] - >; - - /** - * Rule to disallow the use of `alert`, `confirm`, and `prompt`. - * - * @since 0.0.5 - * @see https://eslint.org/docs/latest/rules/no-alert - */ - "no-alert": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `Array` constructors. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-array-constructor - */ - "no-array-constructor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow using an async function as a Promise executor. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 5.3.0 - * @see https://eslint.org/docs/latest/rules/no-async-promise-executor - */ - "no-async-promise-executor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `await` inside of loops. - * - * @since 3.12.0 - * @see https://eslint.org/docs/latest/rules/no-await-in-loop - */ - "no-await-in-loop": Linter.RuleEntry<[]>; - - /** - * Rule to disallow bitwise operators. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-bitwise - */ - "no-bitwise": Linter.RuleEntry< - [ - Partial<{ - allow: string[]; - /** - * @default false - */ - int32Hint: boolean; - }>, - ] - >; - - /** - * Rule to disallow use of the `Buffer()` constructor. - * - * @since 4.0.0-alpha.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-deprecated-api`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-deprecated-api.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-buffer-constructor - */ - "no-buffer-constructor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `arguments.caller` or `arguments.callee`. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/no-caller - */ - "no-caller": Linter.RuleEntry<[]>; - - /** - * Rule to disallow lexical declarations in case clauses. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.9.0 - * @see https://eslint.org/docs/latest/rules/no-case-declarations - */ - "no-case-declarations": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `catch` clause parameters from shadowing variables in the outer scope. - * - * @since 0.0.9 - * @deprecated since 5.1.0. - * This rule was renamed. - * Please, use [`no-shadow`](https://eslint.org/docs/rules/no-shadow). - * @see https://eslint.org/docs/latest/rules/no-catch-shadow - */ - "no-catch-shadow": Linter.RuleEntry<[]>; - - /** - * Rule to disallow reassigning class members. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/no-class-assign - */ - "no-class-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow comparing against `-0`. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 3.17.0 - * @see https://eslint.org/docs/latest/rules/no-compare-neg-zero - */ - "no-compare-neg-zero": Linter.RuleEntry<[]>; - - /** - * Rule to disallow assignment operators in conditional expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-cond-assign - */ - "no-cond-assign": Linter.RuleEntry<["except-parens" | "always"]>; - - /** - * Rule to disallow arrow functions where they could be confused with comparisons. - * - * @since 2.0.0-alpha-2 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-confusing-arrow`](https://eslint.style/rules/js/no-confusing-arrow) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-confusing-arrow - */ - "no-confusing-arrow": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - allowParens: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of `console`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-console - */ - "no-console": Linter.RuleEntry< - [ - Partial<{ - allow: Array; - }>, - ] - >; - - /** - * Rule to disallow reassigning `const` variables. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/no-const-assign - */ - "no-const-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow expressions where the operation doesn't affect the value. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 8.14.0 - * @see https://eslint.org/docs/latest/rules/no-constant-binary-expression - */ - "no-constant-binary-expression": Linter.RuleEntry<[]>; - - /** - * Rule to disallow constant expressions in conditions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.4.1 - * @see https://eslint.org/docs/latest/rules/no-constant-condition - */ - "no-constant-condition": Linter.RuleEntry< - [ - { - /** - * @default true - */ - checkLoops: boolean; - }, - ] - >; - - /** - * Rule to disallow returning value from constructor. - * - * @since 6.7.0 - * @see https://eslint.org/docs/latest/rules/no-constructor-return - */ - "no-constructor-return": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `continue` statements. - * - * @since 0.19.0 - * @see https://eslint.org/docs/latest/rules/no-continue - */ - "no-continue": Linter.RuleEntry<[]>; - - /** - * Rule to disallow control characters in regular expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.1.0 - * @see https://eslint.org/docs/latest/rules/no-control-regex - */ - "no-control-regex": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `debugger`. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-debugger - */ - "no-debugger": Linter.RuleEntry<[]>; - - /** - * Rule to disallow deleting variables. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-delete-var - */ - "no-delete-var": Linter.RuleEntry<[]>; - - /** - * Rule to disallow equal signs explicitly at the beginning of regular expressions. - * - * @since 0.1.0 - * @see https://eslint.org/docs/latest/rules/no-div-regex - */ - "no-div-regex": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate arguments in `function` definitions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.16.0 - * @see https://eslint.org/docs/latest/rules/no-dupe-args - */ - "no-dupe-args": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate class members. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.2.0 - * @see https://eslint.org/docs/latest/rules/no-dupe-class-members - */ - "no-dupe-class-members": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate conditions in if-else-if chains. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 6.7.0 - * @see https://eslint.org/docs/latest/rules/no-dupe-else-if - */ - "no-dupe-else-if": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate keys in object literals. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-dupe-keys - */ - "no-dupe-keys": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate case labels. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.17.0 - * @see https://eslint.org/docs/latest/rules/no-duplicate-case - */ - "no-duplicate-case": Linter.RuleEntry<[]>; - - /** - * Rule to disallow duplicate module imports. - * - * @since 2.5.0 - * @see https://eslint.org/docs/latest/rules/no-duplicate-imports - */ - "no-duplicate-imports": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - includeExports: boolean; - }>, - ] - >; - - /** - * Rule to disallow `else` blocks after `return` statements in `if` statements. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-else-return - */ - "no-else-return": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - allowElseIf: boolean; - }>, - ] - >; - - /** - * Rule to disallow empty block statements. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-empty - */ - "no-empty": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowEmptyCatch: boolean; - }>, - ] - >; - - /** - * Rule to disallow empty character classes in regular expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.22.0 - * @see https://eslint.org/docs/latest/rules/no-empty-character-class - */ - "no-empty-character-class": Linter.RuleEntry<[]>; - - /** - * Rule to disallow empty functions. - * - * @since 2.0.0 - * @see https://eslint.org/docs/latest/rules/no-empty-function - */ - "no-empty-function": Linter.RuleEntry< - [ - Partial<{ - /** - * @default [] - */ - allow: Array< - | "functions" - | "arrowFunctions" - | "generatorFunctions" - | "methods" - | "generatorMethods" - | "getters" - | "setters" - | "constructors" - | "asyncFunctions" - | "asyncMethods" - >; - }>, - ] - >; - - /** - * Rule to disallow empty destructuring patterns. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.7.0 - * @see https://eslint.org/docs/latest/rules/no-empty-pattern - */ - "no-empty-pattern": Linter.RuleEntry<[]>; - - /** - * Rule to disallow empty static blocks. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 8.27.0 - * @see https://eslint.org/docs/latest/rules/no-empty-static-block - */ - "no-empty-static-block": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `null` comparisons without type-checking operators. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-eq-null - */ - "no-eq-null": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `eval()`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-eval - */ - "no-eval": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowIndirect: boolean; - }>, - ] - >; - - /** - * Rule to disallow reassigning exceptions in `catch` clauses. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-ex-assign - */ - "no-ex-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow extending native types. - * - * @since 0.1.4 - * @see https://eslint.org/docs/latest/rules/no-extend-native - */ - "no-extend-native": Linter.RuleEntry< - [ - Partial<{ - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to disallow unnecessary calls to `.bind()`. - * - * @since 0.8.0 - * @see https://eslint.org/docs/latest/rules/no-extra-bind - */ - "no-extra-bind": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary boolean casts. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-extra-boolean-cast - */ - "no-extra-boolean-cast": Linter.RuleEntry< - [ - | Partial<{ - /** - * @since 9.3.0 - * @default false - */ - enforceForInnerExpressions: boolean; - /** - * @deprecated - */ - enforceForLogicalOperands: never; - }> - | Partial<{ - /** - * @deprecated - * @since 7.0.0-alpha.2 - * @default false - */ - enforceForLogicalOperands: boolean; - enforceForInnerExpressions: never; - }>, - ] - >; - - /** - * Rule to disallow unnecessary labels. - * - * @since 2.0.0-rc.0 - * @see https://eslint.org/docs/latest/rules/no-extra-label - */ - "no-extra-label": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary parentheses. - * - * @since 0.1.4 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-extra-parens`](https://eslint.style/rules/js/no-extra-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-extra-parens - */ - "no-extra-parens": - | Linter.RuleEntry< - [ - "all", - Partial<{ - /** - * @default true, - */ - conditionalAssign: boolean; - /** - * @default true - */ - returnAssign: boolean; - /** - * @default true - */ - nestedBinaryExpressions: boolean; - /** - * @default 'none' - */ - ignoreJSX: "none" | "all" | "multi-line" | "single-line"; - /** - * @default true - */ - enforceForArrowConditionals: boolean; - }>, - ] - > - | Linter.RuleEntry<["functions"]>; - - /** - * Rule to disallow unnecessary semicolons. - * - * @since 0.0.9 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-extra-semi`](https://eslint.style/rules/js/no-extra-semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-extra-semi - */ - "no-extra-semi": Linter.RuleEntry<[]>; - - /** - * Rule to disallow fallthrough of `case` statements. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/no-fallthrough - */ - "no-fallthrough": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 'falls?\s?through' - */ - commentPattern: string; - /** - * @default false - */ - allowEmptyCase: boolean; - /** - * @default false - */ - reportUnusedFallthroughComment: boolean; - }>, - ] - >; - - /** - * Rule to disallow leading or trailing decimal points in numeric literals. - * - * @since 0.0.6 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-floating-decimal`](https://eslint.style/rules/js/no-floating-decimal) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-floating-decimal - */ - "no-floating-decimal": Linter.RuleEntry<[]>; - - /** - * Rule to disallow reassigning `function` declarations. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-func-assign - */ - "no-func-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow assignments to native objects or read-only global variables. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 3.3.0 - * @see https://eslint.org/docs/latest/rules/no-global-assign - */ - "no-global-assign": Linter.RuleEntry< - [ - Partial<{ - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to disallow shorthand type conversions. - * - * @since 1.0.0-rc-2 - * @see https://eslint.org/docs/latest/rules/no-implicit-coercion - */ - "no-implicit-coercion": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - boolean: boolean; - /** - * @default true - */ - number: boolean; - /** - * @default true - */ - string: boolean; - /** - * @default false - */ - disallowTemplateShorthand: boolean; - /** - * @default [] - */ - allow: Array<"~" | "!!" | "+" | "- -" | "-" | "*">; - }>, - ] - >; - - /** - * Rule to disallow declarations in the global scope. - * - * @since 2.0.0-alpha-1 - * @see https://eslint.org/docs/latest/rules/no-implicit-globals - */ - "no-implicit-globals": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `eval()`-like methods. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/no-implied-eval - */ - "no-implied-eval": Linter.RuleEntry<[]>; - - /** - * Rule to disallow assigning to imported bindings. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 6.4.0 - * @see https://eslint.org/docs/latest/rules/no-import-assign - */ - "no-import-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow inline comments after code. - * - * @since 0.10.0 - * @see https://eslint.org/docs/latest/rules/no-inline-comments - */ - "no-inline-comments": Linter.RuleEntry<[]>; - - /** - * Rule to disallow variable or `function` declarations in nested blocks. - * - * @since 0.6.0 - * @see https://eslint.org/docs/latest/rules/no-inner-declarations - */ - "no-inner-declarations": Linter.RuleEntry<["functions" | "both"]>; - - /** - * Rule to disallow invalid regular expression strings in `RegExp` constructors. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.1.4 - * @see https://eslint.org/docs/latest/rules/no-invalid-regexp - */ - "no-invalid-regexp": Linter.RuleEntry< - [ - Partial<{ - allowConstructorFlags: string[]; - }>, - ] - >; - - /** - * Rule to disallow use of `this` in contexts where the value of `this` is `undefined`. - * - * @since 1.0.0-rc-2 - * @see https://eslint.org/docs/latest/rules/no-invalid-this - */ - "no-invalid-this": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - capIsConstructor: boolean; - }>, - ] - >; - - /** - * Rule to disallow irregular whitespace. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.9.0 - * @see https://eslint.org/docs/latest/rules/no-irregular-whitespace - */ - "no-irregular-whitespace": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - skipStrings: boolean; - /** - * @default false - */ - skipComments: boolean; - /** - * @default false - */ - skipRegExps: boolean; - /** - * @default false - */ - skipTemplates: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of the `__iterator__` property. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-iterator - */ - "no-iterator": Linter.RuleEntry<[]>; - - /** - * Rule to disallow labels that share a name with a variable. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-label-var - */ - "no-label-var": Linter.RuleEntry<[]>; - - /** - * Rule to disallow labeled statements. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-labels - */ - "no-labels": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowLoop: boolean; - /** - * @default false - */ - allowSwitch: boolean; - }>, - ] - >; - - /** - * Rule to disallow unnecessary nested blocks. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-lone-blocks - */ - "no-lone-blocks": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `if` statements as the only statement in `else` blocks. - * - * @since 0.6.0 - * @see https://eslint.org/docs/latest/rules/no-lonely-if - */ - "no-lonely-if": Linter.RuleEntry<[]>; - - /** - * Rule to disallow function declarations that contain unsafe references inside loop statements. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-loop-func - */ - "no-loop-func": Linter.RuleEntry<[]>; - - /** - * Rule to disallow literal numbers that lose precision. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 7.1.0 - * @see https://eslint.org/docs/latest/rules/no-loss-of-precision - */ - "no-loss-of-precision": Linter.RuleEntry<[]>; - - /** - * Rule to disallow magic numbers. - * - * @since 1.7.0 - * @see https://eslint.org/docs/latest/rules/no-magic-numbers - */ - "no-magic-numbers": Linter.RuleEntry< - [ - Partial<{ - /** - * @default [] - */ - ignore: number[]; - /** - * @default false - */ - ignoreArrayIndexes: boolean; - /** - * @default false - */ - enforceConst: boolean; - /** - * @default false - */ - detectObjects: boolean; - }>, - ] - >; - - /** - * Rule to disallow characters which are made with multiple code points in character class syntax. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 5.3.0 - * @see https://eslint.org/docs/latest/rules/no-misleading-character-class - */ - "no-misleading-character-class": Linter.RuleEntry< - [ - Partial<{ - /** - * @since 9.3.0 - * @default false - */ - allowEscape: boolean; - }>, - ] - >; - - /** - * Rule to disallow mixed binary operators. - * - * @since 2.12.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-mixed-operators`](https://eslint.style/rules/js/no-mixed-operators) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-mixed-operators - */ - "no-mixed-operators": Linter.RuleEntry< - [ - Partial<{ - /** - * @default - * [ - * ["+", "-", "*", "/", "%", "**"], - * ["&", "|", "^", "~", "<<", ">>", ">>>"], - * ["==", "!=", "===", "!==", ">", ">=", "<", "<="], - * ["&&", "||"], - * ["in", "instanceof"] - * ] - */ - groups: string[][]; - /** - * @default true - */ - allowSamePrecedence: boolean; - }>, - ] - >; - - /** - * Rule to disallow `require` calls to be mixed with regular variable declarations. - * - * @since 0.0.9 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-mixed-requires`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-mixed-requires.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-mixed-requires - */ - "no-mixed-requires": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - grouping: boolean; - /** - * @default false - */ - allowCall: boolean; - }>, - ] - >; - - /** - * Rule to disallow mixed spaces and tabs for indentation. - * - * @since 0.7.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-mixed-spaces-and-tabs`](https://eslint.style/rules/js/no-mixed-spaces-and-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs - */ - "no-mixed-spaces-and-tabs": Linter.RuleEntry<["smart-tabs"]>; - - /** - * Rule to disallow use of chained assignment expressions. - * - * @since 3.14.0 - * @see https://eslint.org/docs/latest/rules/no-multi-assign - */ - "no-multi-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow multiple spaces. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-multi-spaces`](https://eslint.style/rules/js/no-multi-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-multi-spaces - */ - "no-multi-spaces": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - ignoreEOLComments: boolean; - /** - * @default { Property: true } - */ - exceptions: Record; - }>, - ] - >; - - /** - * Rule to disallow multiline strings. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-multi-str - */ - "no-multi-str": Linter.RuleEntry<[]>; - - /** - * Rule to disallow multiple empty lines. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-multiple-empty-lines`](https://eslint.style/rules/js/no-multiple-empty-lines) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-multiple-empty-lines - */ - "no-multiple-empty-lines": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default 2 - */ - max: number; - maxEOF: number; - maxBOF: number; - }> - | number, - ] - >; - - /** - * Rule to disallow assignments to native objects or read-only global variables. - * - * @since 0.0.9 - * @deprecated since 3.3.0. - * Renamed rule. - * Please, use [`no-global-assign`](https://eslint.org/docs/rules/no-global-assign). - * @see https://eslint.org/docs/latest/rules/no-native-reassign - */ - "no-native-reassign": Linter.RuleEntry< - [ - Partial<{ - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to disallow negated conditions. - * - * @since 1.6.0 - * @see https://eslint.org/docs/latest/rules/no-negated-condition - */ - "no-negated-condition": Linter.RuleEntry<[]>; - - /** - * Rule to disallow negating the left operand in `in` expressions. - * - * @since 0.1.2 - * @deprecated since 3.3.0. - * Renamed rule. - * Please, use [`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation). - * @see https://eslint.org/docs/latest/rules/no-negated-in-lhs - */ - "no-negated-in-lhs": Linter.RuleEntry<[]>; - - /** - * Rule to disallow nested ternary expressions. - * - * @since 0.2.0 - * @see https://eslint.org/docs/latest/rules/no-nested-ternary - */ - "no-nested-ternary": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators outside of assignments or comparisons. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/no-new - */ - "no-new": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators with the `Function` object. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/no-new-func - */ - "no-new-func": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators with global non-constructor functions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 8.27.0 - * @see https://eslint.org/docs/latest/rules/no-new-native-nonconstructor - */ - "no-new-native-nonconstructor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `Object` constructors. - * - * @since 0.0.9 - * @deprecated since 8.50.0. - * 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. - * Please, use [`no-object-constructor`](https://eslint.org/docs/rules/no-object-constructor). - * @see https://eslint.org/docs/latest/rules/no-new-object - */ - "no-new-object": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators with calls to `require`. - * - * @since 0.6.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-new-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-new-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-new-require - */ - "no-new-require": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators with the `Symbol` object. - * - * @since 2.0.0-beta.1 - * @deprecated since 9.0.0. - * The rule was replaced with a more general rule. - * Please, use [`no-new-native-nonconstructor`](https://eslint.org/docs/latest/rules/no-new-native-nonconstructor). - * @see https://eslint.org/docs/latest/rules/no-new-symbol - */ - "no-new-symbol": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `new` operators with the `String`, `Number`, and `Boolean` objects. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/no-new-wrappers - */ - "no-new-wrappers": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `\8` and `\9` escape sequences in string literals. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 7.14.0 - * @see https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape - */ - "no-nonoctal-decimal-escape": Linter.RuleEntry<[]>; - - /** - * Rule to disallow calling global object properties as functions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-obj-calls - */ - "no-obj-calls": Linter.RuleEntry<[]>; - - /** - * Rule to disallow calls to the `Object` constructor without an argument. - * - * @since 8.50.0 - * @see https://eslint.org/docs/latest/rules/no-object-constructor - */ - "no-object-constructor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow octal literals. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/no-octal - */ - "no-octal": Linter.RuleEntry<[]>; - - /** - * Rule to disallow octal escape sequences in string literals. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-octal-escape - */ - "no-octal-escape": Linter.RuleEntry<[]>; - - /** - * Rule to disallow reassigning function parameters. - * - * @since 0.18.0 - * @see https://eslint.org/docs/latest/rules/no-param-reassign - */ - "no-param-reassign": Linter.RuleEntry< - [ - | { - props?: false; - } - | ({ - props: true; - } & Partial<{ - /** - * @default [] - */ - ignorePropertyModificationsFor: string[]; - /** - * @since 6.6.0 - * @default [] - */ - ignorePropertyModificationsForRegex: string[]; - }>), - ] - >; - - /** - * Rule to disallow string concatenation with `__dirname` and `__filename`. - * - * @since 0.4.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-path-concat`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-path-concat.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-path-concat - */ - "no-path-concat": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the unary operators `++` and `--`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-plusplus - */ - "no-plusplus": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowForLoopAfterthoughts: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of `process.env`. - * - * @since 0.9.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-process-env`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-env.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-process-env - */ - "no-process-env": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `process.exit()`. - * - * @since 0.4.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-process-exit`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-exit.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-process-exit - */ - "no-process-exit": Linter.RuleEntry<[]>; - - /** - * Rule to disallow returning values from Promise executor functions. - * - * @since 7.3.0 - * @see https://eslint.org/docs/latest/rules/no-promise-executor-return - */ - "no-promise-executor-return": Linter.RuleEntry<[ - { - /** - * @default false - */ - allowVoid?: boolean; - }, - ]>; - - /** - * Rule to disallow the use of the `__proto__` property. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-proto - */ - "no-proto": Linter.RuleEntry<[]>; - - /** - * Rule to disallow calling some `Object.prototype` methods directly on objects. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 2.11.0 - * @see https://eslint.org/docs/latest/rules/no-prototype-builtins - */ - "no-prototype-builtins": Linter.RuleEntry<[]>; - - /** - * Rule to disallow variable redeclaration. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-redeclare - */ - "no-redeclare": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - builtinGlobals: boolean; - }>, - ] - >; - - /** - * Rule to disallow multiple spaces in regular expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-regex-spaces - */ - "no-regex-spaces": Linter.RuleEntry<[]>; - - /** - * Rule to disallow specified names in exports. - * - * @since 7.0.0-alpha.0 - * @see https://eslint.org/docs/latest/rules/no-restricted-exports - */ - "no-restricted-exports": Linter.RuleEntry< - [ - Partial<{ - /** - * @default [] - */ - restrictedNamedExports: string[]; - /** - * @since 9.3.0 - */ - restrictedNamedExportsPattern: string; - /** - * @since 8.33.0 - */ - restrictDefaultExports: Partial<{ - /** - * @default false - */ - direct: boolean; - /** - * @default false - */ - named: boolean; - /** - * @default false - */ - defaultFrom: boolean; - /** - * @default false - */ - namedFrom: boolean; - /** - * @default false - */ - namespaceFrom: boolean; - }>; - }>, - ] - >; - - /** - * Rule to disallow specified global variables. - * - * @since 2.3.0 - * @see https://eslint.org/docs/latest/rules/no-restricted-globals - */ - "no-restricted-globals": Linter.RuleEntry< - [ - ...Array< - | string - | { - name: string; - message?: string | undefined; - } - >, - ] - >; - - /** - * Rule to disallow specified modules when loaded by `import`. - * - * @since 2.0.0-alpha-1 - * @see https://eslint.org/docs/latest/rules/no-restricted-imports - */ - "no-restricted-imports": Linter.RuleEntry< - [ - ...Array< - | string - | ValidNoRestrictedImportPathOptions - | Partial<{ - paths: Array; - patterns: Array; - }> - > - ] - >; - - /** - * Rule to disallow specified modules when loaded by `require`. - * - * @since 0.6.0 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-restricted-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-restricted-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-restricted-modules - */ - "no-restricted-modules": Linter.RuleEntry< - [ - ...Array< - | string - | { - name: string; - message?: string | undefined; - } - | Partial<{ - paths: Array< - | string - | { - name: string; - message?: string | undefined; - } - >; - patterns: string[]; - }> - >, - ] - >; - - /** - * Rule to disallow certain properties on certain objects. - * - * @since 3.5.0 - * @see https://eslint.org/docs/latest/rules/no-restricted-properties - */ - "no-restricted-properties": Linter.RuleEntry< - [ - ...Array< - | { - object: string; - property?: string | undefined; - message?: string | undefined; - } - | { - property: string; - message?: string | undefined; - } - >, - ] - >; - - /** - * Rule to disallow specified syntax. - * - * @since 1.4.0 - * @see https://eslint.org/docs/latest/rules/no-restricted-syntax - */ - "no-restricted-syntax": Linter.RuleEntry< - [ - ...Array< - | string - | { - selector: string; - message?: string | undefined; - } - >, - ] - >; - - /** - * Rule to disallow assignment operators in `return` statements. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-return-assign - */ - "no-return-assign": Linter.RuleEntry<["except-parens" | "always"]>; - - /** - * Rule to disallow unnecessary `return await`. - * - * @since 3.10.0 - * @deprecated since 8.46.0. - * The original assumption of the rule no longer holds true because of engine optimization. - * @see https://eslint.org/docs/latest/rules/no-return-await - */ - "no-return-await": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `javascript:` URLs. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-script-url - */ - "no-script-url": Linter.RuleEntry<[]>; - - /** - * Rule to disallow assignments where both sides are exactly the same. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 2.0.0-rc.0 - * @see https://eslint.org/docs/latest/rules/no-self-assign - */ - "no-self-assign": Linter.RuleEntry<[]>; - - /** - * Rule to disallow comparisons where both sides are exactly the same. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-self-compare - */ - "no-self-compare": Linter.RuleEntry<[]>; - - /** - * Rule to disallow comma operators. - * - * @since 0.5.1 - * @see https://eslint.org/docs/latest/rules/no-sequences - */ - "no-sequences": Linter.RuleEntry< - [ - Partial<{ - /** - * @since 7.23.0 - * @default true - */ - allowInParentheses: boolean; - }>, - ] - >; - - /** - * Rule to disallow returning values from setters. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 6.7.0 - * @see https://eslint.org/docs/latest/rules/no-setter-return - */ - "no-setter-return": Linter.RuleEntry<[]>; - - /** - * Rule to disallow variable declarations from shadowing variables declared in the outer scope. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-shadow - */ - "no-shadow": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - builtinGlobals: boolean; - /** - * @default 'functions' - */ - hoist: "functions" | "all" | "never"; - allow: string[]; - /** - * @since 8.10.0 - * @default false - */ - ignoreOnInitialization: boolean; - }>, - ] - >; - - /** - * Rule to disallow identifiers from shadowing restricted names. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.1.4 - * @see https://eslint.org/docs/latest/rules/no-shadow-restricted-names - */ - "no-shadow-restricted-names": Linter.RuleEntry<[]>; - - /** - * Rule to disallow spacing between function identifiers and their applications (deprecated). - * - * @since 0.1.2 - * @deprecated since 3.3.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`function-call-spacing`](https://eslint.style/rules/js/function-call-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-spaced-func - */ - "no-spaced-func": Linter.RuleEntry<[]>; - - /** - * Rule to disallow sparse arrays. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.4.0 - * @see https://eslint.org/docs/latest/rules/no-sparse-arrays - */ - "no-sparse-arrays": Linter.RuleEntry<[]>; - - /** - * Rule to disallow synchronous methods. - * - * @since 0.0.9 - * @deprecated since 7.0.0. - * Node.js rules were moved out of ESLint core. - * Please, use [`no-sync`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-sync.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - * @see https://eslint.org/docs/latest/rules/no-sync - */ - "no-sync": Linter.RuleEntry< - [ - { - /** - * @default false - */ - allowAtRootLevel: boolean; - }, - ] - >; - - /** - * Rule to disallow all tabs. - * - * @since 3.2.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-tabs`](https://eslint.style/rules/js/no-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-tabs - */ - "no-tabs": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowIndentationTabs: boolean; - }>, - ] - >; - - /** - * Rule to disallow template literal placeholder syntax in regular strings. - * - * @since 3.3.0 - * @see https://eslint.org/docs/latest/rules/no-template-curly-in-string - */ - "no-template-curly-in-string": Linter.RuleEntry<[]>; - - /** - * Rule to disallow ternary operators. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-ternary - */ - "no-ternary": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `this`/`super` before calling `super()` in constructors. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.24.0 - * @see https://eslint.org/docs/latest/rules/no-this-before-super - */ - "no-this-before-super": Linter.RuleEntry<[]>; - - /** - * Rule to disallow throwing literals as exceptions. - * - * @since 0.15.0 - * @see https://eslint.org/docs/latest/rules/no-throw-literal - */ - "no-throw-literal": Linter.RuleEntry<[]>; - - /** - * Rule to disallow trailing whitespace at the end of lines. - * - * @since 0.7.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-trailing-spaces`](https://eslint.style/rules/js/no-trailing-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-trailing-spaces - */ - "no-trailing-spaces": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - skipBlankLines: boolean; - /** - * @default false - */ - ignoreComments: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of undeclared variables unless mentioned in \/*global *\/ comments. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-undef - */ - "no-undef": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - typeof: boolean; - }>, - ] - >; - - /** - * Rule to disallow initializing variables to `undefined`. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/no-undef-init - */ - "no-undef-init": Linter.RuleEntry<[]>; - - /** - * Rule to disallow the use of `undefined` as an identifier. - * - * @since 0.7.1 - * @see https://eslint.org/docs/latest/rules/no-undefined - */ - "no-undefined": Linter.RuleEntry<[]>; - - /** - * Rule to disallow dangling underscores in identifiers. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-underscore-dangle - */ - "no-underscore-dangle": Linter.RuleEntry< - [ - Partial<{ - allow: string[]; - /** - * @default false - */ - allowAfterThis: boolean; - /** - * @default false - */ - allowAfterSuper: boolean; - /** - * @since 6.7.0 - * @default false - */ - allowAfterThisConstructor: boolean; - /** - * @default false - */ - enforceInMethodNames: boolean; - /** - * @since 8.15.0 - * @default false - */ - enforceInClassFields: boolean; - /** - * @since 8.31.0 - * @default true - */ - allowInArrayDestructuring: boolean; - /** - * @since 8.31.0 - * @default true - */ - allowInObjectDestructuring: boolean; - /** - * @since 7.7.0 - * @default true - */ - allowFunctionParams: boolean; - }>, - ] - >; - - /** - * Rule to disallow confusing multiline expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.24.0 - * @see https://eslint.org/docs/latest/rules/no-unexpected-multiline - */ - "no-unexpected-multiline": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unmodified loop conditions. - * - * @since 2.0.0-alpha-2 - * @see https://eslint.org/docs/latest/rules/no-unmodified-loop-condition - */ - "no-unmodified-loop-condition": Linter.RuleEntry<[]>; - - /** - * Rule to disallow ternary operators when simpler alternatives exist. - * - * @since 0.21.0 - * @see https://eslint.org/docs/latest/rules/no-unneeded-ternary - */ - "no-unneeded-ternary": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - defaultAssignment: boolean; - }>, - ] - >; - - /** - * Rule to disallow unreachable code after `return`, `throw`, `continue`, and `break` statements. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/no-unreachable - */ - "no-unreachable": Linter.RuleEntry<[]>; - - /** - * Rule to disallow loops with a body that allows only one iteration. - * - * @since 7.3.0 - * @see https://eslint.org/docs/latest/rules/no-unreachable-loop - */ - "no-unreachable-loop": Linter.RuleEntry< - [ - Partial<{ - /** - * @default [] - */ - ignore: "WhileStatement" | "DoWhileStatement" | "ForStatement" | "ForInStatement" | "ForOfStatement"; - }>, - ] - >; - - /** - * Rule to disallow control flow statements in `finally` blocks. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 2.9.0 - * @see https://eslint.org/docs/latest/rules/no-unsafe-finally - */ - "no-unsafe-finally": Linter.RuleEntry<[]>; - - /** - * Rule to disallow negating the left operand of relational operators. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 3.3.0 - * @see https://eslint.org/docs/latest/rules/no-unsafe-negation - */ - "no-unsafe-negation": Linter.RuleEntry< - [ - Partial<{ - /** - * @since 6.6.0 - * @default false - */ - enforceForOrderingRelations: boolean; - }>, - ] - >; - - /** - * Rule to disallow use of optional chaining in contexts where the `undefined` value is not allowed. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 7.15.0 - * @see https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining - */ - "no-unsafe-optional-chaining": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - disallowArithmeticOperators: boolean; - }>, - ] - >; - - /** - * Rule to disallow unused expressions. - * - * @since 0.1.0 - * @see https://eslint.org/docs/latest/rules/no-unused-expressions - */ - "no-unused-expressions": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowShortCircuit: boolean; - /** - * @default false - */ - allowTernary: boolean; - /** - * @default false - */ - allowTaggedTemplates: boolean; - /** - * @since 7.20.0 - * @default false - */ - enforceForJSX: boolean; - }>, - ] - >; - - /** - * Rule to disallow unused labels. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 2.0.0-rc.0 - * @see https://eslint.org/docs/latest/rules/no-unused-labels - */ - "no-unused-labels": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unused private class members. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 8.1.0 - * @see https://eslint.org/docs/latest/rules/no-unused-private-class-members - */ - "no-unused-private-class-members": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unused variables. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-unused-vars - */ - "no-unused-vars": Linter.RuleEntry< - [ - | "all" - | "local" - | Partial<{ - /** - * @default 'all' - */ - vars: "all" | "local"; - varsIgnorePattern: string; - /** - * @default 'after-used' - */ - args: "after-used" | "all" | "none"; - /** - * @default false - */ - ignoreRestSiblings: boolean; - argsIgnorePattern: string; - /** - * @default 'all' - */ - caughtErrors: "none" | "all"; - caughtErrorsIgnorePattern: string; - destructuredArrayIgnorePattern: string; - /** - * @default false - */ - ignoreClassWithStaticInitBlock: boolean; - /** - * @default false - */ - reportUsedIgnorePattern: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of variables before they are defined. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/no-use-before-define - */ - "no-use-before-define": Linter.RuleEntry< - [ - | Partial<{ - /** - * @default true - */ - functions: boolean; - /** - * @default true - */ - classes: boolean; - /** - * @default true - */ - variables: boolean; - /** - * @default false - */ - allowNamedExports: boolean; - }> - | "nofunc", - ] - >; - - /** - * Rule to disallow variable assignments when the value is not used. - * - * @since 9.0.0-alpha.1 - * @see https://eslint.org/docs/latest/rules/no-useless-assignment - */ - "no-useless-assignment": Linter.RuleEntry<[]>; - - /** - * Rule to disallow useless backreferences in regular expressions. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 7.0.0-alpha.0 - * @see https://eslint.org/docs/latest/rules/no-useless-backreference - */ - "no-useless-backreference": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary calls to `.call()` and `.apply()`. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/no-useless-call - */ - "no-useless-call": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary `catch` clauses. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 5.11.0 - * @see https://eslint.org/docs/latest/rules/no-useless-catch - */ - "no-useless-catch": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary computed property keys in objects and classes. - * - * @since 2.9.0 - * @see https://eslint.org/docs/latest/rules/no-useless-computed-key - */ - "no-useless-computed-key": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - enforceForClassMembers: boolean; - }>, - ] - >; - - /** - * Rule to disallow unnecessary concatenation of literals or template literals. - * - * @since 1.3.0 - * @see https://eslint.org/docs/latest/rules/no-useless-concat - */ - "no-useless-concat": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary constructors. - * - * @since 2.0.0-beta.1 - * @see https://eslint.org/docs/latest/rules/no-useless-constructor - */ - "no-useless-constructor": Linter.RuleEntry<[]>; - - /** - * Rule to disallow unnecessary escape characters. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 2.5.0 - * @see https://eslint.org/docs/latest/rules/no-useless-escape - */ - "no-useless-escape": Linter.RuleEntry<[]>; - - /** - * Rule to disallow renaming import, export, and destructured assignments to the same name. - * - * @since 2.11.0 - * @see https://eslint.org/docs/latest/rules/no-useless-rename - */ - "no-useless-rename": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - ignoreImport: boolean; - /** - * @default false - */ - ignoreExport: boolean; - /** - * @default false - */ - ignoreDestructuring: boolean; - }>, - ] - >; - - /** - * Rule to disallow redundant return statements. - * - * @since 3.9.0 - * @see https://eslint.org/docs/latest/rules/no-useless-return - */ - "no-useless-return": Linter.RuleEntry<[]>; - - /** - * Rule to require `let` or `const` instead of `var`. - * - * @since 0.12.0 - * @see https://eslint.org/docs/latest/rules/no-var - */ - "no-var": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `void` operators. - * - * @since 0.8.0 - * @see https://eslint.org/docs/latest/rules/no-void - */ - "no-void": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowAsStatement: boolean; - }>, - ] - >; - - /** - * Rule to disallow specified warning terms in comments. - * - * @since 0.4.4 - * @see https://eslint.org/docs/latest/rules/no-warning-comments - */ - "no-warning-comments": Linter.RuleEntry< - [ - { - /** - * @default ["todo", "fixme", "xxx"] - */ - terms: string[]; - /** - * @default 'start' - */ - location: "start" | "anywhere"; - }, - ] - >; - - /** - * Rule to disallow whitespace before properties. - * - * @since 2.0.0-beta.1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`no-whitespace-before-property`](https://eslint.style/rules/js/no-whitespace-before-property) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/no-whitespace-before-property - */ - "no-whitespace-before-property": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `with` statements. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.2 - * @see https://eslint.org/docs/latest/rules/no-with - */ - "no-with": Linter.RuleEntry<[]>; - - /** - * Rule to enforce the location of single-line statements. - * - * @since 3.17.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`nonblock-statement-body-position`](https://eslint.style/rules/js/nonblock-statement-body-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/nonblock-statement-body-position - */ - "nonblock-statement-body-position": Linter.RuleEntry< - [ - "beside" | "below" | "any", - Partial<{ - overrides: Record; - }>, - ] - >; - - /** - * Rule to enforce consistent line breaks after opening and before closing braces. - * - * @since 2.12.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`object-curly-newline`](https://eslint.style/rules/js/object-curly-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/object-curly-newline - */ - "object-curly-newline": Linter.RuleEntry< - [ - | "always" - | "never" - | Partial<{ - /** - * @default false - */ - multiline: boolean; - minProperties: number; - /** - * @default true - */ - consistent: boolean; - }> - | Partial< - Record< - "ObjectExpression" | "ObjectPattern" | "ImportDeclaration" | "ExportDeclaration", - | "always" - | "never" - | Partial<{ - /** - * @default false - */ - multiline: boolean; - minProperties: number; - /** - * @default true - */ - consistent: boolean; - }> - > - >, - ] - >; - - /** - * Rule to enforce consistent spacing inside braces. - * - * @since 0.22.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`object-curly-spacing`](https://eslint.style/rules/js/object-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/object-curly-spacing - */ - "object-curly-spacing": - | Linter.RuleEntry< - [ - "never", - { - /** - * @default false - */ - arraysInObjects: boolean; - /** - * @default false - */ - objectsInObjects: boolean; - }, - ] - > - | Linter.RuleEntry< - [ - "always", - { - /** - * @default true - */ - arraysInObjects: boolean; - /** - * @default true - */ - objectsInObjects: boolean; - }, - ] - >; - - /** - * Rule to enforce placing object properties on separate lines. - * - * @since 2.10.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`object-property-newline`](https://eslint.style/rules/js/object-property-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/object-property-newline - */ - "object-property-newline": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowAllPropertiesOnSameLine: boolean; - }>, - ] - >; - - /** - * Rule to require or disallow method and property shorthand syntax for object literals. - * - * @since 0.20.0 - * @see https://eslint.org/docs/latest/rules/object-shorthand - */ - "object-shorthand": - | Linter.RuleEntry< - [ - "always" | "methods", - Partial<{ - /** - * @default false - */ - avoidQuotes: boolean; - /** - * @default false - */ - ignoreConstructors: boolean; - /** - * @since 8.22.0 - */ - methodsIgnorePattern: string; - /** - * @default false - */ - avoidExplicitReturnArrows: boolean; - }>, - ] - > - | Linter.RuleEntry< - [ - "properties", - Partial<{ - /** - * @default false - */ - avoidQuotes: boolean; - }>, - ] - > - | Linter.RuleEntry<["never" | "consistent" | "consistent-as-needed"]>; - - /** - * Rule to enforce variables to be declared either together or separately in functions. - * - * @since 0.0.9 - * @see https://eslint.org/docs/latest/rules/one-var - */ - "one-var": Linter.RuleEntry< - [ - | "always" - | "never" - | "consecutive" - | Partial< - { - /** - * @default false - */ - separateRequires: boolean; - } & Record<"var" | "let" | "const", "always" | "never" | "consecutive"> - > - | Partial>, - ] - >; - - /** - * Rule to require or disallow newlines around variable declarations. - * - * @since 2.0.0-beta.3 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`one-var-declaration-per-line`](https://eslint.style/rules/js/one-var-declaration-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/one-var-declaration-per-line - */ - "one-var-declaration-per-line": Linter.RuleEntry<["initializations" | "always"]>; - - /** - * Rule to require or disallow assignment operator shorthand where possible. - * - * @since 0.10.0 - * @see https://eslint.org/docs/latest/rules/operator-assignment - */ - "operator-assignment": Linter.RuleEntry<["always" | "never"]>; - - /** - * Rule to enforce consistent linebreak style for operators. - * - * @since 0.19.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`operator-linebreak`](https://eslint.style/rules/js/operator-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/operator-linebreak - */ - "operator-linebreak": Linter.RuleEntry< - [ - "after" | "before" | "none", - Partial<{ - overrides: Record; - }>, - ] - >; - - /** - * Rule to require or disallow padding within blocks. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`padded-blocks`](https://eslint.style/rules/js/padded-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/padded-blocks - */ - "padded-blocks": Linter.RuleEntry< - [ - "always" | "never" | Partial>, - { - /** - * @default false - */ - allowSingleLineBlocks: boolean; - }, - ] - >; - - /** - * Rule to require or disallow padding lines between statements. - * - * @since 4.0.0-beta.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/padding-line-between-statements - */ - "padding-line-between-statements": Linter.RuleEntry< - [ - ...Array< - { - blankLine: "any" | "never" | "always"; - } & Record<"prev" | "next", string | string[]> - >, - ] - >; - - /** - * Rule to require using arrow functions for callbacks. - * - * @since 1.2.0 - * @see https://eslint.org/docs/latest/rules/prefer-arrow-callback - */ - "prefer-arrow-callback": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowNamedFunctions: boolean; - /** - * @default true - */ - allowUnboundThis: boolean; - }>, - ] - >; - - /** - * Rule to require `const` declarations for variables that are never reassigned after declared. - * - * @since 0.23.0 - * @see https://eslint.org/docs/latest/rules/prefer-const - */ - "prefer-const": Linter.RuleEntry< - [ - Partial<{ - /** - * @default 'any' - */ - destructuring: "any" | "all"; - /** - * @default false - */ - ignoreReadBeforeAssign: boolean; - }>, - ] - >; - - /** - * Rule to require destructuring from arrays and/or objects. - * - * @since 3.13.0 - * @see https://eslint.org/docs/latest/rules/prefer-destructuring - */ - "prefer-destructuring": Linter.RuleEntry< - [ - Partial< - | { - VariableDeclarator: Partial<{ - array: boolean; - object: boolean; - }>; - AssignmentExpression: Partial<{ - array: boolean; - object: boolean; - }>; - } - | { - array: boolean; - object: boolean; - } - >, - Partial<{ - enforceForRenamedProperties: boolean; - }>, - ] - >; - - /** - * Rule to disallow the use of `Math.pow` in favor of the `**` operator. - * - * @since 6.7.0 - * @see https://eslint.org/docs/latest/rules/prefer-exponentiation-operator - */ - "prefer-exponentiation-operator": Linter.RuleEntry<[]>; - - /** - * Rule to enforce using named capture group in regular expression. - * - * @since 5.15.0 - * @see https://eslint.org/docs/latest/rules/prefer-named-capture-group - */ - "prefer-named-capture-group": Linter.RuleEntry<[]>; - - /** - * Rule to disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals. - * - * @since 3.5.0 - * @see https://eslint.org/docs/latest/rules/prefer-numeric-literals - */ - "prefer-numeric-literals": Linter.RuleEntry<[]>; - - /** - * Rule to disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`. - * - * @since 8.5.0 - * @see https://eslint.org/docs/latest/rules/prefer-object-has-own - */ - "prefer-object-has-own": Linter.RuleEntry<[]>; - - /** - * Rule to disallow using `Object.assign` with an object literal as the first argument and prefer the use of object spread instead. - * - * @since 5.0.0-alpha.3 - * @see https://eslint.org/docs/latest/rules/prefer-object-spread - */ - "prefer-object-spread": Linter.RuleEntry<[]>; - - /** - * Rule to require using Error objects as Promise rejection reasons. - * - * @since 3.14.0 - * @see https://eslint.org/docs/latest/rules/prefer-promise-reject-errors - */ - "prefer-promise-reject-errors": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - allowEmptyReject: boolean; - }>, - ] - >; - - /** - * Rule to require `Reflect` methods where applicable. - * - * @since 1.0.0-rc-2 - * @deprecated since 3.9.0. - * The original intention of this rule was misguided. - * @see https://eslint.org/docs/latest/rules/prefer-reflect - */ - "prefer-reflect": Linter.RuleEntry< - [ - Partial<{ - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to disallow use of the `RegExp` constructor in favor of regular expression literals. - * - * @since 6.4.0 - * @see https://eslint.org/docs/latest/rules/prefer-regex-literals - */ - "prefer-regex-literals": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - disallowRedundantWrapping: boolean; - }>, - ] - >; - - /** - * Rule to require rest parameters instead of `arguments`. - * - * @since 2.0.0-alpha-1 - * @see https://eslint.org/docs/latest/rules/prefer-rest-params - */ - "prefer-rest-params": Linter.RuleEntry<[]>; - - /** - * Rule to require spread operators instead of `.apply()`. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/prefer-spread - */ - "prefer-spread": Linter.RuleEntry<[]>; - - /** - * Rule to require template literals instead of string concatenation. - * - * @since 1.2.0 - * @see https://eslint.org/docs/latest/rules/prefer-template - */ - "prefer-template": Linter.RuleEntry<[]>; - - /** - * Rule to require quotes around object literal property names. - * - * @since 0.0.6 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`quote-props`](https://eslint.style/rules/js/quote-props) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/quote-props - */ - "quote-props": - | Linter.RuleEntry<["always" | "consistent"]> - | Linter.RuleEntry< - [ - "as-needed", - Partial<{ - /** - * @default false - */ - keywords: boolean; - /** - * @default true - */ - unnecessary: boolean; - /** - * @default false - */ - numbers: boolean; - }>, - ] - > - | Linter.RuleEntry< - [ - "consistent-as-needed", - Partial<{ - /** - * @default false - */ - keywords: boolean; - }>, - ] - >; - - /** - * Rule to enforce the consistent use of either backticks, double, or single quotes. - * - * @since 0.0.7 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`quotes`](https://eslint.style/rules/js/quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/quotes - */ - quotes: Linter.RuleEntry< - [ - "double" | "single" | "backtick", - Partial<{ - /** - * @default false - */ - avoidEscape: boolean; - /** - * @default false - */ - allowTemplateLiterals: boolean; - }>, - ] - >; - - /** - * Rule to enforce the consistent use of the radix argument when using `parseInt()`. - * - * @since 0.0.7 - * @see https://eslint.org/docs/latest/rules/radix - */ - radix: Linter.RuleEntry<["always" | "as-needed"]>; - - /** - * Rule to disallow assignments that can lead to race conditions due to usage of `await` or `yield`. - * - * @since 5.3.0 - * @see https://eslint.org/docs/latest/rules/require-atomic-updates - */ - "require-atomic-updates": Linter.RuleEntry< - [ - Partial<{ - /** - * @since 8.3.0 - * @default false - */ - allowProperties: boolean; - }>, - ] - >; - - /** - * Rule to disallow async functions which have no `await` expression. - * - * @since 3.11.0 - * @see https://eslint.org/docs/latest/rules/require-await - */ - "require-await": Linter.RuleEntry<[]>; - - /** - * Rule to enforce the use of `u` or `v` flag on regular expressions. - * - * @since 5.3.0 - * @see https://eslint.org/docs/latest/rules/require-unicode-regexp - */ - "require-unicode-regexp": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - requireFlag: "u" | "v"; - }>, - ] - >; - - /** - * Rule to require generator functions to contain `yield`. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 1.0.0-rc-1 - * @see https://eslint.org/docs/latest/rules/require-yield - */ - "require-yield": Linter.RuleEntry<[]>; - - /** - * Rule to enforce spacing between rest and spread operators and their expressions. - * - * @since 2.12.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`rest-spread-spacing`](https://eslint.style/rules/js/rest-spread-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/rest-spread-spacing - */ - "rest-spread-spacing": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require or disallow semicolons instead of ASI. - * - * @since 0.0.6 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`semi`](https://eslint.style/rules/js/semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/semi - */ - semi: - | Linter.RuleEntry< - [ - "always", - Partial<{ - /** - * @default false - */ - omitLastInOneLineBlock: boolean; - }>, - ] - > - | Linter.RuleEntry< - [ - "never", - Partial<{ - /** - * @default 'any' - */ - beforeStatementContinuationChars: "any" | "always" | "never"; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing before and after semicolons. - * - * @since 0.16.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`semi-spacing`](https://eslint.style/rules/js/semi-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/semi-spacing - */ - "semi-spacing": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - before: boolean; - /** - * @default true - */ - after: boolean; - }>, - ] - >; - - /** - * Rule to enforce location of semicolons. - * - * @since 4.0.0-beta.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`semi-style`](https://eslint.style/rules/js/semi-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/semi-style - */ - "semi-style": Linter.RuleEntry<["last" | "first"]>; - - /** - * Rule to enforce sorted `import` declarations within modules. - * - * @since 2.0.0-beta.1 - * @see https://eslint.org/docs/latest/rules/sort-imports - */ - "sort-imports": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - ignoreCase: boolean; - /** - * @default false - */ - ignoreDeclarationSort: boolean; - /** - * @default false - */ - ignoreMemberSort: boolean; - /** - * @default ['none', 'all', 'multiple', 'single'] - */ - memberSyntaxSortOrder: Array<"none" | "all" | "multiple" | "single">; - /** - * @default false - */ - allowSeparatedGroups: boolean; - }>, - ] - >; - - /** - * Rule to require object keys to be sorted. - * - * @since 3.3.0 - * @see https://eslint.org/docs/latest/rules/sort-keys - */ - "sort-keys": Linter.RuleEntry< - [ - "asc" | "desc", - Partial<{ - /** - * @default true - */ - caseSensitive: boolean; - /** - * @default 2 - */ - minKeys: number; - /** - * @default false - */ - natural: boolean; - /** - * @default false - */ - allowLineSeparatedGroups: boolean; - /** - * @default false - */ - ignoreComputedKeys: boolean; - }>, - ] - >; - - /** - * Rule to require variables within the same declaration block to be sorted. - * - * @since 0.2.0 - * @see https://eslint.org/docs/latest/rules/sort-vars - */ - "sort-vars": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - ignoreCase: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing before blocks. - * - * @since 0.9.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`space-before-blocks`](https://eslint.style/rules/js/space-before-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/space-before-blocks - */ - "space-before-blocks": Linter.RuleEntry< - ["always" | "never" | Partial>] - >; - - /** - * Rule to enforce consistent spacing before `function` definition opening parenthesis. - * - * @since 0.18.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`space-before-function-paren`](https://eslint.style/rules/js/space-before-function-paren) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/space-before-function-paren - */ - "space-before-function-paren": Linter.RuleEntry< - ["always" | "never" | Partial>] - >; - - /** - * Rule to enforce consistent spacing inside parentheses. - * - * @since 0.8.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`space-in-parens`](https://eslint.style/rules/js/space-in-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/space-in-parens - */ - "space-in-parens": Linter.RuleEntry< - [ - "never" | "always", - Partial<{ - exceptions: string[]; - }>, - ] - >; - - /** - * Rule to require spacing around infix operators. - * - * @since 0.2.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`space-infix-ops`](https://eslint.style/rules/js/space-infix-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/space-infix-ops - */ - "space-infix-ops": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - int32Hint: boolean; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing before or after unary operators. - * - * @since 0.10.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`space-unary-ops`](https://eslint.style/rules/js/space-unary-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/space-unary-ops - */ - "space-unary-ops": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - words: boolean; - /** - * @default false - */ - nonwords: boolean; - overrides: Record; - }>, - ] - >; - - /** - * Rule to enforce consistent spacing after the `//` or `/*` in a comment. - * - * @since 0.23.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`spaced-comment`](https://eslint.style/rules/js/spaced-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/spaced-comment - */ - "spaced-comment": Linter.RuleEntry< - [ - "always" | "never", - { - exceptions: string[]; - markers: string[]; - line: { - exceptions: string[]; - markers: string[]; - }; - block: { - exceptions: string[]; - markers: string[]; - /** - * @default false - */ - balanced: boolean; - }; - }, - ] - >; - - /** - * Rule to require or disallow strict mode directives. - * - * @since 0.1.0 - * @see https://eslint.org/docs/latest/rules/strict - */ - strict: Linter.RuleEntry<["safe" | "global" | "function" | "never"]>; - - /** - * Rule to enforce spacing around colons of switch statements. - * - * @since 4.0.0-beta.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`switch-colon-spacing`](https://eslint.style/rules/js/switch-colon-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/switch-colon-spacing - */ - "switch-colon-spacing": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - before: boolean; - /** - * @default true - */ - after: boolean; - }>, - ] - >; - - /** - * Rule to require symbol descriptions. - * - * @since 3.4.0 - * @see https://eslint.org/docs/latest/rules/symbol-description - */ - "symbol-description": Linter.RuleEntry<[]>; - - /** - * Rule to require or disallow spacing around embedded expressions of template strings. - * - * @since 2.0.0-rc.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`template-curly-spacing`](https://eslint.style/rules/js/template-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/template-curly-spacing - */ - "template-curly-spacing": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require or disallow spacing between template tags and their literals. - * - * @since 3.15.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`template-tag-spacing`](https://eslint.style/rules/js/template-tag-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/template-tag-spacing - */ - "template-tag-spacing": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require or disallow Unicode byte order mark (BOM). - * - * @since 2.11.0 - * @see https://eslint.org/docs/latest/rules/unicode-bom - */ - "unicode-bom": Linter.RuleEntry<["never" | "always"]>; - - /** - * Rule to require calls to `isNaN()` when checking for `NaN`. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.0.6 - * @see https://eslint.org/docs/latest/rules/use-isnan - */ - "use-isnan": Linter.RuleEntry< - [ - Partial<{ - /** - * @default true - */ - enforceForSwitchCase: boolean; - /** - * @default true - */ - enforceForIndexOf: boolean; - }>, - ] - >; - - /** - * Rule to enforce comparing `typeof` expressions against valid strings. - * - * @remarks - * Recommended by ESLint, the rule was enabled in `eslint:recommended`. - * - * @since 0.5.0 - * @see https://eslint.org/docs/latest/rules/valid-typeof - */ - "valid-typeof": Linter.RuleEntry< - [ - Partial<{ - /** - * @default false - */ - requireStringLiterals: boolean; - }>, - ] - >; - - /** - * Rule to require `var` declarations be placed at the top of their containing scope. - * - * @since 0.8.0 - * @see https://eslint.org/docs/latest/rules/vars-on-top - */ - "vars-on-top": Linter.RuleEntry<[]>; - - /** - * Rule to require parentheses around immediate `function` invocations. - * - * @since 0.0.9 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`wrap-iife`](https://eslint.style/rules/js/wrap-iife) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/wrap-iife - */ - "wrap-iife": Linter.RuleEntry< - [ - "outside" | "inside" | "any", - Partial<{ - /** - * @default false - */ - functionPrototypeMethods: boolean; - }>, - ] - >; - - /** - * Rule to require parenthesis around regex literals. - * - * @since 0.1.0 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`wrap-regex`](https://eslint.style/rules/js/wrap-regex) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/wrap-regex - */ - "wrap-regex": Linter.RuleEntry<[]>; - - /** - * Rule to require or disallow spacing around the `*` in `yield*` expressions. - * - * @since 2.0.0-alpha-1 - * @deprecated since 8.53.0. - * Formatting rules are being moved out of ESLint core. - * Please, use [`yield-star-spacing`](https://eslint.style/rules/js/yield-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - * @see https://eslint.org/docs/latest/rules/yield-star-spacing - */ - "yield-star-spacing": Linter.RuleEntry< - [ - | Partial<{ - before: boolean; - after: boolean; - }> - | "before" - | "after" - | "both" - | "neither", - ] - >; - - /** - * Rule to require or disallow "Yoda" conditions. - * - * @since 0.7.1 - * @see https://eslint.org/docs/latest/rules/yoda - */ - yoda: - | Linter.RuleEntry< - [ - "never", - Partial<{ - exceptRange: boolean; - onlyEquality: boolean; - }>, - ] - > - | Linter.RuleEntry<["always"]>; + /** + * Rule to enforce getter and setter pairs in objects and classes. + * + * @since 0.22.0 + * @see https://eslint.org/docs/latest/rules/accessor-pairs + */ + "accessor-pairs": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + setWithoutGet: boolean; + /** + * @default false + */ + getWithoutSet: boolean; + /** + * @default true + */ + enforceForClassMembers: boolean; + }>, + ] + >; + + /** + * Rule to enforce linebreaks after opening and before closing array brackets. + * + * @since 4.0.0-alpha.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`array-bracket-newline`](https://eslint.style/rules/js/array-bracket-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/array-bracket-newline + */ + "array-bracket-newline": Linter.RuleEntry< + [ + | "always" + | "never" + | "consistent" + | Partial<{ + /** + * @default true + */ + multiline: boolean; + /** + * @default null + */ + minItems: number | null; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing inside array brackets. + * + * @since 0.24.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`array-bracket-spacing`](https://eslint.style/rules/js/array-bracket-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/array-bracket-spacing + */ + "array-bracket-spacing": + | Linter.RuleEntry< + [ + "never", + Partial<{ + /** + * @default false + */ + singleValue: boolean; + /** + * @default false + */ + objectsInArrays: boolean; + /** + * @default false + */ + arraysInArrays: boolean; + }>, + ] + > + | Linter.RuleEntry< + [ + "always", + Partial<{ + /** + * @default true + */ + singleValue: boolean; + /** + * @default true + */ + objectsInArrays: boolean; + /** + * @default true + */ + arraysInArrays: boolean; + }>, + ] + >; + + /** + * Rule to enforce `return` statements in callbacks of array methods. + * + * @since 2.0.0-alpha-1 + * @see https://eslint.org/docs/latest/rules/array-callback-return + */ + "array-callback-return": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowImplicit: boolean; + /** + * @default false + */ + checkForEach: boolean; + /** + * @default false + */ + allowVoid: boolean; + }>, + ] + >; + + /** + * Rule to enforce line breaks after each array element. + * + * @since 4.0.0-rc.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`array-element-newline`](https://eslint.style/rules/js/array-element-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/array-element-newline + */ + "array-element-newline": Linter.RuleEntry< + [ + | "always" + | "never" + | "consistent" + | Partial<{ + /** + * @default true + */ + multiline: boolean; + /** + * @default null + */ + minItems: number | null; + }>, + ] + >; + + /** + * Rule to require braces around arrow function bodies. + * + * @since 1.8.0 + * @see https://eslint.org/docs/latest/rules/arrow-body-style + */ + "arrow-body-style": + | Linter.RuleEntry< + [ + "as-needed", + Partial<{ + /** + * @default false + */ + requireReturnForObjectLiteral: boolean; + }>, + ] + > + | Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to require parentheses around arrow function arguments. + * + * @since 1.0.0-rc-1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`arrow-parens`](https://eslint.style/rules/js/arrow-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/arrow-parens + */ + "arrow-parens": + | Linter.RuleEntry<["always"]> + | Linter.RuleEntry< + [ + "as-needed", + Partial<{ + /** + * @default false + */ + requireForBlockBody: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing before and after the arrow in arrow functions. + * + * @since 1.0.0-rc-1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`arrow-spacing`](https://eslint.style/rules/js/arrow-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/arrow-spacing + */ + "arrow-spacing": Linter.RuleEntry<[]>; + + /** + * Rule to enforce the use of variables within the scope they are defined. + * + * @since 0.1.0 + * @see https://eslint.org/docs/latest/rules/block-scoped-var + */ + "block-scoped-var": Linter.RuleEntry<[]>; + + /** + * Rule to disallow or enforce spaces inside of blocks after opening block and before closing block. + * + * @since 1.2.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`block-spacing`](https://eslint.style/rules/js/block-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/block-spacing + */ + "block-spacing": Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to enforce consistent brace style for blocks. + * + * @since 0.0.7 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`brace-style`](https://eslint.style/rules/js/brace-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/brace-style + */ + "brace-style": Linter.RuleEntry< + [ + "1tbs" | "stroustrup" | "allman", + Partial<{ + /** + * @default false + */ + allowSingleLine: boolean; + }>, + ] + >; + + /** + * Rule to require `return` statements after callbacks. + * + * @since 1.0.0-rc-1 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`callback-return`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/callback-return.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/callback-return + */ + "callback-return": Linter.RuleEntry<[string[]]>; + + /** + * Rule to enforce camelcase naming convention. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/camelcase + */ + camelcase: Linter.RuleEntry< + [ + Partial<{ + /** + * @default 'always' + */ + properties: "always" | "never"; + /** + * @default false + */ + ignoreDestructuring: boolean; + /** + * @since 6.7.0 + * @default false + */ + ignoreImports: boolean; + /** + * @since 7.4.0 + * @default false + */ + ignoreGlobals: boolean; + /** + * @remarks + * Also accept for regular expression patterns + */ + allow: string[]; + }>, + ] + >; + + /** + * Rule to enforce or disallow capitalization of the first letter of a comment. + * + * @since 3.11.0 + * @see https://eslint.org/docs/latest/rules/capitalized-comments + */ + "capitalized-comments": Linter.RuleEntry< + [ + "always" | "never", + Partial<{ + ignorePattern: string; + /** + * @default false + */ + ignoreInlineComments: boolean; + /** + * @default false + */ + ignoreConsecutiveComments: boolean; + }>, + ] + >; + + /** + * Rule to enforce that class methods utilize `this`. + * + * @since 3.4.0 + * @see https://eslint.org/docs/latest/rules/class-methods-use-this + */ + "class-methods-use-this": Linter.RuleEntry< + [ + Partial<{ + exceptMethods: string[]; + }>, + ] + >; + + /** + * Rule to require or disallow trailing commas. + * + * @since 0.16.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`comma-dangle`](https://eslint.style/rules/js/comma-dangle) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/comma-dangle + */ + "comma-dangle": Linter.RuleEntry< + [ + | "never" + | "always" + | "always-multiline" + | "only-multiline" + | Partial<{ + /** + * @default 'never' + */ + arrays: + | "never" + | "always" + | "always-multiline" + | "only-multiline"; + /** + * @default 'never' + */ + objects: + | "never" + | "always" + | "always-multiline" + | "only-multiline"; + /** + * @default 'never' + */ + imports: + | "never" + | "always" + | "always-multiline" + | "only-multiline"; + /** + * @default 'never' + */ + exports: + | "never" + | "always" + | "always-multiline" + | "only-multiline"; + /** + * @default 'never' + */ + functions: + | "never" + | "always" + | "always-multiline" + | "only-multiline"; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing before and after commas. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`comma-spacing`](https://eslint.style/rules/js/comma-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/comma-spacing + */ + "comma-spacing": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + before: boolean; + /** + * @default true + */ + after: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent comma style. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`comma-style`](https://eslint.style/rules/js/comma-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/comma-style + */ + "comma-style": Linter.RuleEntry< + [ + "last" | "first", + Partial<{ + exceptions: Record; + }>, + ] + >; + + /** + * Rule to enforce a maximum cyclomatic complexity allowed in a program. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/complexity + */ + complexity: Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 20 + */ + max: number; + /** + * @deprecated + * @default 20 + */ + maximum: number; + /** + * @default "classic" + * @since 9.12.0 + */ + variant: "classic" | "modified"; + }> + | number, + ] + >; + + /** + * Rule to enforce consistent spacing inside computed property brackets. + * + * @since 0.23.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`computed-property-spacing`](https://eslint.style/rules/js/computed-property-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/computed-property-spacing + */ + "computed-property-spacing": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require `return` statements to either always or never specify values. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/consistent-return + */ + "consistent-return": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + treatUndefinedAsUnspecified: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent naming when capturing the current execution context. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/consistent-this + */ + "consistent-this": Linter.RuleEntry<[...string[]]>; + + /** + * Rule to require `super()` calls in constructors. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.24.0 + * @see https://eslint.org/docs/latest/rules/constructor-super + */ + "constructor-super": Linter.RuleEntry<[]>; + + /** + * Rule to enforce consistent brace style for all control statements. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/curly + */ + curly: Linter.RuleEntry< + ["all" | "multi" | "multi-line" | "multi-or-nest" | "consistent"] + >; + + /** + * Rule to require `default` cases in `switch` statements. + * + * @since 0.6.0 + * @see https://eslint.org/docs/latest/rules/default-case + */ + "default-case": Linter.RuleEntry< + [ + Partial<{ + /** + * @default '^no default$' + */ + commentPattern: string; + }>, + ] + >; + + /** + * Rule to enforce `default` clauses in `switch` statements to be last. + * + * @since 7.0.0-alpha.0 + * @see https://eslint.org/docs/latest/rules/default-case-last + */ + "default-case-last": Linter.RuleEntry<[]>; + + /** + * Rule to enforce default parameters to be last. + * + * @since 6.4.0 + * @see https://eslint.org/docs/latest/rules/default-param-last + */ + "default-param-last": Linter.RuleEntry<[]>; + + /** + * Rule to enforce consistent newlines before and after dots. + * + * @since 0.21.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`dot-location`](https://eslint.style/rules/js/dot-location) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/dot-location + */ + "dot-location": Linter.RuleEntry<["object" | "property"]>; + + /** + * Rule to enforce dot notation whenever possible. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/dot-notation + */ + "dot-notation": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + allowKeywords: boolean; + allowPattern: string; + }>, + ] + >; + + /** + * Rule to require or disallow newline at the end of files. + * + * @since 0.7.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`eol-last`](https://eslint.style/rules/js/eol-last) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/eol-last + */ + "eol-last": Linter.RuleEntry< + [ + "always" | "never", // | 'unix' | 'windows' + ] + >; + + /** + * Rule to require the use of `===` and `!==`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/eqeqeq + */ + eqeqeq: + | Linter.RuleEntry< + [ + "always", + Partial<{ + /** + * @default 'always' + */ + null: "always" | "never" | "ignore"; + }>, + ] + > + | Linter.RuleEntry<["smart" | "allow-null"]>; + + /** + * Rule to enforce `for` loop update clause moving the counter in the right direction. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 4.0.0-beta.0 + * @see https://eslint.org/docs/latest/rules/for-direction + */ + "for-direction": Linter.RuleEntry<[]>; + + /** + * Rule to require or disallow spacing between function identifiers and their invocations. + * + * @since 3.3.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`function-call-spacing`](https://eslint.style/rules/js/function-call-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/func-call-spacing + */ + "func-call-spacing": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require function names to match the name of the variable or property to which they are assigned. + * + * @since 3.8.0 + * @see https://eslint.org/docs/latest/rules/func-name-matching + */ + "func-name-matching": + | Linter.RuleEntry< + [ + "always" | "never", + Partial<{ + /** + * @default false + */ + considerPropertyDescriptor: boolean; + /** + * @default false + */ + includeCommonJSModuleExports: boolean; + }>, + ] + > + | Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + considerPropertyDescriptor: boolean; + /** + * @default false + */ + includeCommonJSModuleExports: boolean; + }>, + ] + >; + + /** + * Rule to require or disallow named `function` expressions. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/func-names + */ + "func-names": Linter.RuleEntry< + [ + "always" | "as-needed" | "never", + Partial<{ + generators: "always" | "as-needed" | "never"; + }>, + ] + >; + + /** + * Rule to enforce the consistent use of either `function` declarations or expressions assigned to variables. + * + * @since 0.2.0 + * @see https://eslint.org/docs/latest/rules/func-style + */ + "func-style": Linter.RuleEntry< + [ + "expression" | "declaration", + Partial<{ + /** + * @default false + */ + allowArrowFunctions: boolean; + overrides: { + namedExports: "declaration" | "expression" | "ignore"; + }; + }>, + ] + >; + + /** + * Rule to enforce line breaks between arguments of a function call. + * + * @since 6.2.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`function-call-argument-newline`](https://eslint.style/rules/js/function-call-argument-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/function-call-argument-newline + */ + "function-call-argument-newline": Linter.RuleEntry< + [ + /** + * @default "always" + */ + "always" | "never" | "consistent", + ] + >; + + /** + * Rule to enforce consistent line breaks inside function parentheses. + * + * @since 4.6.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`function-paren-newline`](https://eslint.style/rules/js/function-paren-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/function-paren-newline + */ + "function-paren-newline": Linter.RuleEntry< + [ + | "always" + | "never" + | "multiline" + | "multiline-arguments" + | "consistent" + | Partial<{ + minItems: number; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing around `*` operators in generator functions. + * + * @since 0.17.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`generator-star-spacing`](https://eslint.style/rules/js/generator-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/generator-star-spacing + */ + "generator-star-spacing": Linter.RuleEntry< + [ + | Partial<{ + before: boolean; + after: boolean; + named: + | Partial<{ + before: boolean; + after: boolean; + }> + | "before" + | "after" + | "both" + | "neither"; + anonymous: + | Partial<{ + before: boolean; + after: boolean; + }> + | "before" + | "after" + | "both" + | "neither"; + method: + | Partial<{ + before: boolean; + after: boolean; + }> + | "before" + | "after" + | "both" + | "neither"; + }> + | "before" + | "after" + | "both" + | "neither", + ] + >; + + /** + * Rule to enforce `return` statements in getters. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 4.2.0 + * @see https://eslint.org/docs/latest/rules/getter-return + */ + "getter-return": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowImplicit: boolean; + }>, + ] + >; + + /** + * Rule to require `require()` calls to be placed at top-level module scope. + * + * @since 1.4.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`global-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/global-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/global-require + */ + "global-require": Linter.RuleEntry<[]>; + + /** + * Rule to require grouped accessor pairs in object literals and classes. + * + * @since 6.7.0 + * @see https://eslint.org/docs/latest/rules/grouped-accessor-pairs + */ + "grouped-accessor-pairs": Linter.RuleEntry< + ["anyOrder" | "getBeforeSet" | "setBeforeGet"] + >; + + /** + * Rule to require `for-in` loops to include an `if` statement. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/guard-for-in + */ + "guard-for-in": Linter.RuleEntry<[]>; + + /** + * Rule to require error handling in callbacks. + * + * @since 0.4.5 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`handle-callback-err`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/handle-callback-err.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/handle-callback-err + */ + "handle-callback-err": Linter.RuleEntry<[string]>; + + /** + * Rule to disallow specified identifiers. + * + * @since 2.0.0-beta.2 + * @deprecated since 7.5.0. + * The rule was renamed. + * Please, use [`id-denylist`](https://eslint.org/docs/rules/id-denylist). + * @see https://eslint.org/docs/latest/rules/id-blacklist + */ + "id-blacklist": Linter.RuleEntry<[...string[]]>; + + /** + * Rule to disallow specified identifiers. + * + * @since 7.4.0 + * @see https://eslint.org/docs/latest/rules/id-denylist + */ + "id-denylist": Linter.RuleEntry; + + /** + * Rule to enforce minimum and maximum identifier lengths. + * + * @since 1.0.0 + * @see https://eslint.org/docs/latest/rules/id-length + */ + "id-length": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 2 + */ + min: number; + /** + * @default Infinity + */ + max: number; + /** + * @default 'always' + */ + properties: "always" | "never"; + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to require identifiers to match a specified regular expression. + * + * @since 1.0.0 + * @see https://eslint.org/docs/latest/rules/id-match + */ + "id-match": Linter.RuleEntry< + [ + string, + Partial<{ + /** + * @default false + */ + properties: boolean; + /** + * @default false + */ + onlyDeclarations: boolean; + /** + * @default false + */ + ignoreDestructuring: boolean; + }>, + ] + >; + + /** + * Rule to enforce the location of arrow function bodies. + * + * @since 4.12.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`implicit-arrow-linebreak`](https://eslint.style/rules/js/implicit-arrow-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/implicit-arrow-linebreak + */ + "implicit-arrow-linebreak": Linter.RuleEntry<["beside" | "below"]>; + + /** + * Rule to enforce consistent indentation. + * + * @since 0.14.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`indent`](https://eslint.style/rules/js/indent) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/indent + */ + indent: Linter.RuleEntry< + [ + number | "tab", + Partial<{ + /** + * @default 0 + */ + SwitchCase: number; + /** + * @default 1 + */ + VariableDeclarator: + | Partial<{ + /** + * @default 1 + */ + var: number | "first"; + /** + * @default 1 + */ + let: number | "first"; + /** + * @default 1 + */ + const: number | "first"; + }> + | number + | "first"; + /** + * @default 1 + */ + outerIIFEBody: number; + /** + * @default 1 + */ + MemberExpression: number | "off"; + /** + * @default { parameters: 1, body: 1 } + */ + FunctionDeclaration: Partial<{ + /** + * @default 1 + */ + parameters: number | "first" | "off"; + /** + * @default 1 + */ + body: number; + }>; + /** + * @default { parameters: 1, body: 1 } + */ + FunctionExpression: Partial<{ + /** + * @default 1 + */ + parameters: number | "first" | "off"; + /** + * @default 1 + */ + body: number; + }>; + /** + * @default { arguments: 1 } + */ + CallExpression: Partial<{ + /** + * @default 1 + */ + arguments: number | "first" | "off"; + }>; + /** + * @default 1 + */ + ArrayExpression: number | "first" | "off"; + /** + * @default 1 + */ + ObjectExpression: number | "first" | "off"; + /** + * @default 1 + */ + ImportDeclaration: number | "first" | "off"; + /** + * @default false + */ + flatTernaryExpressions: boolean; + ignoredNodes: string[]; + /** + * @default false + */ + ignoreComments: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent indentation. + * + * @since 4.0.0-alpha.0 + * @deprecated since 4.0.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`indent`](https://eslint.style/rules/js/indent) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/indent-legacy + */ + "indent-legacy": Linter.RuleEntry< + [ + number | "tab", + Partial<{ + /** + * @default 0 + */ + SwitchCase: number; + /** + * @default 1 + */ + VariableDeclarator: + | Partial<{ + /** + * @default 1 + */ + var: number | "first"; + /** + * @default 1 + */ + let: number | "first"; + /** + * @default 1 + */ + const: number | "first"; + }> + | number + | "first"; + /** + * @default 1 + */ + outerIIFEBody: number; + /** + * @default 1 + */ + MemberExpression: number | "off"; + /** + * @default { parameters: 1, body: 1 } + */ + FunctionDeclaration: Partial<{ + /** + * @default 1 + */ + parameters: number | "first" | "off"; + /** + * @default 1 + */ + body: number; + }>; + /** + * @default { parameters: 1, body: 1 } + */ + FunctionExpression: Partial<{ + /** + * @default 1 + */ + parameters: number | "first" | "off"; + /** + * @default 1 + */ + body: number; + }>; + /** + * @default { arguments: 1 } + */ + CallExpression: Partial<{ + /** + * @default 1 + */ + arguments: number | "first" | "off"; + }>; + /** + * @default 1 + */ + ArrayExpression: number | "first" | "off"; + /** + * @default 1 + */ + ObjectExpression: number | "first" | "off"; + /** + * @default 1 + */ + ImportDeclaration: number | "first" | "off"; + /** + * @default false + */ + flatTernaryExpressions: boolean; + ignoredNodes: string[]; + /** + * @default false + */ + ignoreComments: boolean; + }>, + ] + >; + + /** + * Rule to require or disallow initialization in variable declarations. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/init-declarations + */ + "init-declarations": + | Linter.RuleEntry<["always"]> + | Linter.RuleEntry< + [ + "never", + Partial<{ + ignoreForLoopInit: boolean; + }>, + ] + >; + + /** + * Rule to enforce the consistent use of either double or single quotes in JSX attributes. + * + * @since 1.4.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`jsx-quotes`](https://eslint.style/rules/js/jsx-quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/jsx-quotes + */ + "jsx-quotes": Linter.RuleEntry<["prefer-double" | "prefer-single"]>; + + /** + * Rule to enforce consistent spacing between keys and values in object literal properties. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`key-spacing`](https://eslint.style/rules/js/key-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/key-spacing + */ + "key-spacing": Linter.RuleEntry< + [ + | Partial< + | { + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + align: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'colon' + */ + on: "value" | "colon"; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }> + | "value" + | "colon"; + } + | { + singleLine?: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }> + | undefined; + multiLine?: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + align: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'colon' + */ + on: "value" | "colon"; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }> + | "value" + | "colon"; + }> + | undefined; + } + > + | { + align: Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'colon' + */ + on: "value" | "colon"; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }>; + singleLine?: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }> + | undefined; + multiLine?: + | Partial<{ + /** + * @default false + */ + beforeColon: boolean; + /** + * @default true + */ + afterColon: boolean; + /** + * @default 'strict' + */ + mode: "strict" | "minimum"; + }> + | undefined; + }, + ] + >; + + /** + * Rule to enforce consistent spacing before and after keywords. + * + * @since 2.0.0-beta.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`keyword-spacing`](https://eslint.style/rules/js/keyword-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/keyword-spacing + */ + "keyword-spacing": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + before: boolean; + /** + * @default true + */ + after: boolean; + overrides: Record< + string, + Partial<{ + before: boolean; + after: boolean; + }> + >; + }>, + ] + >; + + /** + * Rule to enforce position of line comments. + * + * @since 3.5.0 + * @deprecated since 9.3.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`line-comment-position`](https://eslint.style/rules/js/line-comment-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/line-comment-position + */ + "line-comment-position": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 'above' + */ + position: "above" | "beside"; + ignorePattern: string; + /** + * @default true + */ + applyDefaultIgnorePatterns: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent linebreak style. + * + * @since 0.21.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`linebreak-style`](https://eslint.style/rules/js/linebreak-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/linebreak-style + */ + "linebreak-style": Linter.RuleEntry<["unix" | "windows"]>; + + /** + * Rule to require empty lines around comments. + * + * @since 0.22.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`lines-around-comment`](https://eslint.style/rules/js/lines-around-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/lines-around-comment + */ + "lines-around-comment": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + beforeBlockComment: boolean; + /** + * @default false + */ + afterBlockComment: boolean; + /** + * @default false + */ + beforeLineComment: boolean; + /** + * @default false + */ + afterLineComment: boolean; + /** + * @default false + */ + allowBlockStart: boolean; + /** + * @default false + */ + allowBlockEnd: boolean; + /** + * @default false + */ + allowObjectStart: boolean; + /** + * @default false + */ + allowObjectEnd: boolean; + /** + * @default false + */ + allowArrayStart: boolean; + /** + * @default false + */ + allowArrayEnd: boolean; + /** + * @default false + */ + allowClassStart: boolean; + /** + * @default false + */ + allowClassEnd: boolean; + ignorePattern: string; + /** + * @default true + */ + applyDefaultIgnorePatterns: boolean; + }>, + ] + >; + + /** + * Rule to require or disallow newlines around directives. + * + * @since 3.5.0 + * @deprecated since 4.0.0. + * The rule was replaced with a more general rule. + * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/lines-around-directive + */ + "lines-around-directive": Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to require or disallow an empty line between class members. + * + * @since 4.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`lines-between-class-members`](https://eslint.style/rules/js/lines-between-class-members) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/lines-between-class-members + */ + "lines-between-class-members": Linter.RuleEntry< + [ + ( + | "always" + | "never" + | { + enforce: Array<{ + blankLine: "always" | "never"; + prev: "method" | "field" | "*"; + next: "method" | "field" | "*"; + }>; + } + ), + Partial<{ + /** + * @default false + */ + exceptAfterSingleLine: boolean; + }>, + ] + >; + + /** + * Rule to require or disallow logical assignment operator shorthand. + * + * @since 8.24.0 + * @see https://eslint.org/docs/latest/rules/logical-assignment-operators + */ + "logical-assignment-operators": + | Linter.RuleEntry< + [ + "always", + Partial<{ + /** + * @default false + */ + enforceForIfStatements: boolean; + }>, + ] + > + | Linter.RuleEntry<["never"]>; + + /** + * Rule to enforce a maximum number of classes per file. + * + * @since 5.0.0-alpha.3 + * @see https://eslint.org/docs/latest/rules/max-classes-per-file + */ + "max-classes-per-file": Linter.RuleEntry<[number]>; + + /** + * Rule to enforce a maximum depth that blocks can be nested. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/max-depth + */ + "max-depth": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 4 + */ + max: number; + }>, + ] + >; + + /** + * Rule to enforce a maximum line length. + * + * @since 0.0.9 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`max-len`](https://eslint.style/rules/js/max-len) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/max-len + */ + "max-len": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 80 + */ + code: number; + /** + * @default 4 + */ + tabWidth: number; + comments: number; + ignorePattern: string; + /** + * @default false + */ + ignoreComments: boolean; + /** + * @default false + */ + ignoreTrailingComments: boolean; + /** + * @default false + */ + ignoreUrls: boolean; + /** + * @default false + */ + ignoreStrings: boolean; + /** + * @default false + */ + ignoreTemplateLiterals: boolean; + /** + * @default false + */ + ignoreRegExpLiterals: boolean; + }>, + ] + >; + + /** + * Rule to enforce a maximum number of lines per file. + * + * @since 2.12.0 + * @see https://eslint.org/docs/latest/rules/max-lines + */ + "max-lines": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 300 + */ + max: number; + /** + * @default false + */ + skipBlankLines: boolean; + /** + * @default false + */ + skipComments: boolean; + }> + | number, + ] + >; + + /** + * Rule to enforce a maximum number of lines of code in a function. + * + * @since 5.0.0 + * @see https://eslint.org/docs/latest/rules/max-lines-per-function + */ + "max-lines-per-function": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 50 + */ + max: number; + /** + * @default false + */ + skipBlankLines: boolean; + /** + * @default false + */ + skipComments: boolean; + /** + * @default false + */ + IIFEs: boolean; + }>, + ] + >; + + /** + * Rule to enforce a maximum depth that callbacks can be nested. + * + * @since 0.2.0 + * @see https://eslint.org/docs/latest/rules/max-nested-callbacks + */ + "max-nested-callbacks": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 10 + */ + max: number; + }> + | number, + ] + >; + + /** + * Rule to enforce a maximum number of parameters in function definitions. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/max-params + */ + "max-params": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 3 + */ + max: number; + }> + | number, + ] + >; + + /** + * Rule to enforce a maximum number of statements allowed in function blocks. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/max-statements + */ + "max-statements": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 10 + */ + max: number; + /** + * @default false + */ + ignoreTopLevelFunctions: boolean; + }> + | number, + ] + >; + + /** + * Rule to enforce a maximum number of statements allowed per line. + * + * @since 2.5.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`max-statements-per-line`](https://eslint.style/rules/js/max-statements-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/max-statements-per-line + */ + "max-statements-per-line": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 1 + */ + max: number; + }> + | number, + ] + >; + + /** + * Rule to enforce a particular style for multiline comments. + * + * @since 4.10.0 + * @deprecated since 9.3.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`multiline-comment-style`](https://eslint.style/rules/js/multiline-comment-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/multiline-comment-style + */ + "multiline-comment-style": Linter.RuleEntry< + ["starred-block" | "bare-block" | "separate-lines"] + >; + + /** + * Rule to enforce newlines between operands of ternary expressions. + * + * @since 3.1.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`multiline-ternary`](https://eslint.style/rules/js/multiline-ternary) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/multiline-ternary + */ + "multiline-ternary": Linter.RuleEntry< + ["always" | "always-multiline" | "never"] + >; + + /** + * Rule to require constructor names to begin with a capital letter. + * + * @since 0.0.3-0 + * @see https://eslint.org/docs/latest/rules/new-cap + */ + "new-cap": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + newIsCap: boolean; + /** + * @default true + */ + capIsNew: boolean; + newIsCapExceptions: string[]; + newIsCapExceptionPattern: string; + capIsNewExceptions: string[]; + capIsNewExceptionPattern: string; + /** + * @default true + */ + properties: boolean; + }>, + ] + >; + + /** + * Rule to enforce or disallow parentheses when invoking a constructor with no arguments. + * + * @since 0.0.6 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`new-parens`](https://eslint.style/rules/js/new-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/new-parens + */ + "new-parens": Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to require or disallow an empty line after variable declarations. + * + * @since 0.18.0 + * @deprecated since 4.0.0. + * The rule was replaced with a more general rule. + * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/newline-after-var + */ + "newline-after-var": Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to require an empty line before `return` statements. + * + * @since 2.3.0 + * @deprecated since 4.0.0. + * The rule was replaced with a more general rule. + * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/newline-before-return + */ + "newline-before-return": Linter.RuleEntry<[]>; + + /** + * Rule to require a newline after each call in a method chain. + * + * @since 2.0.0-rc.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`newline-per-chained-call`](https://eslint.style/rules/js/newline-per-chained-call) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/newline-per-chained-call + */ + "newline-per-chained-call": Linter.RuleEntry< + [ + { + /** + * @default 2 + */ + ignoreChainWithDepth: number; + }, + ] + >; + + /** + * Rule to disallow the use of `alert`, `confirm`, and `prompt`. + * + * @since 0.0.5 + * @see https://eslint.org/docs/latest/rules/no-alert + */ + "no-alert": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `Array` constructors. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-array-constructor + */ + "no-array-constructor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow using an async function as a Promise executor. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 5.3.0 + * @see https://eslint.org/docs/latest/rules/no-async-promise-executor + */ + "no-async-promise-executor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `await` inside of loops. + * + * @since 3.12.0 + * @see https://eslint.org/docs/latest/rules/no-await-in-loop + */ + "no-await-in-loop": Linter.RuleEntry<[]>; + + /** + * Rule to disallow bitwise operators. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-bitwise + */ + "no-bitwise": Linter.RuleEntry< + [ + Partial<{ + allow: string[]; + /** + * @default false + */ + int32Hint: boolean; + }>, + ] + >; + + /** + * Rule to disallow use of the `Buffer()` constructor. + * + * @since 4.0.0-alpha.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-deprecated-api`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-deprecated-api.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-buffer-constructor + */ + "no-buffer-constructor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `arguments.caller` or `arguments.callee`. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/no-caller + */ + "no-caller": Linter.RuleEntry<[]>; + + /** + * Rule to disallow lexical declarations in case clauses. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.9.0 + * @see https://eslint.org/docs/latest/rules/no-case-declarations + */ + "no-case-declarations": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `catch` clause parameters from shadowing variables in the outer scope. + * + * @since 0.0.9 + * @deprecated since 5.1.0. + * This rule was renamed. + * Please, use [`no-shadow`](https://eslint.org/docs/rules/no-shadow). + * @see https://eslint.org/docs/latest/rules/no-catch-shadow + */ + "no-catch-shadow": Linter.RuleEntry<[]>; + + /** + * Rule to disallow reassigning class members. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/no-class-assign + */ + "no-class-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow comparing against `-0`. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 3.17.0 + * @see https://eslint.org/docs/latest/rules/no-compare-neg-zero + */ + "no-compare-neg-zero": Linter.RuleEntry<[]>; + + /** + * Rule to disallow assignment operators in conditional expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-cond-assign + */ + "no-cond-assign": Linter.RuleEntry<["except-parens" | "always"]>; + + /** + * Rule to disallow arrow functions where they could be confused with comparisons. + * + * @since 2.0.0-alpha-2 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-confusing-arrow`](https://eslint.style/rules/js/no-confusing-arrow) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-confusing-arrow + */ + "no-confusing-arrow": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + allowParens: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of `console`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-console + */ + "no-console": Linter.RuleEntry< + [ + Partial<{ + allow: Array; + }>, + ] + >; + + /** + * Rule to disallow reassigning `const` variables. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/no-const-assign + */ + "no-const-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow expressions where the operation doesn't affect the value. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 8.14.0 + * @see https://eslint.org/docs/latest/rules/no-constant-binary-expression + */ + "no-constant-binary-expression": Linter.RuleEntry<[]>; + + /** + * Rule to disallow constant expressions in conditions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.4.1 + * @see https://eslint.org/docs/latest/rules/no-constant-condition + */ + "no-constant-condition": Linter.RuleEntry< + [ + { + /** + * @default true + */ + checkLoops: boolean; + }, + ] + >; + + /** + * Rule to disallow returning value from constructor. + * + * @since 6.7.0 + * @see https://eslint.org/docs/latest/rules/no-constructor-return + */ + "no-constructor-return": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `continue` statements. + * + * @since 0.19.0 + * @see https://eslint.org/docs/latest/rules/no-continue + */ + "no-continue": Linter.RuleEntry<[]>; + + /** + * Rule to disallow control characters in regular expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.1.0 + * @see https://eslint.org/docs/latest/rules/no-control-regex + */ + "no-control-regex": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `debugger`. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-debugger + */ + "no-debugger": Linter.RuleEntry<[]>; + + /** + * Rule to disallow deleting variables. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-delete-var + */ + "no-delete-var": Linter.RuleEntry<[]>; + + /** + * Rule to disallow equal signs explicitly at the beginning of regular expressions. + * + * @since 0.1.0 + * @see https://eslint.org/docs/latest/rules/no-div-regex + */ + "no-div-regex": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate arguments in `function` definitions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.16.0 + * @see https://eslint.org/docs/latest/rules/no-dupe-args + */ + "no-dupe-args": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate class members. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.2.0 + * @see https://eslint.org/docs/latest/rules/no-dupe-class-members + */ + "no-dupe-class-members": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate conditions in if-else-if chains. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 6.7.0 + * @see https://eslint.org/docs/latest/rules/no-dupe-else-if + */ + "no-dupe-else-if": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate keys in object literals. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-dupe-keys + */ + "no-dupe-keys": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate case labels. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.17.0 + * @see https://eslint.org/docs/latest/rules/no-duplicate-case + */ + "no-duplicate-case": Linter.RuleEntry<[]>; + + /** + * Rule to disallow duplicate module imports. + * + * @since 2.5.0 + * @see https://eslint.org/docs/latest/rules/no-duplicate-imports + */ + "no-duplicate-imports": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + includeExports: boolean; + }>, + ] + >; + + /** + * Rule to disallow `else` blocks after `return` statements in `if` statements. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-else-return + */ + "no-else-return": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + allowElseIf: boolean; + }>, + ] + >; + + /** + * Rule to disallow empty block statements. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-empty + */ + "no-empty": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowEmptyCatch: boolean; + }>, + ] + >; + + /** + * Rule to disallow empty character classes in regular expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.22.0 + * @see https://eslint.org/docs/latest/rules/no-empty-character-class + */ + "no-empty-character-class": Linter.RuleEntry<[]>; + + /** + * Rule to disallow empty functions. + * + * @since 2.0.0 + * @see https://eslint.org/docs/latest/rules/no-empty-function + */ + "no-empty-function": Linter.RuleEntry< + [ + Partial<{ + /** + * @default [] + */ + allow: Array< + | "functions" + | "arrowFunctions" + | "generatorFunctions" + | "methods" + | "generatorMethods" + | "getters" + | "setters" + | "constructors" + | "asyncFunctions" + | "asyncMethods" + >; + }>, + ] + >; + + /** + * Rule to disallow empty destructuring patterns. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.7.0 + * @see https://eslint.org/docs/latest/rules/no-empty-pattern + */ + "no-empty-pattern": Linter.RuleEntry<[]>; + + /** + * Rule to disallow empty static blocks. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 8.27.0 + * @see https://eslint.org/docs/latest/rules/no-empty-static-block + */ + "no-empty-static-block": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `null` comparisons without type-checking operators. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-eq-null + */ + "no-eq-null": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `eval()`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-eval + */ + "no-eval": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowIndirect: boolean; + }>, + ] + >; + + /** + * Rule to disallow reassigning exceptions in `catch` clauses. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-ex-assign + */ + "no-ex-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow extending native types. + * + * @since 0.1.4 + * @see https://eslint.org/docs/latest/rules/no-extend-native + */ + "no-extend-native": Linter.RuleEntry< + [ + Partial<{ + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to disallow unnecessary calls to `.bind()`. + * + * @since 0.8.0 + * @see https://eslint.org/docs/latest/rules/no-extra-bind + */ + "no-extra-bind": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary boolean casts. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-extra-boolean-cast + */ + "no-extra-boolean-cast": Linter.RuleEntry< + [ + | Partial<{ + /** + * @since 9.3.0 + * @default false + */ + enforceForInnerExpressions: boolean; + /** + * @deprecated + */ + enforceForLogicalOperands: never; + }> + | Partial<{ + /** + * @deprecated + * @since 7.0.0-alpha.2 + * @default false + */ + enforceForLogicalOperands: boolean; + enforceForInnerExpressions: never; + }>, + ] + >; + + /** + * Rule to disallow unnecessary labels. + * + * @since 2.0.0-rc.0 + * @see https://eslint.org/docs/latest/rules/no-extra-label + */ + "no-extra-label": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary parentheses. + * + * @since 0.1.4 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-extra-parens`](https://eslint.style/rules/js/no-extra-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-extra-parens + */ + "no-extra-parens": + | Linter.RuleEntry< + [ + "all", + Partial<{ + /** + * @default true, + */ + conditionalAssign: boolean; + /** + * @default true + */ + returnAssign: boolean; + /** + * @default true + */ + nestedBinaryExpressions: boolean; + /** + * @default 'none' + */ + ignoreJSX: + | "none" + | "all" + | "multi-line" + | "single-line"; + /** + * @default true + */ + enforceForArrowConditionals: boolean; + }>, + ] + > + | Linter.RuleEntry<["functions"]>; + + /** + * Rule to disallow unnecessary semicolons. + * + * @since 0.0.9 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-extra-semi`](https://eslint.style/rules/js/no-extra-semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-extra-semi + */ + "no-extra-semi": Linter.RuleEntry<[]>; + + /** + * Rule to disallow fallthrough of `case` statements. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/no-fallthrough + */ + "no-fallthrough": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 'falls?\s?through' + */ + commentPattern: string; + /** + * @default false + */ + allowEmptyCase: boolean; + /** + * @default false + */ + reportUnusedFallthroughComment: boolean; + }>, + ] + >; + + /** + * Rule to disallow leading or trailing decimal points in numeric literals. + * + * @since 0.0.6 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-floating-decimal`](https://eslint.style/rules/js/no-floating-decimal) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-floating-decimal + */ + "no-floating-decimal": Linter.RuleEntry<[]>; + + /** + * Rule to disallow reassigning `function` declarations. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-func-assign + */ + "no-func-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow assignments to native objects or read-only global variables. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 3.3.0 + * @see https://eslint.org/docs/latest/rules/no-global-assign + */ + "no-global-assign": Linter.RuleEntry< + [ + Partial<{ + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to disallow shorthand type conversions. + * + * @since 1.0.0-rc-2 + * @see https://eslint.org/docs/latest/rules/no-implicit-coercion + */ + "no-implicit-coercion": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + boolean: boolean; + /** + * @default true + */ + number: boolean; + /** + * @default true + */ + string: boolean; + /** + * @default false + */ + disallowTemplateShorthand: boolean; + /** + * @default [] + */ + allow: Array<"~" | "!!" | "+" | "- -" | "-" | "*">; + }>, + ] + >; + + /** + * Rule to disallow declarations in the global scope. + * + * @since 2.0.0-alpha-1 + * @see https://eslint.org/docs/latest/rules/no-implicit-globals + */ + "no-implicit-globals": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `eval()`-like methods. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/no-implied-eval + */ + "no-implied-eval": Linter.RuleEntry<[]>; + + /** + * Rule to disallow assigning to imported bindings. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 6.4.0 + * @see https://eslint.org/docs/latest/rules/no-import-assign + */ + "no-import-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow inline comments after code. + * + * @since 0.10.0 + * @see https://eslint.org/docs/latest/rules/no-inline-comments + */ + "no-inline-comments": Linter.RuleEntry<[]>; + + /** + * Rule to disallow variable or `function` declarations in nested blocks. + * + * @since 0.6.0 + * @see https://eslint.org/docs/latest/rules/no-inner-declarations + */ + "no-inner-declarations": Linter.RuleEntry<["functions" | "both"]>; + + /** + * Rule to disallow invalid regular expression strings in `RegExp` constructors. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.1.4 + * @see https://eslint.org/docs/latest/rules/no-invalid-regexp + */ + "no-invalid-regexp": Linter.RuleEntry< + [ + Partial<{ + allowConstructorFlags: string[]; + }>, + ] + >; + + /** + * Rule to disallow use of `this` in contexts where the value of `this` is `undefined`. + * + * @since 1.0.0-rc-2 + * @see https://eslint.org/docs/latest/rules/no-invalid-this + */ + "no-invalid-this": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + capIsConstructor: boolean; + }>, + ] + >; + + /** + * Rule to disallow irregular whitespace. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.9.0 + * @see https://eslint.org/docs/latest/rules/no-irregular-whitespace + */ + "no-irregular-whitespace": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + skipStrings: boolean; + /** + * @default false + */ + skipComments: boolean; + /** + * @default false + */ + skipRegExps: boolean; + /** + * @default false + */ + skipTemplates: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of the `__iterator__` property. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-iterator + */ + "no-iterator": Linter.RuleEntry<[]>; + + /** + * Rule to disallow labels that share a name with a variable. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-label-var + */ + "no-label-var": Linter.RuleEntry<[]>; + + /** + * Rule to disallow labeled statements. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-labels + */ + "no-labels": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowLoop: boolean; + /** + * @default false + */ + allowSwitch: boolean; + }>, + ] + >; + + /** + * Rule to disallow unnecessary nested blocks. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-lone-blocks + */ + "no-lone-blocks": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `if` statements as the only statement in `else` blocks. + * + * @since 0.6.0 + * @see https://eslint.org/docs/latest/rules/no-lonely-if + */ + "no-lonely-if": Linter.RuleEntry<[]>; + + /** + * Rule to disallow function declarations that contain unsafe references inside loop statements. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-loop-func + */ + "no-loop-func": Linter.RuleEntry<[]>; + + /** + * Rule to disallow literal numbers that lose precision. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 7.1.0 + * @see https://eslint.org/docs/latest/rules/no-loss-of-precision + */ + "no-loss-of-precision": Linter.RuleEntry<[]>; + + /** + * Rule to disallow magic numbers. + * + * @since 1.7.0 + * @see https://eslint.org/docs/latest/rules/no-magic-numbers + */ + "no-magic-numbers": Linter.RuleEntry< + [ + Partial<{ + /** + * @default [] + */ + ignore: number[]; + /** + * @default false + */ + ignoreArrayIndexes: boolean; + /** + * @default false + */ + enforceConst: boolean; + /** + * @default false + */ + detectObjects: boolean; + }>, + ] + >; + + /** + * Rule to disallow characters which are made with multiple code points in character class syntax. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 5.3.0 + * @see https://eslint.org/docs/latest/rules/no-misleading-character-class + */ + "no-misleading-character-class": Linter.RuleEntry< + [ + Partial<{ + /** + * @since 9.3.0 + * @default false + */ + allowEscape: boolean; + }>, + ] + >; + + /** + * Rule to disallow mixed binary operators. + * + * @since 2.12.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-mixed-operators`](https://eslint.style/rules/js/no-mixed-operators) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-mixed-operators + */ + "no-mixed-operators": Linter.RuleEntry< + [ + Partial<{ + /** + * @default + * [ + * ["+", "-", "*", "/", "%", "**"], + * ["&", "|", "^", "~", "<<", ">>", ">>>"], + * ["==", "!=", "===", "!==", ">", ">=", "<", "<="], + * ["&&", "||"], + * ["in", "instanceof"] + * ] + */ + groups: string[][]; + /** + * @default true + */ + allowSamePrecedence: boolean; + }>, + ] + >; + + /** + * Rule to disallow `require` calls to be mixed with regular variable declarations. + * + * @since 0.0.9 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-mixed-requires`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-mixed-requires.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-mixed-requires + */ + "no-mixed-requires": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + grouping: boolean; + /** + * @default false + */ + allowCall: boolean; + }>, + ] + >; + + /** + * Rule to disallow mixed spaces and tabs for indentation. + * + * @since 0.7.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-mixed-spaces-and-tabs`](https://eslint.style/rules/js/no-mixed-spaces-and-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs + */ + "no-mixed-spaces-and-tabs": Linter.RuleEntry<["smart-tabs"]>; + + /** + * Rule to disallow use of chained assignment expressions. + * + * @since 3.14.0 + * @see https://eslint.org/docs/latest/rules/no-multi-assign + */ + "no-multi-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow multiple spaces. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-multi-spaces`](https://eslint.style/rules/js/no-multi-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-multi-spaces + */ + "no-multi-spaces": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + ignoreEOLComments: boolean; + /** + * @default { Property: true } + */ + exceptions: Record; + }>, + ] + >; + + /** + * Rule to disallow multiline strings. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-multi-str + */ + "no-multi-str": Linter.RuleEntry<[]>; + + /** + * Rule to disallow multiple empty lines. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-multiple-empty-lines`](https://eslint.style/rules/js/no-multiple-empty-lines) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-multiple-empty-lines + */ + "no-multiple-empty-lines": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default 2 + */ + max: number; + maxEOF: number; + maxBOF: number; + }> + | number, + ] + >; + + /** + * Rule to disallow assignments to native objects or read-only global variables. + * + * @since 0.0.9 + * @deprecated since 3.3.0. + * Renamed rule. + * Please, use [`no-global-assign`](https://eslint.org/docs/rules/no-global-assign). + * @see https://eslint.org/docs/latest/rules/no-native-reassign + */ + "no-native-reassign": Linter.RuleEntry< + [ + Partial<{ + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to disallow negated conditions. + * + * @since 1.6.0 + * @see https://eslint.org/docs/latest/rules/no-negated-condition + */ + "no-negated-condition": Linter.RuleEntry<[]>; + + /** + * Rule to disallow negating the left operand in `in` expressions. + * + * @since 0.1.2 + * @deprecated since 3.3.0. + * Renamed rule. + * Please, use [`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation). + * @see https://eslint.org/docs/latest/rules/no-negated-in-lhs + */ + "no-negated-in-lhs": Linter.RuleEntry<[]>; + + /** + * Rule to disallow nested ternary expressions. + * + * @since 0.2.0 + * @see https://eslint.org/docs/latest/rules/no-nested-ternary + */ + "no-nested-ternary": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators outside of assignments or comparisons. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/no-new + */ + "no-new": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators with the `Function` object. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/no-new-func + */ + "no-new-func": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators with global non-constructor functions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 8.27.0 + * @see https://eslint.org/docs/latest/rules/no-new-native-nonconstructor + */ + "no-new-native-nonconstructor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `Object` constructors. + * + * @since 0.0.9 + * @deprecated since 8.50.0. + * 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. + * Please, use [`no-object-constructor`](https://eslint.org/docs/rules/no-object-constructor). + * @see https://eslint.org/docs/latest/rules/no-new-object + */ + "no-new-object": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators with calls to `require`. + * + * @since 0.6.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-new-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-new-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-new-require + */ + "no-new-require": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators with the `Symbol` object. + * + * @since 2.0.0-beta.1 + * @deprecated since 9.0.0. + * The rule was replaced with a more general rule. + * Please, use [`no-new-native-nonconstructor`](https://eslint.org/docs/latest/rules/no-new-native-nonconstructor). + * @see https://eslint.org/docs/latest/rules/no-new-symbol + */ + "no-new-symbol": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `new` operators with the `String`, `Number`, and `Boolean` objects. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/no-new-wrappers + */ + "no-new-wrappers": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `\8` and `\9` escape sequences in string literals. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 7.14.0 + * @see https://eslint.org/docs/latest/rules/no-nonoctal-decimal-escape + */ + "no-nonoctal-decimal-escape": Linter.RuleEntry<[]>; + + /** + * Rule to disallow calling global object properties as functions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-obj-calls + */ + "no-obj-calls": Linter.RuleEntry<[]>; + + /** + * Rule to disallow calls to the `Object` constructor without an argument. + * + * @since 8.50.0 + * @see https://eslint.org/docs/latest/rules/no-object-constructor + */ + "no-object-constructor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow octal literals. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/no-octal + */ + "no-octal": Linter.RuleEntry<[]>; + + /** + * Rule to disallow octal escape sequences in string literals. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-octal-escape + */ + "no-octal-escape": Linter.RuleEntry<[]>; + + /** + * Rule to disallow reassigning function parameters. + * + * @since 0.18.0 + * @see https://eslint.org/docs/latest/rules/no-param-reassign + */ + "no-param-reassign": Linter.RuleEntry< + [ + | { + props?: false; + } + | ({ + props: true; + } & Partial<{ + /** + * @default [] + */ + ignorePropertyModificationsFor: string[]; + /** + * @since 6.6.0 + * @default [] + */ + ignorePropertyModificationsForRegex: string[]; + }>), + ] + >; + + /** + * Rule to disallow string concatenation with `__dirname` and `__filename`. + * + * @since 0.4.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-path-concat`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-path-concat.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-path-concat + */ + "no-path-concat": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the unary operators `++` and `--`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-plusplus + */ + "no-plusplus": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowForLoopAfterthoughts: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of `process.env`. + * + * @since 0.9.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-process-env`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-env.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-process-env + */ + "no-process-env": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `process.exit()`. + * + * @since 0.4.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-process-exit`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-exit.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-process-exit + */ + "no-process-exit": Linter.RuleEntry<[]>; + + /** + * Rule to disallow returning values from Promise executor functions. + * + * @since 7.3.0 + * @see https://eslint.org/docs/latest/rules/no-promise-executor-return + */ + "no-promise-executor-return": Linter.RuleEntry< + [ + { + /** + * @default false + */ + allowVoid?: boolean; + }, + ] + >; + + /** + * Rule to disallow the use of the `__proto__` property. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-proto + */ + "no-proto": Linter.RuleEntry<[]>; + + /** + * Rule to disallow calling some `Object.prototype` methods directly on objects. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 2.11.0 + * @see https://eslint.org/docs/latest/rules/no-prototype-builtins + */ + "no-prototype-builtins": Linter.RuleEntry<[]>; + + /** + * Rule to disallow variable redeclaration. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-redeclare + */ + "no-redeclare": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + builtinGlobals: boolean; + }>, + ] + >; + + /** + * Rule to disallow multiple spaces in regular expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-regex-spaces + */ + "no-regex-spaces": Linter.RuleEntry<[]>; + + /** + * Rule to disallow specified names in exports. + * + * @since 7.0.0-alpha.0 + * @see https://eslint.org/docs/latest/rules/no-restricted-exports + */ + "no-restricted-exports": Linter.RuleEntry< + [ + Partial<{ + /** + * @default [] + */ + restrictedNamedExports: string[]; + /** + * @since 9.3.0 + */ + restrictedNamedExportsPattern: string; + /** + * @since 8.33.0 + */ + restrictDefaultExports: Partial<{ + /** + * @default false + */ + direct: boolean; + /** + * @default false + */ + named: boolean; + /** + * @default false + */ + defaultFrom: boolean; + /** + * @default false + */ + namedFrom: boolean; + /** + * @default false + */ + namespaceFrom: boolean; + }>; + }>, + ] + >; + + /** + * Rule to disallow specified global variables. + * + * @since 2.3.0 + * @see https://eslint.org/docs/latest/rules/no-restricted-globals + */ + "no-restricted-globals": Linter.RuleEntry< + [ + ...Array< + | string + | { + name: string; + message?: string | undefined; + } + >, + ] + >; + + /** + * Rule to disallow specified modules when loaded by `import`. + * + * @since 2.0.0-alpha-1 + * @see https://eslint.org/docs/latest/rules/no-restricted-imports + */ + "no-restricted-imports": Linter.RuleEntry< + [ + ...Array< + | string + | ValidNoRestrictedImportPathOptions + | Partial<{ + paths: Array< + string | ValidNoRestrictedImportPathOptions + >; + patterns: Array< + string | ValidNoRestrictedImportPatternOptions + >; + }> + >, + ] + >; + + /** + * Rule to disallow specified modules when loaded by `require`. + * + * @since 0.6.0 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-restricted-require`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-restricted-require.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-restricted-modules + */ + "no-restricted-modules": Linter.RuleEntry< + [ + ...Array< + | string + | { + name: string; + message?: string | undefined; + } + | Partial<{ + paths: Array< + | string + | { + name: string; + message?: string | undefined; + } + >; + patterns: string[]; + }> + >, + ] + >; + + /** + * Rule to disallow certain properties on certain objects. + * + * @since 3.5.0 + * @see https://eslint.org/docs/latest/rules/no-restricted-properties + */ + "no-restricted-properties": Linter.RuleEntry< + [ + ...Array< + | { + object: string; + property?: string | undefined; + message?: string | undefined; + } + | { + property: string; + message?: string | undefined; + } + >, + ] + >; + + /** + * Rule to disallow specified syntax. + * + * @since 1.4.0 + * @see https://eslint.org/docs/latest/rules/no-restricted-syntax + */ + "no-restricted-syntax": Linter.RuleEntry< + [ + ...Array< + | string + | { + selector: string; + message?: string | undefined; + } + >, + ] + >; + + /** + * Rule to disallow assignment operators in `return` statements. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-return-assign + */ + "no-return-assign": Linter.RuleEntry<["except-parens" | "always"]>; + + /** + * Rule to disallow unnecessary `return await`. + * + * @since 3.10.0 + * @deprecated since 8.46.0. + * The original assumption of the rule no longer holds true because of engine optimization. + * @see https://eslint.org/docs/latest/rules/no-return-await + */ + "no-return-await": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `javascript:` URLs. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-script-url + */ + "no-script-url": Linter.RuleEntry<[]>; + + /** + * Rule to disallow assignments where both sides are exactly the same. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 2.0.0-rc.0 + * @see https://eslint.org/docs/latest/rules/no-self-assign + */ + "no-self-assign": Linter.RuleEntry<[]>; + + /** + * Rule to disallow comparisons where both sides are exactly the same. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-self-compare + */ + "no-self-compare": Linter.RuleEntry<[]>; + + /** + * Rule to disallow comma operators. + * + * @since 0.5.1 + * @see https://eslint.org/docs/latest/rules/no-sequences + */ + "no-sequences": Linter.RuleEntry< + [ + Partial<{ + /** + * @since 7.23.0 + * @default true + */ + allowInParentheses: boolean; + }>, + ] + >; + + /** + * Rule to disallow returning values from setters. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 6.7.0 + * @see https://eslint.org/docs/latest/rules/no-setter-return + */ + "no-setter-return": Linter.RuleEntry<[]>; + + /** + * Rule to disallow variable declarations from shadowing variables declared in the outer scope. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-shadow + */ + "no-shadow": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + builtinGlobals: boolean; + /** + * @default 'functions' + */ + hoist: "functions" | "all" | "never"; + allow: string[]; + /** + * @since 8.10.0 + * @default false + */ + ignoreOnInitialization: boolean; + }>, + ] + >; + + /** + * Rule to disallow identifiers from shadowing restricted names. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.1.4 + * @see https://eslint.org/docs/latest/rules/no-shadow-restricted-names + */ + "no-shadow-restricted-names": Linter.RuleEntry<[]>; + + /** + * Rule to disallow spacing between function identifiers and their applications (deprecated). + * + * @since 0.1.2 + * @deprecated since 3.3.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`function-call-spacing`](https://eslint.style/rules/js/function-call-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-spaced-func + */ + "no-spaced-func": Linter.RuleEntry<[]>; + + /** + * Rule to disallow sparse arrays. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.4.0 + * @see https://eslint.org/docs/latest/rules/no-sparse-arrays + */ + "no-sparse-arrays": Linter.RuleEntry<[]>; + + /** + * Rule to disallow synchronous methods. + * + * @since 0.0.9 + * @deprecated since 7.0.0. + * Node.js rules were moved out of ESLint core. + * Please, use [`no-sync`](https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-sync.md) in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). + * @see https://eslint.org/docs/latest/rules/no-sync + */ + "no-sync": Linter.RuleEntry< + [ + { + /** + * @default false + */ + allowAtRootLevel: boolean; + }, + ] + >; + + /** + * Rule to disallow all tabs. + * + * @since 3.2.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-tabs`](https://eslint.style/rules/js/no-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-tabs + */ + "no-tabs": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowIndentationTabs: boolean; + }>, + ] + >; + + /** + * Rule to disallow template literal placeholder syntax in regular strings. + * + * @since 3.3.0 + * @see https://eslint.org/docs/latest/rules/no-template-curly-in-string + */ + "no-template-curly-in-string": Linter.RuleEntry<[]>; + + /** + * Rule to disallow ternary operators. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-ternary + */ + "no-ternary": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `this`/`super` before calling `super()` in constructors. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.24.0 + * @see https://eslint.org/docs/latest/rules/no-this-before-super + */ + "no-this-before-super": Linter.RuleEntry<[]>; + + /** + * Rule to disallow throwing literals as exceptions. + * + * @since 0.15.0 + * @see https://eslint.org/docs/latest/rules/no-throw-literal + */ + "no-throw-literal": Linter.RuleEntry<[]>; + + /** + * Rule to disallow trailing whitespace at the end of lines. + * + * @since 0.7.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-trailing-spaces`](https://eslint.style/rules/js/no-trailing-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-trailing-spaces + */ + "no-trailing-spaces": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + skipBlankLines: boolean; + /** + * @default false + */ + ignoreComments: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of undeclared variables unless mentioned in \/*global *\/ comments. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-undef + */ + "no-undef": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + typeof: boolean; + }>, + ] + >; + + /** + * Rule to disallow initializing variables to `undefined`. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/no-undef-init + */ + "no-undef-init": Linter.RuleEntry<[]>; + + /** + * Rule to disallow the use of `undefined` as an identifier. + * + * @since 0.7.1 + * @see https://eslint.org/docs/latest/rules/no-undefined + */ + "no-undefined": Linter.RuleEntry<[]>; + + /** + * Rule to disallow dangling underscores in identifiers. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-underscore-dangle + */ + "no-underscore-dangle": Linter.RuleEntry< + [ + Partial<{ + allow: string[]; + /** + * @default false + */ + allowAfterThis: boolean; + /** + * @default false + */ + allowAfterSuper: boolean; + /** + * @since 6.7.0 + * @default false + */ + allowAfterThisConstructor: boolean; + /** + * @default false + */ + enforceInMethodNames: boolean; + /** + * @since 8.15.0 + * @default false + */ + enforceInClassFields: boolean; + /** + * @since 8.31.0 + * @default true + */ + allowInArrayDestructuring: boolean; + /** + * @since 8.31.0 + * @default true + */ + allowInObjectDestructuring: boolean; + /** + * @since 7.7.0 + * @default true + */ + allowFunctionParams: boolean; + }>, + ] + >; + + /** + * Rule to disallow confusing multiline expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.24.0 + * @see https://eslint.org/docs/latest/rules/no-unexpected-multiline + */ + "no-unexpected-multiline": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unmodified loop conditions. + * + * @since 2.0.0-alpha-2 + * @see https://eslint.org/docs/latest/rules/no-unmodified-loop-condition + */ + "no-unmodified-loop-condition": Linter.RuleEntry<[]>; + + /** + * Rule to disallow ternary operators when simpler alternatives exist. + * + * @since 0.21.0 + * @see https://eslint.org/docs/latest/rules/no-unneeded-ternary + */ + "no-unneeded-ternary": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + defaultAssignment: boolean; + }>, + ] + >; + + /** + * Rule to disallow unreachable code after `return`, `throw`, `continue`, and `break` statements. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/no-unreachable + */ + "no-unreachable": Linter.RuleEntry<[]>; + + /** + * Rule to disallow loops with a body that allows only one iteration. + * + * @since 7.3.0 + * @see https://eslint.org/docs/latest/rules/no-unreachable-loop + */ + "no-unreachable-loop": Linter.RuleEntry< + [ + Partial<{ + /** + * @default [] + */ + ignore: + | "WhileStatement" + | "DoWhileStatement" + | "ForStatement" + | "ForInStatement" + | "ForOfStatement"; + }>, + ] + >; + + /** + * Rule to disallow control flow statements in `finally` blocks. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 2.9.0 + * @see https://eslint.org/docs/latest/rules/no-unsafe-finally + */ + "no-unsafe-finally": Linter.RuleEntry<[]>; + + /** + * Rule to disallow negating the left operand of relational operators. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 3.3.0 + * @see https://eslint.org/docs/latest/rules/no-unsafe-negation + */ + "no-unsafe-negation": Linter.RuleEntry< + [ + Partial<{ + /** + * @since 6.6.0 + * @default false + */ + enforceForOrderingRelations: boolean; + }>, + ] + >; + + /** + * Rule to disallow use of optional chaining in contexts where the `undefined` value is not allowed. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 7.15.0 + * @see https://eslint.org/docs/latest/rules/no-unsafe-optional-chaining + */ + "no-unsafe-optional-chaining": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + disallowArithmeticOperators: boolean; + }>, + ] + >; + + /** + * Rule to disallow unused expressions. + * + * @since 0.1.0 + * @see https://eslint.org/docs/latest/rules/no-unused-expressions + */ + "no-unused-expressions": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowShortCircuit: boolean; + /** + * @default false + */ + allowTernary: boolean; + /** + * @default false + */ + allowTaggedTemplates: boolean; + /** + * @since 7.20.0 + * @default false + */ + enforceForJSX: boolean; + }>, + ] + >; + + /** + * Rule to disallow unused labels. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 2.0.0-rc.0 + * @see https://eslint.org/docs/latest/rules/no-unused-labels + */ + "no-unused-labels": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unused private class members. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 8.1.0 + * @see https://eslint.org/docs/latest/rules/no-unused-private-class-members + */ + "no-unused-private-class-members": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unused variables. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-unused-vars + */ + "no-unused-vars": Linter.RuleEntry< + [ + | "all" + | "local" + | Partial<{ + /** + * @default 'all' + */ + vars: "all" | "local"; + varsIgnorePattern: string; + /** + * @default 'after-used' + */ + args: "after-used" | "all" | "none"; + /** + * @default false + */ + ignoreRestSiblings: boolean; + argsIgnorePattern: string; + /** + * @default 'all' + */ + caughtErrors: "none" | "all"; + caughtErrorsIgnorePattern: string; + destructuredArrayIgnorePattern: string; + /** + * @default false + */ + ignoreClassWithStaticInitBlock: boolean; + /** + * @default false + */ + reportUsedIgnorePattern: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of variables before they are defined. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/no-use-before-define + */ + "no-use-before-define": Linter.RuleEntry< + [ + | Partial<{ + /** + * @default true + */ + functions: boolean; + /** + * @default true + */ + classes: boolean; + /** + * @default true + */ + variables: boolean; + /** + * @default false + */ + allowNamedExports: boolean; + }> + | "nofunc", + ] + >; + + /** + * Rule to disallow variable assignments when the value is not used. + * + * @since 9.0.0-alpha.1 + * @see https://eslint.org/docs/latest/rules/no-useless-assignment + */ + "no-useless-assignment": Linter.RuleEntry<[]>; + + /** + * Rule to disallow useless backreferences in regular expressions. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 7.0.0-alpha.0 + * @see https://eslint.org/docs/latest/rules/no-useless-backreference + */ + "no-useless-backreference": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary calls to `.call()` and `.apply()`. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/no-useless-call + */ + "no-useless-call": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary `catch` clauses. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 5.11.0 + * @see https://eslint.org/docs/latest/rules/no-useless-catch + */ + "no-useless-catch": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary computed property keys in objects and classes. + * + * @since 2.9.0 + * @see https://eslint.org/docs/latest/rules/no-useless-computed-key + */ + "no-useless-computed-key": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + enforceForClassMembers: boolean; + }>, + ] + >; + + /** + * Rule to disallow unnecessary concatenation of literals or template literals. + * + * @since 1.3.0 + * @see https://eslint.org/docs/latest/rules/no-useless-concat + */ + "no-useless-concat": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary constructors. + * + * @since 2.0.0-beta.1 + * @see https://eslint.org/docs/latest/rules/no-useless-constructor + */ + "no-useless-constructor": Linter.RuleEntry<[]>; + + /** + * Rule to disallow unnecessary escape characters. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 2.5.0 + * @see https://eslint.org/docs/latest/rules/no-useless-escape + */ + "no-useless-escape": Linter.RuleEntry<[]>; + + /** + * Rule to disallow renaming import, export, and destructured assignments to the same name. + * + * @since 2.11.0 + * @see https://eslint.org/docs/latest/rules/no-useless-rename + */ + "no-useless-rename": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + ignoreImport: boolean; + /** + * @default false + */ + ignoreExport: boolean; + /** + * @default false + */ + ignoreDestructuring: boolean; + }>, + ] + >; + + /** + * Rule to disallow redundant return statements. + * + * @since 3.9.0 + * @see https://eslint.org/docs/latest/rules/no-useless-return + */ + "no-useless-return": Linter.RuleEntry<[]>; + + /** + * Rule to require `let` or `const` instead of `var`. + * + * @since 0.12.0 + * @see https://eslint.org/docs/latest/rules/no-var + */ + "no-var": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `void` operators. + * + * @since 0.8.0 + * @see https://eslint.org/docs/latest/rules/no-void + */ + "no-void": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowAsStatement: boolean; + }>, + ] + >; + + /** + * Rule to disallow specified warning terms in comments. + * + * @since 0.4.4 + * @see https://eslint.org/docs/latest/rules/no-warning-comments + */ + "no-warning-comments": Linter.RuleEntry< + [ + { + /** + * @default ["todo", "fixme", "xxx"] + */ + terms: string[]; + /** + * @default 'start' + */ + location: "start" | "anywhere"; + }, + ] + >; + + /** + * Rule to disallow whitespace before properties. + * + * @since 2.0.0-beta.1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`no-whitespace-before-property`](https://eslint.style/rules/js/no-whitespace-before-property) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/no-whitespace-before-property + */ + "no-whitespace-before-property": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `with` statements. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.2 + * @see https://eslint.org/docs/latest/rules/no-with + */ + "no-with": Linter.RuleEntry<[]>; + + /** + * Rule to enforce the location of single-line statements. + * + * @since 3.17.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`nonblock-statement-body-position`](https://eslint.style/rules/js/nonblock-statement-body-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/nonblock-statement-body-position + */ + "nonblock-statement-body-position": Linter.RuleEntry< + [ + "beside" | "below" | "any", + Partial<{ + overrides: Record; + }>, + ] + >; + + /** + * Rule to enforce consistent line breaks after opening and before closing braces. + * + * @since 2.12.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`object-curly-newline`](https://eslint.style/rules/js/object-curly-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/object-curly-newline + */ + "object-curly-newline": Linter.RuleEntry< + [ + | "always" + | "never" + | Partial<{ + /** + * @default false + */ + multiline: boolean; + minProperties: number; + /** + * @default true + */ + consistent: boolean; + }> + | Partial< + Record< + | "ObjectExpression" + | "ObjectPattern" + | "ImportDeclaration" + | "ExportDeclaration", + | "always" + | "never" + | Partial<{ + /** + * @default false + */ + multiline: boolean; + minProperties: number; + /** + * @default true + */ + consistent: boolean; + }> + > + >, + ] + >; + + /** + * Rule to enforce consistent spacing inside braces. + * + * @since 0.22.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`object-curly-spacing`](https://eslint.style/rules/js/object-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/object-curly-spacing + */ + "object-curly-spacing": + | Linter.RuleEntry< + [ + "never", + { + /** + * @default false + */ + arraysInObjects: boolean; + /** + * @default false + */ + objectsInObjects: boolean; + }, + ] + > + | Linter.RuleEntry< + [ + "always", + { + /** + * @default true + */ + arraysInObjects: boolean; + /** + * @default true + */ + objectsInObjects: boolean; + }, + ] + >; + + /** + * Rule to enforce placing object properties on separate lines. + * + * @since 2.10.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`object-property-newline`](https://eslint.style/rules/js/object-property-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/object-property-newline + */ + "object-property-newline": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowAllPropertiesOnSameLine: boolean; + }>, + ] + >; + + /** + * Rule to require or disallow method and property shorthand syntax for object literals. + * + * @since 0.20.0 + * @see https://eslint.org/docs/latest/rules/object-shorthand + */ + "object-shorthand": + | Linter.RuleEntry< + [ + "always" | "methods", + Partial<{ + /** + * @default false + */ + avoidQuotes: boolean; + /** + * @default false + */ + ignoreConstructors: boolean; + /** + * @since 8.22.0 + */ + methodsIgnorePattern: string; + /** + * @default false + */ + avoidExplicitReturnArrows: boolean; + }>, + ] + > + | Linter.RuleEntry< + [ + "properties", + Partial<{ + /** + * @default false + */ + avoidQuotes: boolean; + }>, + ] + > + | Linter.RuleEntry<["never" | "consistent" | "consistent-as-needed"]>; + + /** + * Rule to enforce variables to be declared either together or separately in functions. + * + * @since 0.0.9 + * @see https://eslint.org/docs/latest/rules/one-var + */ + "one-var": Linter.RuleEntry< + [ + | "always" + | "never" + | "consecutive" + | Partial< + { + /** + * @default false + */ + separateRequires: boolean; + } & Record< + "var" | "let" | "const", + "always" | "never" | "consecutive" + > + > + | Partial< + Record< + "initialized" | "uninitialized", + "always" | "never" | "consecutive" + > + >, + ] + >; + + /** + * Rule to require or disallow newlines around variable declarations. + * + * @since 2.0.0-beta.3 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`one-var-declaration-per-line`](https://eslint.style/rules/js/one-var-declaration-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/one-var-declaration-per-line + */ + "one-var-declaration-per-line": Linter.RuleEntry< + ["initializations" | "always"] + >; + + /** + * Rule to require or disallow assignment operator shorthand where possible. + * + * @since 0.10.0 + * @see https://eslint.org/docs/latest/rules/operator-assignment + */ + "operator-assignment": Linter.RuleEntry<["always" | "never"]>; + + /** + * Rule to enforce consistent linebreak style for operators. + * + * @since 0.19.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`operator-linebreak`](https://eslint.style/rules/js/operator-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/operator-linebreak + */ + "operator-linebreak": Linter.RuleEntry< + [ + "after" | "before" | "none", + Partial<{ + overrides: Record; + }>, + ] + >; + + /** + * Rule to require or disallow padding within blocks. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`padded-blocks`](https://eslint.style/rules/js/padded-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/padded-blocks + */ + "padded-blocks": Linter.RuleEntry< + [ + ( + | "always" + | "never" + | Partial< + Record< + "blocks" | "classes" | "switches", + "always" | "never" + > + > + ), + { + /** + * @default false + */ + allowSingleLineBlocks: boolean; + }, + ] + >; + + /** + * Rule to require or disallow padding lines between statements. + * + * @since 4.0.0-beta.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`padding-line-between-statements`](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/padding-line-between-statements + */ + "padding-line-between-statements": Linter.RuleEntry< + [ + ...Array< + { + blankLine: "any" | "never" | "always"; + } & Record<"prev" | "next", string | string[]> + >, + ] + >; + + /** + * Rule to require using arrow functions for callbacks. + * + * @since 1.2.0 + * @see https://eslint.org/docs/latest/rules/prefer-arrow-callback + */ + "prefer-arrow-callback": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowNamedFunctions: boolean; + /** + * @default true + */ + allowUnboundThis: boolean; + }>, + ] + >; + + /** + * Rule to require `const` declarations for variables that are never reassigned after declared. + * + * @since 0.23.0 + * @see https://eslint.org/docs/latest/rules/prefer-const + */ + "prefer-const": Linter.RuleEntry< + [ + Partial<{ + /** + * @default 'any' + */ + destructuring: "any" | "all"; + /** + * @default false + */ + ignoreReadBeforeAssign: boolean; + }>, + ] + >; + + /** + * Rule to require destructuring from arrays and/or objects. + * + * @since 3.13.0 + * @see https://eslint.org/docs/latest/rules/prefer-destructuring + */ + "prefer-destructuring": Linter.RuleEntry< + [ + Partial< + | { + VariableDeclarator: Partial<{ + array: boolean; + object: boolean; + }>; + AssignmentExpression: Partial<{ + array: boolean; + object: boolean; + }>; + } + | { + array: boolean; + object: boolean; + } + >, + Partial<{ + enforceForRenamedProperties: boolean; + }>, + ] + >; + + /** + * Rule to disallow the use of `Math.pow` in favor of the `**` operator. + * + * @since 6.7.0 + * @see https://eslint.org/docs/latest/rules/prefer-exponentiation-operator + */ + "prefer-exponentiation-operator": Linter.RuleEntry<[]>; + + /** + * Rule to enforce using named capture group in regular expression. + * + * @since 5.15.0 + * @see https://eslint.org/docs/latest/rules/prefer-named-capture-group + */ + "prefer-named-capture-group": Linter.RuleEntry<[]>; + + /** + * Rule to disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals. + * + * @since 3.5.0 + * @see https://eslint.org/docs/latest/rules/prefer-numeric-literals + */ + "prefer-numeric-literals": Linter.RuleEntry<[]>; + + /** + * Rule to disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`. + * + * @since 8.5.0 + * @see https://eslint.org/docs/latest/rules/prefer-object-has-own + */ + "prefer-object-has-own": Linter.RuleEntry<[]>; + + /** + * Rule to disallow using `Object.assign` with an object literal as the first argument and prefer the use of object spread instead. + * + * @since 5.0.0-alpha.3 + * @see https://eslint.org/docs/latest/rules/prefer-object-spread + */ + "prefer-object-spread": Linter.RuleEntry<[]>; + + /** + * Rule to require using Error objects as Promise rejection reasons. + * + * @since 3.14.0 + * @see https://eslint.org/docs/latest/rules/prefer-promise-reject-errors + */ + "prefer-promise-reject-errors": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + allowEmptyReject: boolean; + }>, + ] + >; + + /** + * Rule to require `Reflect` methods where applicable. + * + * @since 1.0.0-rc-2 + * @deprecated since 3.9.0. + * The original intention of this rule was misguided. + * @see https://eslint.org/docs/latest/rules/prefer-reflect + */ + "prefer-reflect": Linter.RuleEntry< + [ + Partial<{ + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to disallow use of the `RegExp` constructor in favor of regular expression literals. + * + * @since 6.4.0 + * @see https://eslint.org/docs/latest/rules/prefer-regex-literals + */ + "prefer-regex-literals": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + disallowRedundantWrapping: boolean; + }>, + ] + >; + + /** + * Rule to require rest parameters instead of `arguments`. + * + * @since 2.0.0-alpha-1 + * @see https://eslint.org/docs/latest/rules/prefer-rest-params + */ + "prefer-rest-params": Linter.RuleEntry<[]>; + + /** + * Rule to require spread operators instead of `.apply()`. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/prefer-spread + */ + "prefer-spread": Linter.RuleEntry<[]>; + + /** + * Rule to require template literals instead of string concatenation. + * + * @since 1.2.0 + * @see https://eslint.org/docs/latest/rules/prefer-template + */ + "prefer-template": Linter.RuleEntry<[]>; + + /** + * Rule to require quotes around object literal property names. + * + * @since 0.0.6 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`quote-props`](https://eslint.style/rules/js/quote-props) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/quote-props + */ + "quote-props": + | Linter.RuleEntry<["always" | "consistent"]> + | Linter.RuleEntry< + [ + "as-needed", + Partial<{ + /** + * @default false + */ + keywords: boolean; + /** + * @default true + */ + unnecessary: boolean; + /** + * @default false + */ + numbers: boolean; + }>, + ] + > + | Linter.RuleEntry< + [ + "consistent-as-needed", + Partial<{ + /** + * @default false + */ + keywords: boolean; + }>, + ] + >; + + /** + * Rule to enforce the consistent use of either backticks, double, or single quotes. + * + * @since 0.0.7 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`quotes`](https://eslint.style/rules/js/quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/quotes + */ + quotes: Linter.RuleEntry< + [ + "double" | "single" | "backtick", + Partial<{ + /** + * @default false + */ + avoidEscape: boolean; + /** + * @default false + */ + allowTemplateLiterals: boolean; + }>, + ] + >; + + /** + * Rule to enforce the consistent use of the radix argument when using `parseInt()`. + * + * @since 0.0.7 + * @see https://eslint.org/docs/latest/rules/radix + */ + radix: Linter.RuleEntry<["always" | "as-needed"]>; + + /** + * Rule to disallow assignments that can lead to race conditions due to usage of `await` or `yield`. + * + * @since 5.3.0 + * @see https://eslint.org/docs/latest/rules/require-atomic-updates + */ + "require-atomic-updates": Linter.RuleEntry< + [ + Partial<{ + /** + * @since 8.3.0 + * @default false + */ + allowProperties: boolean; + }>, + ] + >; + + /** + * Rule to disallow async functions which have no `await` expression. + * + * @since 3.11.0 + * @see https://eslint.org/docs/latest/rules/require-await + */ + "require-await": Linter.RuleEntry<[]>; + + /** + * Rule to enforce the use of `u` or `v` flag on regular expressions. + * + * @since 5.3.0 + * @see https://eslint.org/docs/latest/rules/require-unicode-regexp + */ + "require-unicode-regexp": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + requireFlag: "u" | "v"; + }>, + ] + >; + + /** + * Rule to require generator functions to contain `yield`. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 1.0.0-rc-1 + * @see https://eslint.org/docs/latest/rules/require-yield + */ + "require-yield": Linter.RuleEntry<[]>; + + /** + * Rule to enforce spacing between rest and spread operators and their expressions. + * + * @since 2.12.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`rest-spread-spacing`](https://eslint.style/rules/js/rest-spread-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/rest-spread-spacing + */ + "rest-spread-spacing": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require or disallow semicolons instead of ASI. + * + * @since 0.0.6 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`semi`](https://eslint.style/rules/js/semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/semi + */ + semi: + | Linter.RuleEntry< + [ + "always", + Partial<{ + /** + * @default false + */ + omitLastInOneLineBlock: boolean; + }>, + ] + > + | Linter.RuleEntry< + [ + "never", + Partial<{ + /** + * @default 'any' + */ + beforeStatementContinuationChars: + | "any" + | "always" + | "never"; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing before and after semicolons. + * + * @since 0.16.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`semi-spacing`](https://eslint.style/rules/js/semi-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/semi-spacing + */ + "semi-spacing": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + before: boolean; + /** + * @default true + */ + after: boolean; + }>, + ] + >; + + /** + * Rule to enforce location of semicolons. + * + * @since 4.0.0-beta.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`semi-style`](https://eslint.style/rules/js/semi-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/semi-style + */ + "semi-style": Linter.RuleEntry<["last" | "first"]>; + + /** + * Rule to enforce sorted `import` declarations within modules. + * + * @since 2.0.0-beta.1 + * @see https://eslint.org/docs/latest/rules/sort-imports + */ + "sort-imports": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + ignoreCase: boolean; + /** + * @default false + */ + ignoreDeclarationSort: boolean; + /** + * @default false + */ + ignoreMemberSort: boolean; + /** + * @default ['none', 'all', 'multiple', 'single'] + */ + memberSyntaxSortOrder: Array< + "none" | "all" | "multiple" | "single" + >; + /** + * @default false + */ + allowSeparatedGroups: boolean; + }>, + ] + >; + + /** + * Rule to require object keys to be sorted. + * + * @since 3.3.0 + * @see https://eslint.org/docs/latest/rules/sort-keys + */ + "sort-keys": Linter.RuleEntry< + [ + "asc" | "desc", + Partial<{ + /** + * @default true + */ + caseSensitive: boolean; + /** + * @default 2 + */ + minKeys: number; + /** + * @default false + */ + natural: boolean; + /** + * @default false + */ + allowLineSeparatedGroups: boolean; + /** + * @default false + */ + ignoreComputedKeys: boolean; + }>, + ] + >; + + /** + * Rule to require variables within the same declaration block to be sorted. + * + * @since 0.2.0 + * @see https://eslint.org/docs/latest/rules/sort-vars + */ + "sort-vars": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + ignoreCase: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing before blocks. + * + * @since 0.9.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`space-before-blocks`](https://eslint.style/rules/js/space-before-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/space-before-blocks + */ + "space-before-blocks": Linter.RuleEntry< + [ + | "always" + | "never" + | Partial< + Record< + "functions" | "keywords" | "classes", + "always" | "never" | "off" + > + >, + ] + >; + + /** + * Rule to enforce consistent spacing before `function` definition opening parenthesis. + * + * @since 0.18.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`space-before-function-paren`](https://eslint.style/rules/js/space-before-function-paren) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/space-before-function-paren + */ + "space-before-function-paren": Linter.RuleEntry< + [ + | "always" + | "never" + | Partial< + Record< + "anonymous" | "named" | "asyncArrow", + "always" | "never" | "ignore" + > + >, + ] + >; + + /** + * Rule to enforce consistent spacing inside parentheses. + * + * @since 0.8.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`space-in-parens`](https://eslint.style/rules/js/space-in-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/space-in-parens + */ + "space-in-parens": Linter.RuleEntry< + [ + "never" | "always", + Partial<{ + exceptions: string[]; + }>, + ] + >; + + /** + * Rule to require spacing around infix operators. + * + * @since 0.2.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`space-infix-ops`](https://eslint.style/rules/js/space-infix-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/space-infix-ops + */ + "space-infix-ops": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + int32Hint: boolean; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing before or after unary operators. + * + * @since 0.10.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`space-unary-ops`](https://eslint.style/rules/js/space-unary-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/space-unary-ops + */ + "space-unary-ops": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + words: boolean; + /** + * @default false + */ + nonwords: boolean; + overrides: Record; + }>, + ] + >; + + /** + * Rule to enforce consistent spacing after the `//` or `/*` in a comment. + * + * @since 0.23.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`spaced-comment`](https://eslint.style/rules/js/spaced-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/spaced-comment + */ + "spaced-comment": Linter.RuleEntry< + [ + "always" | "never", + { + exceptions: string[]; + markers: string[]; + line: { + exceptions: string[]; + markers: string[]; + }; + block: { + exceptions: string[]; + markers: string[]; + /** + * @default false + */ + balanced: boolean; + }; + }, + ] + >; + + /** + * Rule to require or disallow strict mode directives. + * + * @since 0.1.0 + * @see https://eslint.org/docs/latest/rules/strict + */ + strict: Linter.RuleEntry<["safe" | "global" | "function" | "never"]>; + + /** + * Rule to enforce spacing around colons of switch statements. + * + * @since 4.0.0-beta.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`switch-colon-spacing`](https://eslint.style/rules/js/switch-colon-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/switch-colon-spacing + */ + "switch-colon-spacing": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + before: boolean; + /** + * @default true + */ + after: boolean; + }>, + ] + >; + + /** + * Rule to require symbol descriptions. + * + * @since 3.4.0 + * @see https://eslint.org/docs/latest/rules/symbol-description + */ + "symbol-description": Linter.RuleEntry<[]>; + + /** + * Rule to require or disallow spacing around embedded expressions of template strings. + * + * @since 2.0.0-rc.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`template-curly-spacing`](https://eslint.style/rules/js/template-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/template-curly-spacing + */ + "template-curly-spacing": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require or disallow spacing between template tags and their literals. + * + * @since 3.15.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`template-tag-spacing`](https://eslint.style/rules/js/template-tag-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/template-tag-spacing + */ + "template-tag-spacing": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require or disallow Unicode byte order mark (BOM). + * + * @since 2.11.0 + * @see https://eslint.org/docs/latest/rules/unicode-bom + */ + "unicode-bom": Linter.RuleEntry<["never" | "always"]>; + + /** + * Rule to require calls to `isNaN()` when checking for `NaN`. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.0.6 + * @see https://eslint.org/docs/latest/rules/use-isnan + */ + "use-isnan": Linter.RuleEntry< + [ + Partial<{ + /** + * @default true + */ + enforceForSwitchCase: boolean; + /** + * @default true + */ + enforceForIndexOf: boolean; + }>, + ] + >; + + /** + * Rule to enforce comparing `typeof` expressions against valid strings. + * + * @remarks + * Recommended by ESLint, the rule was enabled in `eslint:recommended`. + * + * @since 0.5.0 + * @see https://eslint.org/docs/latest/rules/valid-typeof + */ + "valid-typeof": Linter.RuleEntry< + [ + Partial<{ + /** + * @default false + */ + requireStringLiterals: boolean; + }>, + ] + >; + + /** + * Rule to require `var` declarations be placed at the top of their containing scope. + * + * @since 0.8.0 + * @see https://eslint.org/docs/latest/rules/vars-on-top + */ + "vars-on-top": Linter.RuleEntry<[]>; + + /** + * Rule to require parentheses around immediate `function` invocations. + * + * @since 0.0.9 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`wrap-iife`](https://eslint.style/rules/js/wrap-iife) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/wrap-iife + */ + "wrap-iife": Linter.RuleEntry< + [ + "outside" | "inside" | "any", + Partial<{ + /** + * @default false + */ + functionPrototypeMethods: boolean; + }>, + ] + >; + + /** + * Rule to require parenthesis around regex literals. + * + * @since 0.1.0 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`wrap-regex`](https://eslint.style/rules/js/wrap-regex) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/wrap-regex + */ + "wrap-regex": Linter.RuleEntry<[]>; + + /** + * Rule to require or disallow spacing around the `*` in `yield*` expressions. + * + * @since 2.0.0-alpha-1 + * @deprecated since 8.53.0. + * Formatting rules are being moved out of ESLint core. + * Please, use [`yield-star-spacing`](https://eslint.style/rules/js/yield-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). + * @see https://eslint.org/docs/latest/rules/yield-star-spacing + */ + "yield-star-spacing": Linter.RuleEntry< + [ + | Partial<{ + before: boolean; + after: boolean; + }> + | "before" + | "after" + | "both" + | "neither", + ] + >; + + /** + * Rule to require or disallow "Yoda" conditions. + * + * @since 0.7.1 + * @see https://eslint.org/docs/latest/rules/yoda + */ + yoda: + | Linter.RuleEntry< + [ + "never", + Partial<{ + exceptRange: boolean; + onlyEquality: boolean; + }>, + ] + > + | Linter.RuleEntry<["always"]>; } diff --git a/lib/types/use-at-your-own-risk.d.ts b/lib/types/use-at-your-own-risk.d.ts index 2600b72df723..f97f4f341ea1 100644 --- a/lib/types/use-at-your-own-risk.d.ts +++ b/lib/types/use-at-your-own-risk.d.ts @@ -25,7 +25,6 @@ * SOFTWARE */ - import { ESLint, Rule } from "./index.js"; /** @deprecated */ @@ -33,52 +32,55 @@ export const builtinRules: Map; /** @deprecated */ export class FileEnumerator { - constructor( - params?: { - cwd?: string; - configArrayFactory?: any; - extensions?: any; - globInputPaths?: boolean; - errorOnUnmatchedPattern?: boolean; - ignore?: boolean; - }, - ); - isTargetPath(filePath: string, providedConfig?: any): boolean; - iterateFiles( - patternOrPatterns: string | string[], - ): IterableIterator<{ config: any; filePath: string; ignored: boolean }>; + constructor(params?: { + cwd?: string; + configArrayFactory?: any; + extensions?: any; + globInputPaths?: boolean; + errorOnUnmatchedPattern?: boolean; + ignore?: boolean; + }); + isTargetPath(filePath: string, providedConfig?: any): boolean; + iterateFiles( + patternOrPatterns: string | string[], + ): IterableIterator<{ config: any; filePath: string; ignored: boolean }>; } export { /** @deprecated */ ESLint as FlatESLint }; /** @deprecated */ export class LegacyESLint { - static configType: "eslintrc"; + static configType: "eslintrc"; - static readonly version: string; + static readonly version: string; - static outputFixes(results: ESLint.LintResult[]): Promise; + static outputFixes(results: ESLint.LintResult[]): Promise; - static getErrorResults(results: ESLint.LintResult[]): ESLint.LintResult[]; + static getErrorResults(results: ESLint.LintResult[]): ESLint.LintResult[]; - constructor(options?: ESLint.LegacyOptions); + constructor(options?: ESLint.LegacyOptions); - lintFiles(patterns: string | string[]): Promise; + lintFiles(patterns: string | string[]): Promise; - lintText( - code: string, - options?: { filePath?: string | undefined; warnIgnored?: boolean | undefined }, - ): Promise; + lintText( + code: string, + options?: { + filePath?: string | undefined; + warnIgnored?: boolean | undefined; + }, + ): Promise; - getRulesMetaForResults(results: ESLint.LintResult[]): ESLint.LintResultData["rulesMeta"]; + getRulesMetaForResults( + results: ESLint.LintResult[], + ): ESLint.LintResultData["rulesMeta"]; - hasFlag(flag: string): false; + hasFlag(flag: string): false; - calculateConfigForFile(filePath: string): Promise; + calculateConfigForFile(filePath: string): Promise; - isPathIgnored(filePath: string): Promise; + isPathIgnored(filePath: string): Promise; - loadFormatter(nameOrPath?: string): Promise; + loadFormatter(nameOrPath?: string): Promise; } /** @deprecated */ diff --git a/lib/unsupported-api.js b/lib/unsupported-api.js index 1feb18f42693..2287728e1963 100644 --- a/lib/unsupported-api.js +++ b/lib/unsupported-api.js @@ -21,9 +21,9 @@ const builtinRules = require("./rules"); //----------------------------------------------------------------------------- module.exports = { - builtinRules, - FlatESLint, - shouldUseFlatConfig, - FileEnumerator, - LegacyESLint + builtinRules, + FlatESLint, + shouldUseFlatConfig, + FileEnumerator, + LegacyESLint, }; diff --git a/messages/all-files-ignored.js b/messages/all-files-ignored.js index 70877a4d823e..3e795c9a4a44 100644 --- a/messages/all-files-ignored.js +++ b/messages/all-files-ignored.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pattern } = it; +module.exports = function (it) { + const { pattern } = it; - return ` + return ` You are linting "${pattern}", but all of the files matching the glob pattern "${pattern}" are ignored. If you don't want to lint these files, remove the pattern "${pattern}" from the list of arguments passed to ESLint. diff --git a/messages/all-matched-files-ignored.js b/messages/all-matched-files-ignored.js index b568bec82397..2621343bf0c2 100644 --- a/messages/all-matched-files-ignored.js +++ b/messages/all-matched-files-ignored.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pattern } = it; +module.exports = function (it) { + const { pattern } = it; - return ` + return ` You are linting "${pattern}", but all of the files matching the glob pattern "${pattern}" are ignored. If you don't want to lint these files, remove the pattern "${pattern}" from the list of arguments passed to ESLint. diff --git a/messages/config-file-missing.js b/messages/config-file-missing.js index a416a87d3439..c4f8ecdbf162 100644 --- a/messages/config-file-missing.js +++ b/messages/config-file-missing.js @@ -1,7 +1,7 @@ "use strict"; -module.exports = function() { - return ` +module.exports = function () { + return ` ESLint couldn't find an eslint.config.(js|mjs|cjs) file. From ESLint v9.0.0, the default configuration file is now eslint.config.js. diff --git a/messages/config-plugin-missing.js b/messages/config-plugin-missing.js index 20695c5de6a8..207367491ba3 100644 --- a/messages/config-plugin-missing.js +++ b/messages/config-plugin-missing.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pluginName, ruleId } = it; +module.exports = function (it) { + const { pluginName, ruleId } = it; - return ` + return ` A configuration object specifies rule "${ruleId}", but could not find plugin "${pluginName}". Common causes of this problem include: diff --git a/messages/config-serialize-function.js b/messages/config-serialize-function.js index eefc1551e763..4300e47e452a 100644 --- a/messages/config-serialize-function.js +++ b/messages/config-serialize-function.js @@ -1,10 +1,10 @@ "use strict"; -module.exports = function({ key, objectKey }) { - - // special case for parsers - const isParser = objectKey === "parser" && (key === "parse" || key === "parseForESLint"); - const parserMessage = ` +module.exports = function ({ key, objectKey }) { + // special case for parsers + const isParser = + objectKey === "parser" && (key === "parse" || key === "parseForESLint"); + const parserMessage = ` This typically happens when you're using a custom parser that does not provide a "meta" property, which is how ESLint determines the serialized representation. Please open an issue with the maintainer of the custom parser @@ -13,13 +13,15 @@ and share this link: https://eslint.org/docs/latest/extend/custom-parsers#meta-data-in-custom-parsers `.trim(); - return ` + return ` The requested operation requires ESLint to serialize configuration data, but the configuration key "${objectKey}.${key}" contains a function value, which cannot be serialized. ${ - isParser ? parserMessage : "Please double-check your configuration for errors." + isParser + ? parserMessage + : "Please double-check your configuration for errors." } If you still have problems, please stop by https://eslint.org/chat/help to chat diff --git a/messages/eslintrc-incompat.js b/messages/eslintrc-incompat.js index b89c39bd88ba..27fe8c9f4b18 100644 --- a/messages/eslintrc-incompat.js +++ b/messages/eslintrc-incompat.js @@ -3,8 +3,7 @@ /* eslint consistent-return: 0 -- no default case */ const messages = { - - env: ` + env: ` A config object is using the "env" key, which is not supported in flat config system. Flat config uses "languageOptions.globals" to define global variables for your files. @@ -16,7 +15,7 @@ If you're not using "env" directly (it may be coming from a plugin), please see https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - extends: ` + extends: ` A config object is using the "extends" key, which is not supported in flat config system. Instead of "extends", you can include config objects that you'd like to extend from directly in the flat config array. @@ -28,7 +27,7 @@ If you're not using "extends" directly (it may be coming from a plugin), please https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - globals: ` + globals: ` A config object is using the "globals" key, which is not supported in flat config system. Flat config uses "languageOptions.globals" to define global variables for your files. @@ -40,7 +39,7 @@ If you're not using "globals" directly (it may be coming from a plugin), please https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - ignorePatterns: ` + ignorePatterns: ` A config object is using the "ignorePatterns" key, which is not supported in flat config system. Flat config uses "ignores" to specify files to ignore. @@ -52,7 +51,7 @@ If you're not using "ignorePatterns" directly (it may be coming from a plugin), https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - noInlineConfig: ` + noInlineConfig: ` A config object is using the "noInlineConfig" key, which is not supported in flat config system. Flat config uses "linterOptions.noInlineConfig" to specify files to ignore. @@ -61,7 +60,7 @@ Please see the following page for information on how to convert your config obje https://eslint.org/docs/latest/use/configure/migration-guide#linter-options `, - overrides: ` + overrides: ` A config object is using the "overrides" key, which is not supported in flat config system. Flat config is an array that acts like the eslintrc "overrides" array. @@ -73,7 +72,7 @@ If you're not using "overrides" directly (it may be coming from a plugin), pleas https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - parser: ` + parser: ` A config object is using the "parser" key, which is not supported in flat config system. Flat config uses "languageOptions.parser" to override the default parser. @@ -85,7 +84,7 @@ If you're not using "parser" directly (it may be coming from a plugin), please s https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - parserOptions: ` + parserOptions: ` A config object is using the "parserOptions" key, which is not supported in flat config system. Flat config uses "languageOptions.parserOptions" to specify parser options. @@ -97,7 +96,7 @@ If you're not using "parserOptions" directly (it may be coming from a plugin), p https://eslint.org/docs/latest/use/configure/migration-guide#using-eslintrc-configs-in-flat-config `, - reportUnusedDisableDirectives: ` + reportUnusedDisableDirectives: ` A config object is using the "reportUnusedDisableDirectives" key, which is not supported in flat config system. Flat config uses "linterOptions.reportUnusedDisableDirectives" to specify files to ignore. @@ -106,14 +105,13 @@ Please see the following page for information on how to convert your config obje https://eslint.org/docs/latest/use/configure/migration-guide#linter-options `, - root: ` + root: ` A config object is using the "root" key, which is not supported in flat config system. Flat configs always act as if they are the root config file, so this key can be safely removed. -` +`, }; -module.exports = function({ key }) { - - return messages[key].trim(); +module.exports = function ({ key }) { + return messages[key].trim(); }; diff --git a/messages/eslintrc-plugins.js b/messages/eslintrc-plugins.js index cd0c8032c4f7..68cee2099f17 100644 --- a/messages/eslintrc-plugins.js +++ b/messages/eslintrc-plugins.js @@ -1,10 +1,9 @@ "use strict"; -module.exports = function({ plugins }) { +module.exports = function ({ plugins }) { + const isArrayOfStrings = typeof plugins[0] === "string"; - const isArrayOfStrings = typeof plugins[0] === "string"; - - return ` + return ` A config object has a "plugins" key defined as an array${isArrayOfStrings ? " of strings" : ""}. It looks something like this: { diff --git a/messages/extend-config-missing.js b/messages/extend-config-missing.js index 5b3498fcda40..32e41e960c8c 100644 --- a/messages/extend-config-missing.js +++ b/messages/extend-config-missing.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { configName, importerName } = it; +module.exports = function (it) { + const { configName, importerName } = it; - return ` + return ` ESLint couldn't find the config "${configName}" to extend from. Please check that the name of the config is correct. The config "${configName}" was referenced from the config file in "${importerName}". diff --git a/messages/failed-to-read-json.js b/messages/failed-to-read-json.js index e7c6cb58759f..3244e8428767 100644 --- a/messages/failed-to-read-json.js +++ b/messages/failed-to-read-json.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { path, message } = it; +module.exports = function (it) { + const { path, message } = it; - return ` + return ` Failed to read JSON file at ${path}: ${message} diff --git a/messages/file-not-found.js b/messages/file-not-found.js index 1a62fcf96b9e..50e483bc1a3f 100644 --- a/messages/file-not-found.js +++ b/messages/file-not-found.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pattern, globDisabled } = it; +module.exports = function (it) { + const { pattern, globDisabled } = it; - return ` + return ` No files matching the pattern "${pattern}"${globDisabled ? " (with disabling globs)" : ""} were found. Please check for typing mistakes in the pattern. `.trimStart(); diff --git a/messages/invalid-rule-options.js b/messages/invalid-rule-options.js index 9a8acc934ae4..05354cb48951 100644 --- a/messages/invalid-rule-options.js +++ b/messages/invalid-rule-options.js @@ -2,8 +2,8 @@ const { stringifyValueForError } = require("./shared"); -module.exports = function({ ruleId, value }) { - return ` +module.exports = function ({ ruleId, value }) { + return ` Configuration for rule "${ruleId}" is invalid. Each rule must have a severity ("off", 0, "warn", 1, "error", or 2) and may be followed by additional options for the rule. You passed '${stringifyValueForError(value, 4)}', which doesn't contain a valid severity. diff --git a/messages/invalid-rule-severity.js b/messages/invalid-rule-severity.js index 3f13183c6a89..17e9b5d46e3f 100644 --- a/messages/invalid-rule-severity.js +++ b/messages/invalid-rule-severity.js @@ -2,8 +2,8 @@ const { stringifyValueForError } = require("./shared"); -module.exports = function({ ruleId, value }) { - return ` +module.exports = function ({ ruleId, value }) { + return ` Configuration for rule "${ruleId}" is invalid. Expected severity of "off", 0, "warn", 1, "error", or 2. You passed '${stringifyValueForError(value, 4)}'. diff --git a/messages/no-config-found.js b/messages/no-config-found.js index 64b93edbca13..4421206dfa48 100644 --- a/messages/no-config-found.js +++ b/messages/no-config-found.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { directoryPath } = it; +module.exports = function (it) { + const { directoryPath } = it; - return ` + return ` ESLint couldn't find a configuration file. To set up a configuration file for this project, please run: npm init @eslint/config@latest diff --git a/messages/plugin-conflict.js b/messages/plugin-conflict.js index 4113a538fc9f..d90df0cae768 100644 --- a/messages/plugin-conflict.js +++ b/messages/plugin-conflict.js @@ -1,22 +1,22 @@ "use strict"; -module.exports = function(it) { - const { pluginId, plugins } = it; +module.exports = function (it) { + const { pluginId, plugins } = it; - let result = `ESLint couldn't determine the plugin "${pluginId}" uniquely. + let result = `ESLint couldn't determine the plugin "${pluginId}" uniquely. `; - for (const { filePath, importerName } of plugins) { - result += ` + for (const { filePath, importerName } of plugins) { + result += ` - ${filePath} (loaded in "${importerName}")`; - } + } - result += ` + result += ` Please remove the "plugins" setting from either config or remove either plugin installation. If you still can't figure out the problem, please see https://eslint.org/docs/latest/use/troubleshooting. `; - return result; + return result; }; diff --git a/messages/plugin-invalid.js b/messages/plugin-invalid.js index 4c60e41d3192..fc9ff6ab5187 100644 --- a/messages/plugin-invalid.js +++ b/messages/plugin-invalid.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { configName, importerName } = it; +module.exports = function (it) { + const { configName, importerName } = it; - return ` + return ` "${configName}" is invalid syntax for a config specifier. * If your intention is to extend from a configuration exported from the plugin, add the configuration name after a slash: e.g. "${configName}/myConfig". diff --git a/messages/plugin-missing.js b/messages/plugin-missing.js index 366ec4500e5f..6152a6a44721 100644 --- a/messages/plugin-missing.js +++ b/messages/plugin-missing.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pluginName, resolvePluginsRelativeTo, importerName } = it; +module.exports = function (it) { + const { pluginName, resolvePluginsRelativeTo, importerName } = it; - return ` + return ` ESLint couldn't find the plugin "${pluginName}". (The package "${pluginName}" was not found when loaded as a Node module from the directory "${resolvePluginsRelativeTo}".) diff --git a/messages/print-config-with-directory-path.js b/messages/print-config-with-directory-path.js index 4559c8d6de40..9f37745f20b3 100644 --- a/messages/print-config-with-directory-path.js +++ b/messages/print-config-with-directory-path.js @@ -1,7 +1,7 @@ "use strict"; -module.exports = function() { - return ` +module.exports = function () { + return ` The '--print-config' CLI option requires a path to a source code file rather than a directory. See also: https://eslint.org/docs/latest/use/command-line-interface#--print-config `.trimStart(); diff --git a/messages/shared.js b/messages/shared.js index 8c6e9b921456..4244ed680026 100644 --- a/messages/shared.js +++ b/messages/shared.js @@ -12,7 +12,12 @@ * @returns {string} The value, stringified. */ function stringifyValueForError(value, indentation) { - return value ? JSON.stringify(value, null, 4).replace(/\n/gu, `\n${" ".repeat(indentation)}`) : `${value}`; + return value + ? JSON.stringify(value, null, 4).replace( + /\n/gu, + `\n${" ".repeat(indentation)}`, + ) + : `${value}`; } module.exports = { stringifyValueForError }; diff --git a/messages/whitespace-found.js b/messages/whitespace-found.js index 8a801bcec6f8..c61602165ed4 100644 --- a/messages/whitespace-found.js +++ b/messages/whitespace-found.js @@ -1,9 +1,9 @@ "use strict"; -module.exports = function(it) { - const { pluginName } = it; +module.exports = function (it) { + const { pluginName } = it; - return ` + return ` ESLint couldn't find the plugin "${pluginName}". because there is whitespace in the name. Please check your configuration and remove all whitespace from the plugin name. If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team. diff --git a/package.json b/package.json index 1fabeebb6118..3d99c4135fec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "9.22.0", + "version": "9.23.0", "author": "Nicholas C. Zakas ", "description": "An AST-based pattern checker for JavaScript.", "type": "commonjs", @@ -68,7 +68,7 @@ "release:generate:rc": "node Makefile.js generatePrerelease -- rc", "release:publish": "node Makefile.js publishRelease", "test": "node Makefile.js test", - "test:browser": "node Makefile.js wdio", + "test:browser": "node Makefile.js cypress", "test:cli": "mocha", "test:fuzz": "node Makefile.js fuzz", "test:performance": "node Makefile.js perf", @@ -79,8 +79,7 @@ "pre-commit": "lint-staged" }, "lint-staged": { - "*.js": "trunk check --fix --filter=eslint", - "*.md": "trunk check --fix --filter=markdownlint", + "*": "trunk check --fix", "lib/rules/*.js": [ "node tools/update-eslint-all.js", "node tools/update-rule-type-headers.js", @@ -90,8 +89,7 @@ "node tools/check-rule-examples.js", "node tools/fetch-docs-links.js", "git add docs/src/_data/further_reading_links.json" - ], - "docs/**/*.svg": "trunk check --fix --filter=svgo" + ] }, "files": [ "LICENSE", @@ -109,10 +107,10 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", - "@eslint/config-helpers": "^0.1.0", + "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.22.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -146,20 +144,18 @@ "@arethetypeswrong/cli": "^0.17.0", "@babel/core": "^7.4.3", "@babel/preset-env": "^7.4.3", - "@eslint/json": "^0.10.0", + "@cypress/webpack-preprocessor": "^6.0.2", + "@eslint/json": "^0.11.0", "@trunkio/launcher": "^1.3.0", "@types/node": "^20.11.5", "@typescript-eslint/parser": "^8.4.0", - "@wdio/browser-runner": "^9.2.4", - "@wdio/cli": "^9.2.4", - "@wdio/concise-reporter": "^9.2.2", - "@wdio/mocha-framework": "^9.2.2", "babel-loader": "^8.0.5", "c8": "^7.12.0", "chai": "^4.0.1", "cheerio": "^0.22.0", "common-tags": "^1.8.0", "core-js": "^3.1.3", + "cypress": "^14.1.0", "ejs": "^3.0.2", "eslint": "file:.", "eslint-config-eslint": "file:packages/eslint-config-eslint", @@ -197,12 +193,10 @@ "proxyquire": "^2.0.1", "recast": "^0.23.0", "regenerator-runtime": "^0.14.0", - "rollup-plugin-node-polyfills": "^0.2.1", "semver": "^7.5.3", - "shelljs": "^0.8.5", + "shelljs": "^0.9.0", "sinon": "^11.0.0", "typescript": "^5.3.3", - "vite-plugin-commonjs": "^0.10.0", "webpack": "^5.23.0", "webpack-cli": "^4.5.0", "yorkie": "^2.0.0" diff --git a/packages/eslint-config-eslint/README.md b/packages/eslint-config-eslint/README.md index f5b58e485608..9b2922211c14 100644 --- a/packages/eslint-config-eslint/README.md +++ b/packages/eslint-config-eslint/README.md @@ -27,17 +27,16 @@ npm install eslint-config-eslint --save-dev In your `eslint.config.js` file, add: ```js +import { defineConfig } from "eslint/config"; import eslintConfigESLint from "eslint-config-eslint"; -export default [ - ...eslintConfigESLint -]; +export default defineConfig([eslintConfigESLint]); ``` **Note**: This configuration array contains configuration objects with the `files` property. -* `files: ["**/*.js"]`: ESM-specific configurations. -* `files: ["**/*.cjs"]`: CommonJS-specific configurations. +- `files: ["**/*.js"]`: ESM-specific configurations. +- `files: ["**/*.cjs"]`: CommonJS-specific configurations. ### CommonJS projects @@ -46,9 +45,7 @@ In your `eslint.config.js` file, add: ```js const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); -module.exports = [ - ...eslintConfigESLintCJS -]; +module.exports = [...eslintConfigESLintCJS]; ``` ### Base config @@ -58,19 +55,20 @@ Note that the above configurations are intended for files that will run in Node. Here's an example of an `eslint.config.js` file for a website project with scripts that run in browser and CommonJS configuration files and tools that run in Node.js: ```js +const { defineConfig } = require("eslint/config"); const eslintConfigESLintBase = require("eslint-config-eslint/base"); const eslintConfigESLintCJS = require("eslint-config-eslint/cjs"); -module.exports = [ - ...eslintConfigESLintBase.map(config => ({ - ...config, - files: ["scripts/*.js"] - })), - ...eslintConfigESLintCJS.map(config => ({ - ...config, - files: ["eslint.config.js", ".eleventy.js", "tools/*.js"] - })) -]; +module.exports = defineConfig([ + { + files: ["scripts/*.js"], + extends: [eslintConfigESLintBase], + }, + { + files: ["eslint.config.js", ".eleventy.js", "tools/*.js"], + extends: [eslintConfigESLintCJS], + }, +]); ``` ### Formatting config @@ -78,13 +76,11 @@ module.exports = [ Note that none of the above configurations includes formatting rules. If you want to enable formatting rules, add the formatting config. ```js +import { defineConfig } from "eslint/config"; import eslintConfigESLint from "eslint-config-eslint"; import eslintConfigESLintFormatting from "eslint-config-eslint/formatting"; -export default [ - ...eslintConfigESLint, - eslintConfigESLintFormatting -]; +export default defineConfig([eslintConfigESLint, eslintConfigESLintFormatting]); ``` ### Where to ask for help? diff --git a/packages/eslint-config-eslint/base.js b/packages/eslint-config-eslint/base.js index 22cac881afc3..f2f4580e78fd 100644 --- a/packages/eslint-config-eslint/base.js +++ b/packages/eslint-config-eslint/base.js @@ -9,276 +9,303 @@ const unicorn = require("eslint-plugin-unicorn"); /** * @type {import("eslint").Linter.Config[]} */ -const jsConfigs = [js.configs.recommended, { - name: "eslint-config-eslint/js", - rules: { - "array-callback-return": "error", - "arrow-body-style": ["error", "as-needed"], - camelcase: "error", - "class-methods-use-this": "error", - "consistent-return": "error", - curly: ["error", "all"], - "default-case": "error", - "default-case-last": "error", - "default-param-last": "error", - "dot-notation": [ - "error", - { allowKeywords: true } - ], - eqeqeq: "error", - "func-style": ["error", "declaration"], - "grouped-accessor-pairs": "error", - "guard-for-in": "error", - "new-cap": "error", - "no-alert": "error", - "no-array-constructor": "error", - "no-caller": "error", - "no-console": "error", - "no-constructor-return": "error", - "no-else-return": ["error", { allowElseIf: false } - ], - "no-eval": "error", - "no-extend-native": "error", - "no-extra-bind": "error", - "no-implied-eval": "error", - "no-inner-declarations": "error", - "no-invalid-this": "error", - "no-iterator": "error", - "no-label-var": "error", - "no-labels": "error", - "no-lone-blocks": "error", - "no-loop-func": "error", - "no-multi-str": "error", - "no-nested-ternary": "error", - "no-new": "error", - "no-new-func": "error", - "no-new-wrappers": "error", - "no-object-constructor": "error", - "no-octal-escape": "error", - "no-param-reassign": "error", - "no-proto": "error", - "no-process-exit": "off", - "no-restricted-properties": ["error", - { - object: "assert", - property: "equal", - message: "Use assert.strictEqual instead of assert.equal." - }, - { - object: "assert", - property: "notEqual", - message: "Use assert.notStrictEqual instead of assert.notEqual." - }, - { - object: "assert", - property: "deepEqual", - message: "Use assert.deepStrictEqual instead of assert.deepEqual." - }, - { - object: "assert", - property: "notDeepEqual", - message: "Use assert.notDeepStrictEqual instead of assert.notDeepEqual." - } - ], - "no-return-assign": "error", - "no-script-url": "error", - "no-self-compare": "error", - "no-sequences": "error", - "no-shadow": "error", - "no-throw-literal": "error", - "no-undef": ["error", { typeof: true }], - "no-undef-init": "error", - "no-undefined": "error", - "no-underscore-dangle": ["error", { allowAfterThis: true } - ], - "no-unmodified-loop-condition": "error", - "no-unneeded-ternary": "error", - "no-unreachable-loop": "error", - "no-unused-expressions": "error", - "no-unused-vars": ["error", { - vars: "all", - args: "after-used", - caughtErrors: "all" - } - ], - "no-use-before-define": "error", - "no-useless-assignment": "error", - "no-useless-call": "error", - "no-useless-computed-key": "error", - "no-useless-concat": "error", - "no-useless-constructor": "error", - "no-useless-rename": "error", - "no-useless-return": "error", - "no-var": "error", - "object-shorthand": ["error", - "always", - { - avoidExplicitReturnArrows: true - } - ], - "operator-assignment": "error", - "prefer-arrow-callback": "error", - "prefer-const": "error", - "prefer-exponentiation-operator": "error", - "prefer-numeric-literals": "error", - "prefer-object-has-own": "error", - "prefer-promise-reject-errors": "error", - "prefer-regex-literals": "error", - "prefer-rest-params": "error", - "prefer-spread": "error", - "prefer-template": "error", - radix: "error", - "require-unicode-regexp": "error", - strict: ["error", "global"], - "symbol-description": "error", - "unicode-bom": "error", - yoda: ["error", "never", { exceptRange: true }] - } -}]; +const jsConfigs = [ + js.configs.recommended, + { + name: "eslint-config-eslint/js", + rules: { + "array-callback-return": "error", + "arrow-body-style": ["error", "as-needed"], + camelcase: "error", + "class-methods-use-this": "error", + "consistent-return": "error", + curly: ["error", "all"], + "default-case": "error", + "default-case-last": "error", + "default-param-last": "error", + "dot-notation": ["error", { allowKeywords: true }], + eqeqeq: "error", + "func-style": ["error", "declaration"], + "grouped-accessor-pairs": "error", + "guard-for-in": "error", + "new-cap": "error", + "no-alert": "error", + "no-array-constructor": "error", + "no-caller": "error", + "no-console": "error", + "no-constructor-return": "error", + "no-else-return": ["error", { allowElseIf: false }], + "no-eval": "error", + "no-extend-native": "error", + "no-extra-bind": "error", + "no-implied-eval": "error", + "no-inner-declarations": "error", + "no-invalid-this": "error", + "no-iterator": "error", + "no-label-var": "error", + "no-labels": "error", + "no-lone-blocks": "error", + "no-loop-func": "error", + "no-multi-str": "error", + "no-nested-ternary": "error", + "no-new": "error", + "no-new-func": "error", + "no-new-wrappers": "error", + "no-object-constructor": "error", + "no-octal-escape": "error", + "no-param-reassign": "error", + "no-proto": "error", + "no-process-exit": "off", + "no-restricted-properties": [ + "error", + { + object: "assert", + property: "equal", + message: "Use assert.strictEqual instead of assert.equal.", + }, + { + object: "assert", + property: "notEqual", + message: + "Use assert.notStrictEqual instead of assert.notEqual.", + }, + { + object: "assert", + property: "deepEqual", + message: + "Use assert.deepStrictEqual instead of assert.deepEqual.", + }, + { + object: "assert", + property: "notDeepEqual", + message: + "Use assert.notDeepStrictEqual instead of assert.notDeepEqual.", + }, + ], + "no-return-assign": "error", + "no-script-url": "error", + "no-self-compare": "error", + "no-sequences": "error", + "no-shadow": "error", + "no-throw-literal": "error", + "no-undef": ["error", { typeof: true }], + "no-undef-init": "error", + "no-undefined": "error", + "no-underscore-dangle": ["error", { allowAfterThis: true }], + "no-unmodified-loop-condition": "error", + "no-unneeded-ternary": "error", + "no-unreachable-loop": "error", + "no-unused-expressions": "error", + "no-unused-vars": [ + "error", + { + vars: "all", + args: "after-used", + caughtErrors: "all", + }, + ], + "no-use-before-define": "error", + "no-useless-assignment": "error", + "no-useless-call": "error", + "no-useless-computed-key": "error", + "no-useless-concat": "error", + "no-useless-constructor": "error", + "no-useless-rename": "error", + "no-useless-return": "error", + "no-var": "error", + "object-shorthand": [ + "error", + "always", + { + avoidExplicitReturnArrows: true, + }, + ], + "operator-assignment": "error", + "prefer-arrow-callback": "error", + "prefer-const": "error", + "prefer-exponentiation-operator": "error", + "prefer-numeric-literals": "error", + "prefer-object-has-own": "error", + "prefer-promise-reject-errors": "error", + "prefer-regex-literals": "error", + "prefer-rest-params": "error", + "prefer-spread": "error", + "prefer-template": "error", + radix: "error", + "require-unicode-regexp": "error", + strict: ["error", "global"], + "symbol-description": "error", + "unicode-bom": "error", + yoda: ["error", "never", { exceptRange: true }], + }, + }, +]; // extends eslint-plugin-jsdoc's recommended config /** * @type {import("eslint").Linter.Config[]} */ -const jsdocConfigs = [jsdoc.configs["flat/recommended"], { - name: "eslint-config-eslint/jsdoc", - settings: { - jsdoc: { - mode: "typescript", - tagNamePreference: { - file: "fileoverview", - augments: "extends", - class: "constructor" - }, - preferredTypes: { - "*": { - message: "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", - replacement: "any" - }, - Any: { - message: "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", - replacement: "any" - }, - function: { - message: "Point to a `@callback` namepath or `Function` if truly arbitrary in form", - replacement: "Function" - }, - Promise: { - message: "Specify the specific Promise type, including, if necessary, the type `any`" - }, - ".<>": { - message: "Prefer type form without dot", - replacement: "<>" - }, - object: { - message: "Use the specific object type or `Object` if truly arbitrary", - replacement: "Object" - }, - array: "Array" - } - } - }, - rules: { - "jsdoc/check-syntax": "error", - "jsdoc/check-values": ["error", { allowedLicenses: true }], - "jsdoc/no-bad-blocks": "error", - "jsdoc/no-defaults": "off", - "jsdoc/require-asterisk-prefix": "error", - "jsdoc/require-description": ["error", { checkConstructors: false }], - "jsdoc/require-hyphen-before-param-description": ["error", "never"], - "jsdoc/require-returns": ["error", - { - forceRequireReturn: true, - forceReturnsWithAsync: true - } - ], - "jsdoc/require-throws": "error", - "jsdoc/tag-lines": ["error", "never", - { - tags: { - example: { lines: "always" }, - fileoverview: { lines: "any" } - }, - startLines: 0 - } - ], - "jsdoc/no-undefined-types": "off", - "jsdoc/require-yields": "off", - "jsdoc/check-access": "error", - "jsdoc/check-alignment": "error", - "jsdoc/check-param-names": "error", - "jsdoc/check-property-names": "error", - "jsdoc/check-tag-names": "error", - "jsdoc/check-types": "error", - "jsdoc/empty-tags": "error", - "jsdoc/implements-on-classes": "error", - "jsdoc/multiline-blocks": "error", - "jsdoc/no-multi-asterisks": ["error", { allowWhitespace: true }], - "jsdoc/require-jsdoc": ["error", { require: { ClassDeclaration: true } }], - "jsdoc/require-param": "error", - "jsdoc/require-param-description": "error", - "jsdoc/require-param-name": "error", - "jsdoc/require-param-type": "error", - "jsdoc/require-property": "error", - "jsdoc/require-property-description": "error", - "jsdoc/require-property-name": "error", - "jsdoc/require-property-type": "error", - "jsdoc/require-returns-check": "error", - "jsdoc/require-returns-description": "error", - "jsdoc/require-returns-type": "error", - "jsdoc/require-yields-check": "error", - "jsdoc/valid-types": "error" - } -}]; +const jsdocConfigs = [ + jsdoc.configs["flat/recommended"], + { + name: "eslint-config-eslint/jsdoc", + settings: { + jsdoc: { + mode: "typescript", + tagNamePreference: { + file: "fileoverview", + augments: "extends", + class: "constructor", + }, + preferredTypes: { + "*": { + message: + "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", + replacement: "any", + }, + Any: { + message: + "Use a more precise type or if necessary use `any` or `ArbitraryCallbackResult`", + replacement: "any", + }, + function: { + message: + "Point to a `@callback` namepath or `Function` if truly arbitrary in form", + replacement: "Function", + }, + Promise: { + message: + "Specify the specific Promise type, including, if necessary, the type `any`", + }, + ".<>": { + message: "Prefer type form without dot", + replacement: "<>", + }, + object: { + message: + "Use the specific object type or `Object` if truly arbitrary", + replacement: "Object", + }, + array: "Array", + }, + }, + }, + rules: { + "jsdoc/check-syntax": "error", + "jsdoc/check-values": ["error", { allowedLicenses: true }], + "jsdoc/no-bad-blocks": "error", + "jsdoc/no-defaults": "off", + "jsdoc/require-asterisk-prefix": "error", + "jsdoc/require-description": [ + "error", + { checkConstructors: false }, + ], + "jsdoc/require-hyphen-before-param-description": ["error", "never"], + "jsdoc/require-returns": [ + "error", + { + forceRequireReturn: true, + forceReturnsWithAsync: true, + }, + ], + "jsdoc/require-throws": "error", + "jsdoc/tag-lines": [ + "error", + "never", + { + tags: { + example: { lines: "always" }, + fileoverview: { lines: "any" }, + }, + startLines: 0, + }, + ], + "jsdoc/no-undefined-types": "off", + "jsdoc/require-yields": "off", + "jsdoc/check-access": "error", + "jsdoc/check-alignment": "error", + "jsdoc/check-param-names": "error", + "jsdoc/check-property-names": "error", + "jsdoc/check-tag-names": "error", + "jsdoc/check-types": "error", + "jsdoc/empty-tags": "error", + "jsdoc/implements-on-classes": "error", + "jsdoc/multiline-blocks": "error", + "jsdoc/no-multi-asterisks": ["error", { allowWhitespace: true }], + "jsdoc/require-jsdoc": [ + "error", + { require: { ClassDeclaration: true } }, + ], + "jsdoc/require-param": "error", + "jsdoc/require-param-description": "error", + "jsdoc/require-param-name": "error", + "jsdoc/require-param-type": "error", + "jsdoc/require-property": "error", + "jsdoc/require-property-description": "error", + "jsdoc/require-property-name": "error", + "jsdoc/require-property-type": "error", + "jsdoc/require-returns-check": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/require-returns-type": "error", + "jsdoc/require-yields-check": "error", + "jsdoc/valid-types": "error", + }, + }, +]; // extends eslint-plugin-unicorn's config /** * @type {import("eslint").Linter.Config[]} */ -const unicornConfigs = [{ - name: "eslint-config-eslint/unicorn", - plugins: { unicorn }, - rules: { - "unicorn/prefer-array-find": "error", - "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-array-flat": "error", - "unicorn/prefer-array-index-of": "error", - "unicorn/prefer-array-some": "error", - "unicorn/prefer-at": "error", - "unicorn/prefer-includes": "error", - "unicorn/prefer-set-has": "error", - "unicorn/prefer-string-slice": "error", - "unicorn/prefer-string-starts-ends-with": "error", - "unicorn/prefer-string-trim-start-end": "error" - } -}]; +const unicornConfigs = [ + { + name: "eslint-config-eslint/unicorn", + plugins: { unicorn }, + rules: { + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-flat": "error", + "unicorn/prefer-array-index-of": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-at": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-set-has": "error", + "unicorn/prefer-string-slice": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/prefer-string-trim-start-end": "error", + }, + }, +]; // extends @eslint-community/eslint-plugin-eslint-comments's recommended config /** * @type {import("eslint").Linter.Config[]} */ -const eslintCommentsConfigs = [eslintCommentsPluginConfigs.recommended, { - name: "eslint-config-eslint/eslint-comments", - rules: { - "@eslint-community/eslint-comments/disable-enable-pair": ["error"], - "@eslint-community/eslint-comments/no-unused-disable": "error", - "@eslint-community/eslint-comments/require-description": "error" - } -}]; +const eslintCommentsConfigs = [ + eslintCommentsPluginConfigs.recommended, + { + name: "eslint-config-eslint/eslint-comments", + rules: { + "@eslint-community/eslint-comments/disable-enable-pair": ["error"], + "@eslint-community/eslint-comments/no-unused-disable": "error", + "@eslint-community/eslint-comments/require-description": "error", + }, + }, +]; /** * @type {import("eslint").Linter.Config[]} */ module.exports = [ - { - name: "eslint-config-eslint/base", - linterOptions: { - reportUnusedDisableDirectives: "error", - reportUnusedInlineConfigs: "error" - } - }, - ...jsConfigs, - ...unicornConfigs, - ...jsdocConfigs, - ...eslintCommentsConfigs + { + name: "eslint-config-eslint/base", + linterOptions: { + reportUnusedDisableDirectives: "error", + reportUnusedInlineConfigs: "error", + }, + }, + ...jsConfigs, + ...unicornConfigs, + ...jsdocConfigs, + ...eslintCommentsConfigs, ]; diff --git a/packages/eslint-config-eslint/cjs.js b/packages/eslint-config-eslint/cjs.js index f50517bb12dc..078ffcb7a4c0 100644 --- a/packages/eslint-config-eslint/cjs.js +++ b/packages/eslint-config-eslint/cjs.js @@ -6,7 +6,4 @@ const { cjsConfigs } = require("./nodejs"); /** * @type {import("eslint").Linter.Config[]} */ -module.exports = [ - ...baseConfigs, - ...cjsConfigs -]; +module.exports = [...baseConfigs, ...cjsConfigs]; diff --git a/packages/eslint-config-eslint/formatting.js b/packages/eslint-config-eslint/formatting.js index 7fd7c2eadadc..e8e1986747ea 100644 --- a/packages/eslint-config-eslint/formatting.js +++ b/packages/eslint-config-eslint/formatting.js @@ -4,135 +4,135 @@ * @type {import("eslint").Linter.RulesRecord} */ const rules = { - "array-bracket-spacing": "error", - "arrow-parens": ["error", "as-needed"], - "arrow-spacing": "error", - "block-spacing": "error", - "brace-style": ["error", "1tbs"], - "comma-dangle": "error", - "comma-spacing": "error", - "comma-style": ["error", "last"], - "computed-property-spacing": "error", - "dot-location": ["error", "property"], - "eol-last": "error", - "func-call-spacing": "error", - "function-call-argument-newline": ["error", "consistent"], - "function-paren-newline": ["error", "consistent"], - "generator-star-spacing": "error", - indent: ["error", 4, { SwitchCase: 1 }], - "key-spacing": ["error", { beforeColon: false, afterColon: true }], - "keyword-spacing": "error", - "lines-around-comment": [ - "error", - { - beforeBlockComment: true, - afterBlockComment: false, - beforeLineComment: true, - afterLineComment: false - } - ], - "max-len": [ - "error", - 160, - { - ignoreComments: true, - ignoreUrls: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignoreRegExpLiterals: true - } - ], - "max-statements-per-line": "error", - "new-parens": "error", - "no-confusing-arrow": "error", - "no-extra-semi": "error", - "no-floating-decimal": "error", - "no-mixed-spaces-and-tabs": ["error", false], - "no-multi-spaces": "error", - "no-multiple-empty-lines": [ - "error", - { - max: 2, - maxBOF: 0, - maxEOF: 0 - } - ], - "no-tabs": "error", - "no-trailing-spaces": "error", - "no-whitespace-before-property": "error", - "object-curly-newline": [ - "error", - { - consistent: true, - multiline: true - } - ], - "object-curly-spacing": ["error", "always"], - "object-property-newline": [ - "error", - { - allowAllPropertiesOnSameLine: true - } - ], - "one-var-declaration-per-line": "error", - "operator-linebreak": "error", - "padding-line-between-statements": [ - "error", - { - blankLine: "always", - prev: ["const", "let", "var"], - next: "*" - }, - { - blankLine: "any", - prev: ["const", "let", "var"], - next: ["const", "let", "var"] - } - ], - quotes: ["error", "double", { avoidEscape: true }], - "quote-props": ["error", "as-needed"], - "rest-spread-spacing": "error", - semi: "error", - "semi-spacing": [ - "error", - { - before: false, - after: true - } - ], - "semi-style": "error", - "space-before-blocks": "error", - "space-before-function-paren": [ - "error", - { - anonymous: "never", - named: "never", - asyncArrow: "always" - } - ], - "space-in-parens": "error", - "space-infix-ops": "error", - "space-unary-ops": [ - "error", - { - words: true, - nonwords: false - } - ], - "spaced-comment": [ - "error", - "always", - { - exceptions: ["-"] - } - ], - "switch-colon-spacing": "error", - "template-curly-spacing": ["error", "never"], - "template-tag-spacing": "error", - "wrap-iife": "error", - "yield-star-spacing": "error" + "array-bracket-spacing": "error", + "arrow-parens": ["error", "as-needed"], + "arrow-spacing": "error", + "block-spacing": "error", + "brace-style": ["error", "1tbs"], + "comma-dangle": "error", + "comma-spacing": "error", + "comma-style": ["error", "last"], + "computed-property-spacing": "error", + "dot-location": ["error", "property"], + "eol-last": "error", + "func-call-spacing": "error", + "function-call-argument-newline": ["error", "consistent"], + "function-paren-newline": ["error", "consistent"], + "generator-star-spacing": "error", + indent: ["error", 4, { SwitchCase: 1 }], + "key-spacing": ["error", { beforeColon: false, afterColon: true }], + "keyword-spacing": "error", + "lines-around-comment": [ + "error", + { + beforeBlockComment: true, + afterBlockComment: false, + beforeLineComment: true, + afterLineComment: false, + }, + ], + "max-len": [ + "error", + 160, + { + ignoreComments: true, + ignoreUrls: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreRegExpLiterals: true, + }, + ], + "max-statements-per-line": "error", + "new-parens": "error", + "no-confusing-arrow": "error", + "no-extra-semi": "error", + "no-floating-decimal": "error", + "no-mixed-spaces-and-tabs": ["error", false], + "no-multi-spaces": "error", + "no-multiple-empty-lines": [ + "error", + { + max: 2, + maxBOF: 0, + maxEOF: 0, + }, + ], + "no-tabs": "error", + "no-trailing-spaces": "error", + "no-whitespace-before-property": "error", + "object-curly-newline": [ + "error", + { + consistent: true, + multiline: true, + }, + ], + "object-curly-spacing": ["error", "always"], + "object-property-newline": [ + "error", + { + allowAllPropertiesOnSameLine: true, + }, + ], + "one-var-declaration-per-line": "error", + "operator-linebreak": "error", + "padding-line-between-statements": [ + "error", + { + blankLine: "always", + prev: ["const", "let", "var"], + next: "*", + }, + { + blankLine: "any", + prev: ["const", "let", "var"], + next: ["const", "let", "var"], + }, + ], + quotes: ["error", "double", { avoidEscape: true }], + "quote-props": ["error", "as-needed"], + "rest-spread-spacing": "error", + semi: "error", + "semi-spacing": [ + "error", + { + before: false, + after: true, + }, + ], + "semi-style": "error", + "space-before-blocks": "error", + "space-before-function-paren": [ + "error", + { + anonymous: "never", + named: "never", + asyncArrow: "always", + }, + ], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": [ + "error", + { + words: true, + nonwords: false, + }, + ], + "spaced-comment": [ + "error", + "always", + { + exceptions: ["-"], + }, + ], + "switch-colon-spacing": "error", + "template-curly-spacing": ["error", "never"], + "template-tag-spacing": "error", + "wrap-iife": "error", + "yield-star-spacing": "error", }; module.exports = { - rules + rules, }; diff --git a/packages/eslint-config-eslint/index.js b/packages/eslint-config-eslint/index.js index 47ca942708b3..6ac15ac4835c 100644 --- a/packages/eslint-config-eslint/index.js +++ b/packages/eslint-config-eslint/index.js @@ -7,13 +7,13 @@ const { esmConfigs, cjsConfigs } = require("./nodejs"); * @type {import("eslint").Linter.Config[]} */ module.exports = [ - ...baseConfigs, - ...esmConfigs.map(config => ({ - files: ["**/*.js"], - ...config - })), - ...cjsConfigs.map(config => ({ - files: ["**/*.cjs"], - ...config - })) + ...baseConfigs, + ...esmConfigs.map(config => ({ + files: ["**/*.js"], + ...config, + })), + ...cjsConfigs.map(config => ({ + files: ["**/*.cjs"], + ...config, + })), ]; diff --git a/packages/eslint-config-eslint/nodejs.js b/packages/eslint-config-eslint/nodejs.js index 0fa76456f72a..ffe867c1e618 100644 --- a/packages/eslint-config-eslint/nodejs.js +++ b/packages/eslint-config-eslint/nodejs.js @@ -1,49 +1,49 @@ "use strict"; const { - configs: { - "flat/recommended-script": recommendedScriptConfig, - "flat/recommended-module": recommendedModuleConfig - } + configs: { + "flat/recommended-script": recommendedScriptConfig, + "flat/recommended-module": recommendedModuleConfig, + }, } = require("eslint-plugin-n"); /** * @type {import("eslint").Linter.RulesRecord} */ const sharedRules = { - "n/callback-return": ["error", ["cb", "callback", "next"]], - "n/handle-callback-err": ["error", "err"], - "n/prefer-node-protocol": "error" + "n/callback-return": ["error", ["cb", "callback", "next"]], + "n/handle-callback-err": ["error", "err"], + "n/prefer-node-protocol": "error", }; /** * @type {import("eslint").Linter.Config[]} */ const cjsConfigs = [ - recommendedScriptConfig, - { - name: "eslint-config-eslint/cjs", - rules: { - ...sharedRules, - "n/no-mixed-requires": "error", - "n/no-new-require": "error", - "n/no-path-concat": "error" - } - } + recommendedScriptConfig, + { + name: "eslint-config-eslint/cjs", + rules: { + ...sharedRules, + "n/no-mixed-requires": "error", + "n/no-new-require": "error", + "n/no-path-concat": "error", + }, + }, ]; /** * @type {import("eslint").Linter.Config[]} */ const esmConfigs = [ - recommendedModuleConfig, - { - name: "eslint-config-eslint/esm", - rules: sharedRules - } + recommendedModuleConfig, + { + name: "eslint-config-eslint/esm", + rules: sharedRules, + }, ]; module.exports = { - cjsConfigs, - esmConfigs + cjsConfigs, + esmConfigs, }; diff --git a/packages/eslint-config-eslint/tsconfig.json b/packages/eslint-config-eslint/tsconfig.json index 1f4be3264de1..b13f4f4a53e9 100644 --- a/packages/eslint-config-eslint/tsconfig.json +++ b/packages/eslint-config-eslint/tsconfig.json @@ -1,19 +1,19 @@ { - "compilerOptions": { - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "exactOptionalPropertyTypes": true, - "forceConsistentCasingInFileNames": true, - "isolatedModules": true, - "lib": ["ESNext", "DOM"], - "module": "NodeNext", - "moduleResolution": "NodeNext", - "noEmit": true, - "strict": true, - "target": "ESNext", - "useDefineForClassFields": true, - "useUnknownInCatchVariables": true, - "verbatimModuleSyntax": true - }, - "include": ["."] + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "exactOptionalPropertyTypes": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "lib": ["ESNext", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noEmit": true, + "strict": true, + "target": "ESNext", + "useDefineForClassFields": true, + "useUnknownInCatchVariables": true, + "verbatimModuleSyntax": true + }, + "include": ["."] } diff --git a/packages/eslint-config-eslint/types/formatting.d.ts b/packages/eslint-config-eslint/types/formatting.d.ts index 0453e98c2950..f2a9f874b634 100644 --- a/packages/eslint-config-eslint/types/formatting.d.ts +++ b/packages/eslint-config-eslint/types/formatting.d.ts @@ -1,7 +1,7 @@ import type { Linter } from "eslint"; declare const eslintConfigESLintFormatting: Required< - Pick + Pick >; export = eslintConfigESLintFormatting; diff --git a/packages/js/README.md b/packages/js/README.md index 04fc5b2a5887..2c4686ae1ae6 100644 --- a/packages/js/README.md +++ b/packages/js/README.md @@ -8,8 +8,8 @@ The beginnings of separating out JavaScript-specific functionality from ESLint. Right now, this plugin contains two configurations: -* `recommended` - enables the rules recommended by the ESLint team (the replacement for `"eslint:recommended"`) -* `all` - enables all ESLint rules (the replacement for `"eslint:all"`) +- `recommended` - enables the rules recommended by the ESLint team (the replacement for `"eslint:recommended"`) +- `all` - enables all ESLint rules (the replacement for `"eslint:all"`) ## Installation @@ -22,37 +22,46 @@ npm install @eslint/js -D Use in your `eslint.config.js` file anytime you want to extend one of the configs: ```js +import { defineConfig } from "eslint/config"; import js from "@eslint/js"; -export default [ - - // apply recommended rules to JS files - { - name: "your-project/recommended-rules", - files: ["**/*.js"], - rules: js.configs.recommended.rules - }, - - // apply recommended rules to JS files with an override - { - name: "your-project/recommended-rules-with-override", - files: ["**/*.js"], - rules: { - ...js.configs.recommended.rules, - "no-unused-vars": "warn" - } - }, - - // apply all rules to JS files - { - name: "your-project/all-rules", - files: ["**/*.js"], - rules: { - ...js.configs.all.rules, - "no-unused-vars": "warn" - } - } -] +export default defineConfig([ + // apply recommended rules to JS files + { + name: "your-project/recommended-rules", + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + }, + + // apply recommended rules to JS files with an override + { + name: "your-project/recommended-rules-with-override", + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + rules: { + "no-unused-vars": "warn", + }, + }, + + // apply all rules to JS files + { + name: "your-project/all-rules", + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/all"], + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` ## License diff --git a/packages/js/package.json b/packages/js/package.json index 9f34bec93866..6db814b68fe1 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,6 +1,6 @@ { "name": "@eslint/js", - "version": "9.22.0", + "version": "9.23.0", "description": "ESLint JavaScript language implementation", "main": "./src/index.js", "types": "./types/index.d.ts", diff --git a/packages/js/src/configs/eslint-all.js b/packages/js/src/configs/eslint-all.js index e7f4e0e3a11b..a854e5f16ce5 100644 --- a/packages/js/src/configs/eslint-all.js +++ b/packages/js/src/configs/eslint-all.js @@ -4,8 +4,6 @@ */ "use strict"; -/* eslint quote-props: off -- autogenerated so don't lint */ - /* * IMPORTANT! * diff --git a/packages/js/src/configs/eslint-recommended.js b/packages/js/src/configs/eslint-recommended.js index 3559267efc6f..d4105c1ea66b 100644 --- a/packages/js/src/configs/eslint-recommended.js +++ b/packages/js/src/configs/eslint-recommended.js @@ -17,67 +17,67 @@ */ module.exports = Object.freeze({ - rules: Object.freeze({ - "constructor-super": "error", - "for-direction": "error", - "getter-return": "error", - "no-async-promise-executor": "error", - "no-case-declarations": "error", - "no-class-assign": "error", - "no-compare-neg-zero": "error", - "no-cond-assign": "error", - "no-const-assign": "error", - "no-constant-binary-expression": "error", - "no-constant-condition": "error", - "no-control-regex": "error", - "no-debugger": "error", - "no-delete-var": "error", - "no-dupe-args": "error", - "no-dupe-class-members": "error", - "no-dupe-else-if": "error", - "no-dupe-keys": "error", - "no-duplicate-case": "error", - "no-empty": "error", - "no-empty-character-class": "error", - "no-empty-pattern": "error", - "no-empty-static-block": "error", - "no-ex-assign": "error", - "no-extra-boolean-cast": "error", - "no-fallthrough": "error", - "no-func-assign": "error", - "no-global-assign": "error", - "no-import-assign": "error", - "no-invalid-regexp": "error", - "no-irregular-whitespace": "error", - "no-loss-of-precision": "error", - "no-misleading-character-class": "error", - "no-new-native-nonconstructor": "error", - "no-nonoctal-decimal-escape": "error", - "no-obj-calls": "error", - "no-octal": "error", - "no-prototype-builtins": "error", - "no-redeclare": "error", - "no-regex-spaces": "error", - "no-self-assign": "error", - "no-setter-return": "error", - "no-shadow-restricted-names": "error", - "no-sparse-arrays": "error", - "no-this-before-super": "error", - "no-undef": "error", - "no-unexpected-multiline": "error", - "no-unreachable": "error", - "no-unsafe-finally": "error", - "no-unsafe-negation": "error", - "no-unsafe-optional-chaining": "error", - "no-unused-labels": "error", - "no-unused-private-class-members": "error", - "no-unused-vars": "error", - "no-useless-backreference": "error", - "no-useless-catch": "error", - "no-useless-escape": "error", - "no-with": "error", - "require-yield": "error", - "use-isnan": "error", - "valid-typeof": "error" - }) + rules: Object.freeze({ + "constructor-super": "error", + "for-direction": "error", + "getter-return": "error", + "no-async-promise-executor": "error", + "no-case-declarations": "error", + "no-class-assign": "error", + "no-compare-neg-zero": "error", + "no-cond-assign": "error", + "no-const-assign": "error", + "no-constant-binary-expression": "error", + "no-constant-condition": "error", + "no-control-regex": "error", + "no-debugger": "error", + "no-delete-var": "error", + "no-dupe-args": "error", + "no-dupe-class-members": "error", + "no-dupe-else-if": "error", + "no-dupe-keys": "error", + "no-duplicate-case": "error", + "no-empty": "error", + "no-empty-character-class": "error", + "no-empty-pattern": "error", + "no-empty-static-block": "error", + "no-ex-assign": "error", + "no-extra-boolean-cast": "error", + "no-fallthrough": "error", + "no-func-assign": "error", + "no-global-assign": "error", + "no-import-assign": "error", + "no-invalid-regexp": "error", + "no-irregular-whitespace": "error", + "no-loss-of-precision": "error", + "no-misleading-character-class": "error", + "no-new-native-nonconstructor": "error", + "no-nonoctal-decimal-escape": "error", + "no-obj-calls": "error", + "no-octal": "error", + "no-prototype-builtins": "error", + "no-redeclare": "error", + "no-regex-spaces": "error", + "no-self-assign": "error", + "no-setter-return": "error", + "no-shadow-restricted-names": "error", + "no-sparse-arrays": "error", + "no-this-before-super": "error", + "no-undef": "error", + "no-unexpected-multiline": "error", + "no-unreachable": "error", + "no-unsafe-finally": "error", + "no-unsafe-negation": "error", + "no-unsafe-optional-chaining": "error", + "no-unused-labels": "error", + "no-unused-private-class-members": "error", + "no-unused-vars": "error", + "no-useless-backreference": "error", + "no-useless-catch": "error", + "no-useless-escape": "error", + "no-with": "error", + "require-yield": "error", + "use-isnan": "error", + "valid-typeof": "error", + }), }); diff --git a/packages/js/src/index.js b/packages/js/src/index.js index ec252bcd5167..ff6a21d87564 100644 --- a/packages/js/src/index.js +++ b/packages/js/src/index.js @@ -12,12 +12,12 @@ const { name, version } = require("../package.json"); //------------------------------------------------------------------------------ module.exports = { - meta: { - name, - version - }, - configs: { - all: require("./configs/eslint-all"), - recommended: require("./configs/eslint-recommended") - } + meta: { + name, + version, + }, + configs: { + all: require("./configs/eslint-all"), + recommended: require("./configs/eslint-recommended"), + }, }; diff --git a/packages/js/tests/types/tsconfig.json b/packages/js/tests/types/tsconfig.json index d3dfbf868a9e..6f75e07f6b8c 100644 --- a/packages/js/tests/types/tsconfig.json +++ b/packages/js/tests/types/tsconfig.json @@ -1,21 +1,15 @@ { - "compilerOptions": { - "module": "node16", - "lib": [ - "dom", - "es6" - ], - "noImplicitAny": true, - "noImplicitThis": true, - "strictNullChecks": true, - "strictFunctionTypes": true, - "types": [], - "noEmit": true, - "forceConsistentCasingInFileNames": true, - "exactOptionalPropertyTypes": true - }, - "files": [ - "../../types/index.d.ts", - "types.test.ts" - ] + "compilerOptions": { + "module": "node16", + "lib": ["dom", "es6"], + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "types": [], + "noEmit": true, + "forceConsistentCasingInFileNames": true, + "exactOptionalPropertyTypes": true + }, + "files": ["../../types/index.d.ts", "types.test.ts"] } diff --git a/packages/js/tests/types/types.test.ts b/packages/js/tests/types/types.test.ts index a6129afe3271..16b9cde7b2d3 100644 --- a/packages/js/tests/types/types.test.ts +++ b/packages/js/tests/types/types.test.ts @@ -41,36 +41,36 @@ config = [js.configs.all]; config = [js.configs.recommended, js.configs.all]; config = [ - { - ...js.configs.recommended, - files: ["blah"], - }, - { - ...js.configs.all, - files: ["meh"], - }, - { - files: ["foo"], - }, + { + ...js.configs.recommended, + files: ["blah"], + }, + { + ...js.configs.all, + files: ["meh"], + }, + { + files: ["foo"], + }, ]; config = [ - { - files: ["**/*.js"], - rules: js.configs.recommended.rules, - }, - { - files: ["**/*.js"], - rules: { - ...js.configs.recommended.rules, - "no-unused-vars": "warn", - }, - }, - { - files: ["**/*.js"], - rules: { - ...js.configs.all.rules, - "no-unused-vars": "warn", - }, - }, + { + files: ["**/*.js"], + rules: js.configs.recommended.rules, + }, + { + files: ["**/*.js"], + rules: { + ...js.configs.recommended.rules, + "no-unused-vars": "warn", + }, + }, + { + files: ["**/*.js"], + rules: { + ...js.configs.all.rules, + "no-unused-vars": "warn", + }, + }, ]; diff --git a/packages/js/types/index.d.ts b/packages/js/types/index.d.ts index 92de943e0b78..d4921d8bfbfd 100644 --- a/packages/js/types/index.d.ts +++ b/packages/js/types/index.d.ts @@ -1,14 +1,14 @@ import type { Linter } from "eslint"; declare const js: { - readonly meta: { - readonly name: string; - readonly version: string; - }, - readonly configs: { - readonly recommended: { readonly rules: Readonly }; - readonly all: { readonly rules: Readonly }; - }; + readonly meta: { + readonly name: string; + readonly version: string; + }; + readonly configs: { + readonly recommended: { readonly rules: Readonly }; + readonly all: { readonly rules: Readonly }; + }; }; export = js; diff --git a/templates/bug-report.md b/templates/bug-report.md index d7a7a4049076..86812c69bc71 100644 --- a/templates/bug-report.md +++ b/templates/bug-report.md @@ -1,10 +1,10 @@ **Tell us about your environment (`npx eslint --env-info`):** -* **Node version:** -* **npm version:** -* **Local ESLint version:** -* **Global ESLint version:** -* **Operating System:** +- **Node version:** +- **npm version:** +- **Local ESLint version:** +- **Global ESLint version:** +- **Operating System:** **What parser are you using (place an "X" next to just one item)?** @@ -21,6 +21,7 @@ Configuration + ```js ``` diff --git a/tests/_utils/index.js b/tests/_utils/index.js index c1d476dc62d3..74d553a669b2 100644 --- a/tests/_utils/index.js +++ b/tests/_utils/index.js @@ -21,14 +21,19 @@ const { createTeardown, addFile } = require("fs-teardown"); * @returns {string} The template literal, with spaces removed from all lines */ function unIndent(strings, ...values) { - const text = strings - .map((s, i) => (i === 0 ? s : values[i - 1] + s)) - .join(""); - const lines = text.replace(/^\n/u, "").replace(/\n\s*$/u, "").split("\n"); - const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */u)[0].length); - const minLineIndent = Math.min(...lineIndents); - - return lines.map(line => line.slice(minLineIndent)).join("\n"); + const text = strings + .map((s, i) => (i === 0 ? s : values[i - 1] + s)) + .join(""); + const lines = text + .replace(/^\n/u, "") + .replace(/\n\s*$/u, "") + .split("\n"); + const lineIndents = lines + .filter(line => line.trim()) + .map(line => line.match(/ */u)[0].length); + const minLineIndent = Math.min(...lineIndents); + + return lines.map(line => line.slice(minLineIndent)).join("\n"); } /** @@ -44,12 +49,14 @@ function unIndent(strings, ...values) { * methods. */ function createCustomTeardown({ cwd, files }) { - const { prepare, cleanup, getPath } = createTeardown( - cwd, - ...Object.keys(files).map(filename => addFile(filename, files[filename])) - ); + const { prepare, cleanup, getPath } = createTeardown( + cwd, + ...Object.keys(files).map(filename => + addFile(filename, files[filename]), + ), + ); - return { prepare, cleanup, getPath }; + return { prepare, cleanup, getPath }; } //----------------------------------------------------------------------------- @@ -57,6 +64,6 @@ function createCustomTeardown({ cwd, files }) { //----------------------------------------------------------------------------- module.exports = { - unIndent, - createCustomTeardown + unIndent, + createCustomTeardown, }; diff --git a/tests/_utils/test-lazy-loading-rules.js b/tests/_utils/test-lazy-loading-rules.js index 6b51cf862af7..24853173fe40 100644 --- a/tests/_utils/test-lazy-loading-rules.js +++ b/tests/_utils/test-lazy-loading-rules.js @@ -15,10 +15,8 @@ const path = require("node:path"); const assert = require("node:assert"); const { addHook } = require("pirates"); -const { - dir: rulesDirectoryPath, - name: rulesDirectoryIndexFilename -} = path.parse(require.resolve("../../lib/rules")); +const { dir: rulesDirectoryPath, name: rulesDirectoryIndexFilename } = + path.parse(require.resolve("../../lib/rules")); // Show full stack trace. The default 10 is usually not enough to find the root cause of this problem. Error.stackTraceLimit = Infinity; @@ -33,34 +31,35 @@ const usedRules = usedRulesCommaSeparated.split(","); // `require()` hook addHook( - (_code, filename) => { - throw new Error(`Unexpected attempt to load unused rule ${filename}`); - }, - { + (_code, filename) => { + throw new Error(`Unexpected attempt to load unused rule ${filename}`); + }, + { + // returns `true` if the hook (the function passed in as the first argument) should be called for this filename + matcher(filename) { + const { dir, name } = path.parse(filename); - // returns `true` if the hook (the function passed in as the first argument) should be called for this filename - matcher(filename) { - const { dir, name } = path.parse(filename); + if ( + dir === rulesDirectoryPath && + ![rulesDirectoryIndexFilename, ...usedRules].includes(name) + ) { + return true; + } - if (dir === rulesDirectoryPath && ![rulesDirectoryIndexFilename, ...usedRules].includes(name)) { - return true; - } - - return false; - } - - } + return false; + }, + }, ); /* * Everything related to loading any ESLint modules should be in this IIFE */ (async () => { - const { LegacyESLint } = require("../../lib/unsupported-api"); - const eslint = new LegacyESLint({ cwd }); + const { LegacyESLint } = require("../../lib/unsupported-api"); + const eslint = new LegacyESLint({ cwd }); - await eslint.lintFiles([pattern]); + await eslint.lintFiles([pattern]); })().catch(({ message, stack }) => { - process.send({ message, stack }); - process.exit(1); // eslint-disable-line n/no-process-exit -- this is a child process + process.send({ message, stack }); + process.exit(1); // eslint-disable-line n/no-process-exit -- this is a child process }); diff --git a/tests/bench/large.js b/tests/bench/large.js index 26a1f9e266a1..130da7ab0ce8 100644 --- a/tests/bench/large.js +++ b/tests/bench/large.js @@ -1,60571 +1,19500 @@ // 2.4.3 var JSHINT; -if (typeof window === 'undefined') window = {}; +if (typeof window === "undefined") window = {}; (function () { -var require; -require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 65 && i <= 90 || // A-Z - i === 95 || // _ - i >= 97 && i <= 122; // a-z -} - -var identifierPartTable = []; - -for (var i = 0; i < 128; i++) { - identifierPartTable[i] = - identifierStartTable[i] || // $, _, A-Z, a-z - i >= 48 && i <= 57; // 0-9 -} - -module.exports = { - asciiIdentifierStartTable: identifierStartTable, - asciiIdentifierPartTable: identifierPartTable -}; - -},{}],2:[function(require,module,exports){ -module.exports = [ - 768, - 769, - 770, - 771, - 772, - 773, - 774, - 775, - 776, - 777, - 778, - 779, - 780, - 781, - 782, - 783, - 784, - 785, - 786, - 787, - 788, - 789, - 790, - 791, - 792, - 793, - 794, - 795, - 796, - 797, - 798, - 799, - 800, - 801, - 802, - 803, - 804, - 805, - 806, - 807, - 808, - 809, - 810, - 811, - 812, - 813, - 814, - 815, - 816, - 817, - 818, - 819, - 820, - 821, - 822, - 823, - 824, - 825, - 826, - 827, - 828, - 829, - 830, - 831, - 832, - 833, - 834, - 835, - 836, - 837, - 838, - 839, - 840, - 841, - 842, - 843, - 844, - 845, - 846, - 847, - 848, - 849, - 850, - 851, - 852, - 853, - 854, - 855, - 856, - 857, - 858, - 859, - 860, - 861, - 862, - 863, - 864, - 865, - 866, - 867, - 868, - 869, - 870, - 871, - 872, - 873, - 874, - 875, - 876, - 877, - 878, - 879, - 1155, - 1156, - 1157, - 1158, - 1159, - 1425, - 1426, - 1427, - 1428, - 1429, - 1430, - 1431, - 1432, - 1433, - 1434, - 1435, - 1436, - 1437, - 1438, - 1439, - 1440, - 1441, - 1442, - 1443, - 1444, - 1445, - 1446, - 1447, - 1448, - 1449, - 1450, - 1451, - 1452, - 1453, - 1454, - 1455, - 1456, - 1457, - 1458, - 1459, - 1460, - 1461, - 1462, - 1463, - 1464, - 1465, - 1466, - 1467, - 1468, - 1469, - 1471, - 1473, - 1474, - 1476, - 1477, - 1479, - 1552, - 1553, - 1554, - 1555, - 1556, - 1557, - 1558, - 1559, - 1560, - 1561, - 1562, - 1611, - 1612, - 1613, - 1614, - 1615, - 1616, - 1617, - 1618, - 1619, - 1620, - 1621, - 1622, - 1623, - 1624, - 1625, - 1626, - 1627, - 1628, - 1629, - 1630, - 1631, - 1632, - 1633, - 1634, - 1635, - 1636, - 1637, - 1638, - 1639, - 1640, - 1641, - 1648, - 1750, - 1751, - 1752, - 1753, - 1754, - 1755, - 1756, - 1759, - 1760, - 1761, - 1762, - 1763, - 1764, - 1767, - 1768, - 1770, - 1771, - 1772, - 1773, - 1776, - 1777, - 1778, - 1779, - 1780, - 1781, - 1782, - 1783, - 1784, - 1785, - 1809, - 1840, - 1841, - 1842, - 1843, - 1844, - 1845, - 1846, - 1847, - 1848, - 1849, - 1850, - 1851, - 1852, - 1853, - 1854, - 1855, - 1856, - 1857, - 1858, - 1859, - 1860, - 1861, - 1862, - 1863, - 1864, - 1865, - 1866, - 1958, - 1959, - 1960, - 1961, - 1962, - 1963, - 1964, - 1965, - 1966, - 1967, - 1968, - 1984, - 1985, - 1986, - 1987, - 1988, - 1989, - 1990, - 1991, - 1992, - 1993, - 2027, - 2028, - 2029, - 2030, - 2031, - 2032, - 2033, - 2034, - 2035, - 2070, - 2071, - 2072, - 2073, - 2075, - 2076, - 2077, - 2078, - 2079, - 2080, - 2081, - 2082, - 2083, - 2085, - 2086, - 2087, - 2089, - 2090, - 2091, - 2092, - 2093, - 2137, - 2138, - 2139, - 2276, - 2277, - 2278, - 2279, - 2280, - 2281, - 2282, - 2283, - 2284, - 2285, - 2286, - 2287, - 2288, - 2289, - 2290, - 2291, - 2292, - 2293, - 2294, - 2295, - 2296, - 2297, - 2298, - 2299, - 2300, - 2301, - 2302, - 2304, - 2305, - 2306, - 2307, - 2362, - 2363, - 2364, - 2366, - 2367, - 2368, - 2369, - 2370, - 2371, - 2372, - 2373, - 2374, - 2375, - 2376, - 2377, - 2378, - 2379, - 2380, - 2381, - 2382, - 2383, - 2385, - 2386, - 2387, - 2388, - 2389, - 2390, - 2391, - 2402, - 2403, - 2406, - 2407, - 2408, - 2409, - 2410, - 2411, - 2412, - 2413, - 2414, - 2415, - 2433, - 2434, - 2435, - 2492, - 2494, - 2495, - 2496, - 2497, - 2498, - 2499, - 2500, - 2503, - 2504, - 2507, - 2508, - 2509, - 2519, - 2530, - 2531, - 2534, - 2535, - 2536, - 2537, - 2538, - 2539, - 2540, - 2541, - 2542, - 2543, - 2561, - 2562, - 2563, - 2620, - 2622, - 2623, - 2624, - 2625, - 2626, - 2631, - 2632, - 2635, - 2636, - 2637, - 2641, - 2662, - 2663, - 2664, - 2665, - 2666, - 2667, - 2668, - 2669, - 2670, - 2671, - 2672, - 2673, - 2677, - 2689, - 2690, - 2691, - 2748, - 2750, - 2751, - 2752, - 2753, - 2754, - 2755, - 2756, - 2757, - 2759, - 2760, - 2761, - 2763, - 2764, - 2765, - 2786, - 2787, - 2790, - 2791, - 2792, - 2793, - 2794, - 2795, - 2796, - 2797, - 2798, - 2799, - 2817, - 2818, - 2819, - 2876, - 2878, - 2879, - 2880, - 2881, - 2882, - 2883, - 2884, - 2887, - 2888, - 2891, - 2892, - 2893, - 2902, - 2903, - 2914, - 2915, - 2918, - 2919, - 2920, - 2921, - 2922, - 2923, - 2924, - 2925, - 2926, - 2927, - 2946, - 3006, - 3007, - 3008, - 3009, - 3010, - 3014, - 3015, - 3016, - 3018, - 3019, - 3020, - 3021, - 3031, - 3046, - 3047, - 3048, - 3049, - 3050, - 3051, - 3052, - 3053, - 3054, - 3055, - 3073, - 3074, - 3075, - 3134, - 3135, - 3136, - 3137, - 3138, - 3139, - 3140, - 3142, - 3143, - 3144, - 3146, - 3147, - 3148, - 3149, - 3157, - 3158, - 3170, - 3171, - 3174, - 3175, - 3176, - 3177, - 3178, - 3179, - 3180, - 3181, - 3182, - 3183, - 3202, - 3203, - 3260, - 3262, - 3263, - 3264, - 3265, - 3266, - 3267, - 3268, - 3270, - 3271, - 3272, - 3274, - 3275, - 3276, - 3277, - 3285, - 3286, - 3298, - 3299, - 3302, - 3303, - 3304, - 3305, - 3306, - 3307, - 3308, - 3309, - 3310, - 3311, - 3330, - 3331, - 3390, - 3391, - 3392, - 3393, - 3394, - 3395, - 3396, - 3398, - 3399, - 3400, - 3402, - 3403, - 3404, - 3405, - 3415, - 3426, - 3427, - 3430, - 3431, - 3432, - 3433, - 3434, - 3435, - 3436, - 3437, - 3438, - 3439, - 3458, - 3459, - 3530, - 3535, - 3536, - 3537, - 3538, - 3539, - 3540, - 3542, - 3544, - 3545, - 3546, - 3547, - 3548, - 3549, - 3550, - 3551, - 3570, - 3571, - 3633, - 3636, - 3637, - 3638, - 3639, - 3640, - 3641, - 3642, - 3655, - 3656, - 3657, - 3658, - 3659, - 3660, - 3661, - 3662, - 3664, - 3665, - 3666, - 3667, - 3668, - 3669, - 3670, - 3671, - 3672, - 3673, - 3761, - 3764, - 3765, - 3766, - 3767, - 3768, - 3769, - 3771, - 3772, - 3784, - 3785, - 3786, - 3787, - 3788, - 3789, - 3792, - 3793, - 3794, - 3795, - 3796, - 3797, - 3798, - 3799, - 3800, - 3801, - 3864, - 3865, - 3872, - 3873, - 3874, - 3875, - 3876, - 3877, - 3878, - 3879, - 3880, - 3881, - 3893, - 3895, - 3897, - 3902, - 3903, - 3953, - 3954, - 3955, - 3956, - 3957, - 3958, - 3959, - 3960, - 3961, - 3962, - 3963, - 3964, - 3965, - 3966, - 3967, - 3968, - 3969, - 3970, - 3971, - 3972, - 3974, - 3975, - 3981, - 3982, - 3983, - 3984, - 3985, - 3986, - 3987, - 3988, - 3989, - 3990, - 3991, - 3993, - 3994, - 3995, - 3996, - 3997, - 3998, - 3999, - 4000, - 4001, - 4002, - 4003, - 4004, - 4005, - 4006, - 4007, - 4008, - 4009, - 4010, - 4011, - 4012, - 4013, - 4014, - 4015, - 4016, - 4017, - 4018, - 4019, - 4020, - 4021, - 4022, - 4023, - 4024, - 4025, - 4026, - 4027, - 4028, - 4038, - 4139, - 4140, - 4141, - 4142, - 4143, - 4144, - 4145, - 4146, - 4147, - 4148, - 4149, - 4150, - 4151, - 4152, - 4153, - 4154, - 4155, - 4156, - 4157, - 4158, - 4160, - 4161, - 4162, - 4163, - 4164, - 4165, - 4166, - 4167, - 4168, - 4169, - 4182, - 4183, - 4184, - 4185, - 4190, - 4191, - 4192, - 4194, - 4195, - 4196, - 4199, - 4200, - 4201, - 4202, - 4203, - 4204, - 4205, - 4209, - 4210, - 4211, - 4212, - 4226, - 4227, - 4228, - 4229, - 4230, - 4231, - 4232, - 4233, - 4234, - 4235, - 4236, - 4237, - 4239, - 4240, - 4241, - 4242, - 4243, - 4244, - 4245, - 4246, - 4247, - 4248, - 4249, - 4250, - 4251, - 4252, - 4253, - 4957, - 4958, - 4959, - 5906, - 5907, - 5908, - 5938, - 5939, - 5940, - 5970, - 5971, - 6002, - 6003, - 6068, - 6069, - 6070, - 6071, - 6072, - 6073, - 6074, - 6075, - 6076, - 6077, - 6078, - 6079, - 6080, - 6081, - 6082, - 6083, - 6084, - 6085, - 6086, - 6087, - 6088, - 6089, - 6090, - 6091, - 6092, - 6093, - 6094, - 6095, - 6096, - 6097, - 6098, - 6099, - 6109, - 6112, - 6113, - 6114, - 6115, - 6116, - 6117, - 6118, - 6119, - 6120, - 6121, - 6155, - 6156, - 6157, - 6160, - 6161, - 6162, - 6163, - 6164, - 6165, - 6166, - 6167, - 6168, - 6169, - 6313, - 6432, - 6433, - 6434, - 6435, - 6436, - 6437, - 6438, - 6439, - 6440, - 6441, - 6442, - 6443, - 6448, - 6449, - 6450, - 6451, - 6452, - 6453, - 6454, - 6455, - 6456, - 6457, - 6458, - 6459, - 6470, - 6471, - 6472, - 6473, - 6474, - 6475, - 6476, - 6477, - 6478, - 6479, - 6576, - 6577, - 6578, - 6579, - 6580, - 6581, - 6582, - 6583, - 6584, - 6585, - 6586, - 6587, - 6588, - 6589, - 6590, - 6591, - 6592, - 6600, - 6601, - 6608, - 6609, - 6610, - 6611, - 6612, - 6613, - 6614, - 6615, - 6616, - 6617, - 6679, - 6680, - 6681, - 6682, - 6683, - 6741, - 6742, - 6743, - 6744, - 6745, - 6746, - 6747, - 6748, - 6749, - 6750, - 6752, - 6753, - 6754, - 6755, - 6756, - 6757, - 6758, - 6759, - 6760, - 6761, - 6762, - 6763, - 6764, - 6765, - 6766, - 6767, - 6768, - 6769, - 6770, - 6771, - 6772, - 6773, - 6774, - 6775, - 6776, - 6777, - 6778, - 6779, - 6780, - 6783, - 6784, - 6785, - 6786, - 6787, - 6788, - 6789, - 6790, - 6791, - 6792, - 6793, - 6800, - 6801, - 6802, - 6803, - 6804, - 6805, - 6806, - 6807, - 6808, - 6809, - 6912, - 6913, - 6914, - 6915, - 6916, - 6964, - 6965, - 6966, - 6967, - 6968, - 6969, - 6970, - 6971, - 6972, - 6973, - 6974, - 6975, - 6976, - 6977, - 6978, - 6979, - 6980, - 6992, - 6993, - 6994, - 6995, - 6996, - 6997, - 6998, - 6999, - 7000, - 7001, - 7019, - 7020, - 7021, - 7022, - 7023, - 7024, - 7025, - 7026, - 7027, - 7040, - 7041, - 7042, - 7073, - 7074, - 7075, - 7076, - 7077, - 7078, - 7079, - 7080, - 7081, - 7082, - 7083, - 7084, - 7085, - 7088, - 7089, - 7090, - 7091, - 7092, - 7093, - 7094, - 7095, - 7096, - 7097, - 7142, - 7143, - 7144, - 7145, - 7146, - 7147, - 7148, - 7149, - 7150, - 7151, - 7152, - 7153, - 7154, - 7155, - 7204, - 7205, - 7206, - 7207, - 7208, - 7209, - 7210, - 7211, - 7212, - 7213, - 7214, - 7215, - 7216, - 7217, - 7218, - 7219, - 7220, - 7221, - 7222, - 7223, - 7232, - 7233, - 7234, - 7235, - 7236, - 7237, - 7238, - 7239, - 7240, - 7241, - 7248, - 7249, - 7250, - 7251, - 7252, - 7253, - 7254, - 7255, - 7256, - 7257, - 7376, - 7377, - 7378, - 7380, - 7381, - 7382, - 7383, - 7384, - 7385, - 7386, - 7387, - 7388, - 7389, - 7390, - 7391, - 7392, - 7393, - 7394, - 7395, - 7396, - 7397, - 7398, - 7399, - 7400, - 7405, - 7410, - 7411, - 7412, - 7616, - 7617, - 7618, - 7619, - 7620, - 7621, - 7622, - 7623, - 7624, - 7625, - 7626, - 7627, - 7628, - 7629, - 7630, - 7631, - 7632, - 7633, - 7634, - 7635, - 7636, - 7637, - 7638, - 7639, - 7640, - 7641, - 7642, - 7643, - 7644, - 7645, - 7646, - 7647, - 7648, - 7649, - 7650, - 7651, - 7652, - 7653, - 7654, - 7676, - 7677, - 7678, - 7679, - 8204, - 8205, - 8255, - 8256, - 8276, - 8400, - 8401, - 8402, - 8403, - 8404, - 8405, - 8406, - 8407, - 8408, - 8409, - 8410, - 8411, - 8412, - 8417, - 8421, - 8422, - 8423, - 8424, - 8425, - 8426, - 8427, - 8428, - 8429, - 8430, - 8431, - 8432, - 11503, - 11504, - 11505, - 11647, - 11744, - 11745, - 11746, - 11747, - 11748, - 11749, - 11750, - 11751, - 11752, - 11753, - 11754, - 11755, - 11756, - 11757, - 11758, - 11759, - 11760, - 11761, - 11762, - 11763, - 11764, - 11765, - 11766, - 11767, - 11768, - 11769, - 11770, - 11771, - 11772, - 11773, - 11774, - 11775, - 12330, - 12331, - 12332, - 12333, - 12334, - 12335, - 12441, - 12442, - 42528, - 42529, - 42530, - 42531, - 42532, - 42533, - 42534, - 42535, - 42536, - 42537, - 42607, - 42612, - 42613, - 42614, - 42615, - 42616, - 42617, - 42618, - 42619, - 42620, - 42621, - 42655, - 42736, - 42737, - 43010, - 43014, - 43019, - 43043, - 43044, - 43045, - 43046, - 43047, - 43136, - 43137, - 43188, - 43189, - 43190, - 43191, - 43192, - 43193, - 43194, - 43195, - 43196, - 43197, - 43198, - 43199, - 43200, - 43201, - 43202, - 43203, - 43204, - 43216, - 43217, - 43218, - 43219, - 43220, - 43221, - 43222, - 43223, - 43224, - 43225, - 43232, - 43233, - 43234, - 43235, - 43236, - 43237, - 43238, - 43239, - 43240, - 43241, - 43242, - 43243, - 43244, - 43245, - 43246, - 43247, - 43248, - 43249, - 43264, - 43265, - 43266, - 43267, - 43268, - 43269, - 43270, - 43271, - 43272, - 43273, - 43302, - 43303, - 43304, - 43305, - 43306, - 43307, - 43308, - 43309, - 43335, - 43336, - 43337, - 43338, - 43339, - 43340, - 43341, - 43342, - 43343, - 43344, - 43345, - 43346, - 43347, - 43392, - 43393, - 43394, - 43395, - 43443, - 43444, - 43445, - 43446, - 43447, - 43448, - 43449, - 43450, - 43451, - 43452, - 43453, - 43454, - 43455, - 43456, - 43472, - 43473, - 43474, - 43475, - 43476, - 43477, - 43478, - 43479, - 43480, - 43481, - 43561, - 43562, - 43563, - 43564, - 43565, - 43566, - 43567, - 43568, - 43569, - 43570, - 43571, - 43572, - 43573, - 43574, - 43587, - 43596, - 43597, - 43600, - 43601, - 43602, - 43603, - 43604, - 43605, - 43606, - 43607, - 43608, - 43609, - 43643, - 43696, - 43698, - 43699, - 43700, - 43703, - 43704, - 43710, - 43711, - 43713, - 43755, - 43756, - 43757, - 43758, - 43759, - 43765, - 43766, - 44003, - 44004, - 44005, - 44006, - 44007, - 44008, - 44009, - 44010, - 44012, - 44013, - 44016, - 44017, - 44018, - 44019, - 44020, - 44021, - 44022, - 44023, - 44024, - 44025, - 64286, - 65024, - 65025, - 65026, - 65027, - 65028, - 65029, - 65030, - 65031, - 65032, - 65033, - 65034, - 65035, - 65036, - 65037, - 65038, - 65039, - 65056, - 65057, - 65058, - 65059, - 65060, - 65061, - 65062, - 65075, - 65076, - 65101, - 65102, - 65103, - 65296, - 65297, - 65298, - 65299, - 65300, - 65301, - 65302, - 65303, - 65304, - 65305, - 65343 -]; - -},{}],3:[function(require,module,exports){ -module.exports = [ - 170, - 181, - 186, - 192, - 193, - 194, - 195, - 196, - 197, - 198, - 199, - 200, - 201, - 202, - 203, - 204, - 205, - 206, - 207, - 208, - 209, - 210, - 211, - 212, - 213, - 214, - 216, - 217, - 218, - 219, - 220, - 221, - 222, - 223, - 224, - 225, - 226, - 227, - 228, - 229, - 230, - 231, - 232, - 233, - 234, - 235, - 236, - 237, - 238, - 239, - 240, - 241, - 242, - 243, - 244, - 245, - 246, - 248, - 249, - 250, - 251, - 252, - 253, - 254, - 255, - 256, - 257, - 258, - 259, - 260, - 261, - 262, - 263, - 264, - 265, - 266, - 267, - 268, - 269, - 270, - 271, - 272, - 273, - 274, - 275, - 276, - 277, - 278, - 279, - 280, - 281, - 282, - 283, - 284, - 285, - 286, - 287, - 288, - 289, - 290, - 291, - 292, - 293, - 294, - 295, - 296, - 297, - 298, - 299, - 300, - 301, - 302, - 303, - 304, - 305, - 306, - 307, - 308, - 309, - 310, - 311, - 312, - 313, - 314, - 315, - 316, - 317, - 318, - 319, - 320, - 321, - 322, - 323, - 324, - 325, - 326, - 327, - 328, - 329, - 330, - 331, - 332, - 333, - 334, - 335, - 336, - 337, - 338, - 339, - 340, - 341, - 342, - 343, - 344, - 345, - 346, - 347, - 348, - 349, - 350, - 351, - 352, - 353, - 354, - 355, - 356, - 357, - 358, - 359, - 360, - 361, - 362, - 363, - 364, - 365, - 366, - 367, - 368, - 369, - 370, - 371, - 372, - 373, - 374, - 375, - 376, - 377, - 378, - 379, - 380, - 381, - 382, - 383, - 384, - 385, - 386, - 387, - 388, - 389, - 390, - 391, - 392, - 393, - 394, - 395, - 396, - 397, - 398, - 399, - 400, - 401, - 402, - 403, - 404, - 405, - 406, - 407, - 408, - 409, - 410, - 411, - 412, - 413, - 414, - 415, - 416, - 417, - 418, - 419, - 420, - 421, - 422, - 423, - 424, - 425, - 426, - 427, - 428, - 429, - 430, - 431, - 432, - 433, - 434, - 435, - 436, - 437, - 438, - 439, - 440, - 441, - 442, - 443, - 444, - 445, - 446, - 447, - 448, - 449, - 450, - 451, - 452, - 453, - 454, - 455, - 456, - 457, - 458, - 459, - 460, - 461, - 462, - 463, - 464, - 465, - 466, - 467, - 468, - 469, - 470, - 471, - 472, - 473, - 474, - 475, - 476, - 477, - 478, - 479, - 480, - 481, - 482, - 483, - 484, - 485, - 486, - 487, - 488, - 489, - 490, - 491, - 492, - 493, - 494, - 495, - 496, - 497, - 498, - 499, - 500, - 501, - 502, - 503, - 504, - 505, - 506, - 507, - 508, - 509, - 510, - 511, - 512, - 513, - 514, - 515, - 516, - 517, - 518, - 519, - 520, - 521, - 522, - 523, - 524, - 525, - 526, - 527, - 528, - 529, - 530, - 531, - 532, - 533, - 534, - 535, - 536, - 537, - 538, - 539, - 540, - 541, - 542, - 543, - 544, - 545, - 546, - 547, - 548, - 549, - 550, - 551, - 552, - 553, - 554, - 555, - 556, - 557, - 558, - 559, - 560, - 561, - 562, - 563, - 564, - 565, - 566, - 567, - 568, - 569, - 570, - 571, - 572, - 573, - 574, - 575, - 576, - 577, - 578, - 579, - 580, - 581, - 582, - 583, - 584, - 585, - 586, - 587, - 588, - 589, - 590, - 591, - 592, - 593, - 594, - 595, - 596, - 597, - 598, - 599, - 600, - 601, - 602, - 603, - 604, - 605, - 606, - 607, - 608, - 609, - 610, - 611, - 612, - 613, - 614, - 615, - 616, - 617, - 618, - 619, - 620, - 621, - 622, - 623, - 624, - 625, - 626, - 627, - 628, - 629, - 630, - 631, - 632, - 633, - 634, - 635, - 636, - 637, - 638, - 639, - 640, - 641, - 642, - 643, - 644, - 645, - 646, - 647, - 648, - 649, - 650, - 651, - 652, - 653, - 654, - 655, - 656, - 657, - 658, - 659, - 660, - 661, - 662, - 663, - 664, - 665, - 666, - 667, - 668, - 669, - 670, - 671, - 672, - 673, - 674, - 675, - 676, - 677, - 678, - 679, - 680, - 681, - 682, - 683, - 684, - 685, - 686, - 687, - 688, - 689, - 690, - 691, - 692, - 693, - 694, - 695, - 696, - 697, - 698, - 699, - 700, - 701, - 702, - 703, - 704, - 705, - 710, - 711, - 712, - 713, - 714, - 715, - 716, - 717, - 718, - 719, - 720, - 721, - 736, - 737, - 738, - 739, - 740, - 748, - 750, - 880, - 881, - 882, - 883, - 884, - 886, - 887, - 890, - 891, - 892, - 893, - 902, - 904, - 905, - 906, - 908, - 910, - 911, - 912, - 913, - 914, - 915, - 916, - 917, - 918, - 919, - 920, - 921, - 922, - 923, - 924, - 925, - 926, - 927, - 928, - 929, - 931, - 932, - 933, - 934, - 935, - 936, - 937, - 938, - 939, - 940, - 941, - 942, - 943, - 944, - 945, - 946, - 947, - 948, - 949, - 950, - 951, - 952, - 953, - 954, - 955, - 956, - 957, - 958, - 959, - 960, - 961, - 962, - 963, - 964, - 965, - 966, - 967, - 968, - 969, - 970, - 971, - 972, - 973, - 974, - 975, - 976, - 977, - 978, - 979, - 980, - 981, - 982, - 983, - 984, - 985, - 986, - 987, - 988, - 989, - 990, - 991, - 992, - 993, - 994, - 995, - 996, - 997, - 998, - 999, - 1000, - 1001, - 1002, - 1003, - 1004, - 1005, - 1006, - 1007, - 1008, - 1009, - 1010, - 1011, - 1012, - 1013, - 1015, - 1016, - 1017, - 1018, - 1019, - 1020, - 1021, - 1022, - 1023, - 1024, - 1025, - 1026, - 1027, - 1028, - 1029, - 1030, - 1031, - 1032, - 1033, - 1034, - 1035, - 1036, - 1037, - 1038, - 1039, - 1040, - 1041, - 1042, - 1043, - 1044, - 1045, - 1046, - 1047, - 1048, - 1049, - 1050, - 1051, - 1052, - 1053, - 1054, - 1055, - 1056, - 1057, - 1058, - 1059, - 1060, - 1061, - 1062, - 1063, - 1064, - 1065, - 1066, - 1067, - 1068, - 1069, - 1070, - 1071, - 1072, - 1073, - 1074, - 1075, - 1076, - 1077, - 1078, - 1079, - 1080, - 1081, - 1082, - 1083, - 1084, - 1085, - 1086, - 1087, - 1088, - 1089, - 1090, - 1091, - 1092, - 1093, - 1094, - 1095, - 1096, - 1097, - 1098, - 1099, - 1100, - 1101, - 1102, - 1103, - 1104, - 1105, - 1106, - 1107, - 1108, - 1109, - 1110, - 1111, - 1112, - 1113, - 1114, - 1115, - 1116, - 1117, - 1118, - 1119, - 1120, - 1121, - 1122, - 1123, - 1124, - 1125, - 1126, - 1127, - 1128, - 1129, - 1130, - 1131, - 1132, - 1133, - 1134, - 1135, - 1136, - 1137, - 1138, - 1139, - 1140, - 1141, - 1142, - 1143, - 1144, - 1145, - 1146, - 1147, - 1148, - 1149, - 1150, - 1151, - 1152, - 1153, - 1162, - 1163, - 1164, - 1165, - 1166, - 1167, - 1168, - 1169, - 1170, - 1171, - 1172, - 1173, - 1174, - 1175, - 1176, - 1177, - 1178, - 1179, - 1180, - 1181, - 1182, - 1183, - 1184, - 1185, - 1186, - 1187, - 1188, - 1189, - 1190, - 1191, - 1192, - 1193, - 1194, - 1195, - 1196, - 1197, - 1198, - 1199, - 1200, - 1201, - 1202, - 1203, - 1204, - 1205, - 1206, - 1207, - 1208, - 1209, - 1210, - 1211, - 1212, - 1213, - 1214, - 1215, - 1216, - 1217, - 1218, - 1219, - 1220, - 1221, - 1222, - 1223, - 1224, - 1225, - 1226, - 1227, - 1228, - 1229, - 1230, - 1231, - 1232, - 1233, - 1234, - 1235, - 1236, - 1237, - 1238, - 1239, - 1240, - 1241, - 1242, - 1243, - 1244, - 1245, - 1246, - 1247, - 1248, - 1249, - 1250, - 1251, - 1252, - 1253, - 1254, - 1255, - 1256, - 1257, - 1258, - 1259, - 1260, - 1261, - 1262, - 1263, - 1264, - 1265, - 1266, - 1267, - 1268, - 1269, - 1270, - 1271, - 1272, - 1273, - 1274, - 1275, - 1276, - 1277, - 1278, - 1279, - 1280, - 1281, - 1282, - 1283, - 1284, - 1285, - 1286, - 1287, - 1288, - 1289, - 1290, - 1291, - 1292, - 1293, - 1294, - 1295, - 1296, - 1297, - 1298, - 1299, - 1300, - 1301, - 1302, - 1303, - 1304, - 1305, - 1306, - 1307, - 1308, - 1309, - 1310, - 1311, - 1312, - 1313, - 1314, - 1315, - 1316, - 1317, - 1318, - 1319, - 1329, - 1330, - 1331, - 1332, - 1333, - 1334, - 1335, - 1336, - 1337, - 1338, - 1339, - 1340, - 1341, - 1342, - 1343, - 1344, - 1345, - 1346, - 1347, - 1348, - 1349, - 1350, - 1351, - 1352, - 1353, - 1354, - 1355, - 1356, - 1357, - 1358, - 1359, - 1360, - 1361, - 1362, - 1363, - 1364, - 1365, - 1366, - 1369, - 1377, - 1378, - 1379, - 1380, - 1381, - 1382, - 1383, - 1384, - 1385, - 1386, - 1387, - 1388, - 1389, - 1390, - 1391, - 1392, - 1393, - 1394, - 1395, - 1396, - 1397, - 1398, - 1399, - 1400, - 1401, - 1402, - 1403, - 1404, - 1405, - 1406, - 1407, - 1408, - 1409, - 1410, - 1411, - 1412, - 1413, - 1414, - 1415, - 1488, - 1489, - 1490, - 1491, - 1492, - 1493, - 1494, - 1495, - 1496, - 1497, - 1498, - 1499, - 1500, - 1501, - 1502, - 1503, - 1504, - 1505, - 1506, - 1507, - 1508, - 1509, - 1510, - 1511, - 1512, - 1513, - 1514, - 1520, - 1521, - 1522, - 1568, - 1569, - 1570, - 1571, - 1572, - 1573, - 1574, - 1575, - 1576, - 1577, - 1578, - 1579, - 1580, - 1581, - 1582, - 1583, - 1584, - 1585, - 1586, - 1587, - 1588, - 1589, - 1590, - 1591, - 1592, - 1593, - 1594, - 1595, - 1596, - 1597, - 1598, - 1599, - 1600, - 1601, - 1602, - 1603, - 1604, - 1605, - 1606, - 1607, - 1608, - 1609, - 1610, - 1646, - 1647, - 1649, - 1650, - 1651, - 1652, - 1653, - 1654, - 1655, - 1656, - 1657, - 1658, - 1659, - 1660, - 1661, - 1662, - 1663, - 1664, - 1665, - 1666, - 1667, - 1668, - 1669, - 1670, - 1671, - 1672, - 1673, - 1674, - 1675, - 1676, - 1677, - 1678, - 1679, - 1680, - 1681, - 1682, - 1683, - 1684, - 1685, - 1686, - 1687, - 1688, - 1689, - 1690, - 1691, - 1692, - 1693, - 1694, - 1695, - 1696, - 1697, - 1698, - 1699, - 1700, - 1701, - 1702, - 1703, - 1704, - 1705, - 1706, - 1707, - 1708, - 1709, - 1710, - 1711, - 1712, - 1713, - 1714, - 1715, - 1716, - 1717, - 1718, - 1719, - 1720, - 1721, - 1722, - 1723, - 1724, - 1725, - 1726, - 1727, - 1728, - 1729, - 1730, - 1731, - 1732, - 1733, - 1734, - 1735, - 1736, - 1737, - 1738, - 1739, - 1740, - 1741, - 1742, - 1743, - 1744, - 1745, - 1746, - 1747, - 1749, - 1765, - 1766, - 1774, - 1775, - 1786, - 1787, - 1788, - 1791, - 1808, - 1810, - 1811, - 1812, - 1813, - 1814, - 1815, - 1816, - 1817, - 1818, - 1819, - 1820, - 1821, - 1822, - 1823, - 1824, - 1825, - 1826, - 1827, - 1828, - 1829, - 1830, - 1831, - 1832, - 1833, - 1834, - 1835, - 1836, - 1837, - 1838, - 1839, - 1869, - 1870, - 1871, - 1872, - 1873, - 1874, - 1875, - 1876, - 1877, - 1878, - 1879, - 1880, - 1881, - 1882, - 1883, - 1884, - 1885, - 1886, - 1887, - 1888, - 1889, - 1890, - 1891, - 1892, - 1893, - 1894, - 1895, - 1896, - 1897, - 1898, - 1899, - 1900, - 1901, - 1902, - 1903, - 1904, - 1905, - 1906, - 1907, - 1908, - 1909, - 1910, - 1911, - 1912, - 1913, - 1914, - 1915, - 1916, - 1917, - 1918, - 1919, - 1920, - 1921, - 1922, - 1923, - 1924, - 1925, - 1926, - 1927, - 1928, - 1929, - 1930, - 1931, - 1932, - 1933, - 1934, - 1935, - 1936, - 1937, - 1938, - 1939, - 1940, - 1941, - 1942, - 1943, - 1944, - 1945, - 1946, - 1947, - 1948, - 1949, - 1950, - 1951, - 1952, - 1953, - 1954, - 1955, - 1956, - 1957, - 1969, - 1994, - 1995, - 1996, - 1997, - 1998, - 1999, - 2000, - 2001, - 2002, - 2003, - 2004, - 2005, - 2006, - 2007, - 2008, - 2009, - 2010, - 2011, - 2012, - 2013, - 2014, - 2015, - 2016, - 2017, - 2018, - 2019, - 2020, - 2021, - 2022, - 2023, - 2024, - 2025, - 2026, - 2036, - 2037, - 2042, - 2048, - 2049, - 2050, - 2051, - 2052, - 2053, - 2054, - 2055, - 2056, - 2057, - 2058, - 2059, - 2060, - 2061, - 2062, - 2063, - 2064, - 2065, - 2066, - 2067, - 2068, - 2069, - 2074, - 2084, - 2088, - 2112, - 2113, - 2114, - 2115, - 2116, - 2117, - 2118, - 2119, - 2120, - 2121, - 2122, - 2123, - 2124, - 2125, - 2126, - 2127, - 2128, - 2129, - 2130, - 2131, - 2132, - 2133, - 2134, - 2135, - 2136, - 2208, - 2210, - 2211, - 2212, - 2213, - 2214, - 2215, - 2216, - 2217, - 2218, - 2219, - 2220, - 2308, - 2309, - 2310, - 2311, - 2312, - 2313, - 2314, - 2315, - 2316, - 2317, - 2318, - 2319, - 2320, - 2321, - 2322, - 2323, - 2324, - 2325, - 2326, - 2327, - 2328, - 2329, - 2330, - 2331, - 2332, - 2333, - 2334, - 2335, - 2336, - 2337, - 2338, - 2339, - 2340, - 2341, - 2342, - 2343, - 2344, - 2345, - 2346, - 2347, - 2348, - 2349, - 2350, - 2351, - 2352, - 2353, - 2354, - 2355, - 2356, - 2357, - 2358, - 2359, - 2360, - 2361, - 2365, - 2384, - 2392, - 2393, - 2394, - 2395, - 2396, - 2397, - 2398, - 2399, - 2400, - 2401, - 2417, - 2418, - 2419, - 2420, - 2421, - 2422, - 2423, - 2425, - 2426, - 2427, - 2428, - 2429, - 2430, - 2431, - 2437, - 2438, - 2439, - 2440, - 2441, - 2442, - 2443, - 2444, - 2447, - 2448, - 2451, - 2452, - 2453, - 2454, - 2455, - 2456, - 2457, - 2458, - 2459, - 2460, - 2461, - 2462, - 2463, - 2464, - 2465, - 2466, - 2467, - 2468, - 2469, - 2470, - 2471, - 2472, - 2474, - 2475, - 2476, - 2477, - 2478, - 2479, - 2480, - 2482, - 2486, - 2487, - 2488, - 2489, - 2493, - 2510, - 2524, - 2525, - 2527, - 2528, - 2529, - 2544, - 2545, - 2565, - 2566, - 2567, - 2568, - 2569, - 2570, - 2575, - 2576, - 2579, - 2580, - 2581, - 2582, - 2583, - 2584, - 2585, - 2586, - 2587, - 2588, - 2589, - 2590, - 2591, - 2592, - 2593, - 2594, - 2595, - 2596, - 2597, - 2598, - 2599, - 2600, - 2602, - 2603, - 2604, - 2605, - 2606, - 2607, - 2608, - 2610, - 2611, - 2613, - 2614, - 2616, - 2617, - 2649, - 2650, - 2651, - 2652, - 2654, - 2674, - 2675, - 2676, - 2693, - 2694, - 2695, - 2696, - 2697, - 2698, - 2699, - 2700, - 2701, - 2703, - 2704, - 2705, - 2707, - 2708, - 2709, - 2710, - 2711, - 2712, - 2713, - 2714, - 2715, - 2716, - 2717, - 2718, - 2719, - 2720, - 2721, - 2722, - 2723, - 2724, - 2725, - 2726, - 2727, - 2728, - 2730, - 2731, - 2732, - 2733, - 2734, - 2735, - 2736, - 2738, - 2739, - 2741, - 2742, - 2743, - 2744, - 2745, - 2749, - 2768, - 2784, - 2785, - 2821, - 2822, - 2823, - 2824, - 2825, - 2826, - 2827, - 2828, - 2831, - 2832, - 2835, - 2836, - 2837, - 2838, - 2839, - 2840, - 2841, - 2842, - 2843, - 2844, - 2845, - 2846, - 2847, - 2848, - 2849, - 2850, - 2851, - 2852, - 2853, - 2854, - 2855, - 2856, - 2858, - 2859, - 2860, - 2861, - 2862, - 2863, - 2864, - 2866, - 2867, - 2869, - 2870, - 2871, - 2872, - 2873, - 2877, - 2908, - 2909, - 2911, - 2912, - 2913, - 2929, - 2947, - 2949, - 2950, - 2951, - 2952, - 2953, - 2954, - 2958, - 2959, - 2960, - 2962, - 2963, - 2964, - 2965, - 2969, - 2970, - 2972, - 2974, - 2975, - 2979, - 2980, - 2984, - 2985, - 2986, - 2990, - 2991, - 2992, - 2993, - 2994, - 2995, - 2996, - 2997, - 2998, - 2999, - 3000, - 3001, - 3024, - 3077, - 3078, - 3079, - 3080, - 3081, - 3082, - 3083, - 3084, - 3086, - 3087, - 3088, - 3090, - 3091, - 3092, - 3093, - 3094, - 3095, - 3096, - 3097, - 3098, - 3099, - 3100, - 3101, - 3102, - 3103, - 3104, - 3105, - 3106, - 3107, - 3108, - 3109, - 3110, - 3111, - 3112, - 3114, - 3115, - 3116, - 3117, - 3118, - 3119, - 3120, - 3121, - 3122, - 3123, - 3125, - 3126, - 3127, - 3128, - 3129, - 3133, - 3160, - 3161, - 3168, - 3169, - 3205, - 3206, - 3207, - 3208, - 3209, - 3210, - 3211, - 3212, - 3214, - 3215, - 3216, - 3218, - 3219, - 3220, - 3221, - 3222, - 3223, - 3224, - 3225, - 3226, - 3227, - 3228, - 3229, - 3230, - 3231, - 3232, - 3233, - 3234, - 3235, - 3236, - 3237, - 3238, - 3239, - 3240, - 3242, - 3243, - 3244, - 3245, - 3246, - 3247, - 3248, - 3249, - 3250, - 3251, - 3253, - 3254, - 3255, - 3256, - 3257, - 3261, - 3294, - 3296, - 3297, - 3313, - 3314, - 3333, - 3334, - 3335, - 3336, - 3337, - 3338, - 3339, - 3340, - 3342, - 3343, - 3344, - 3346, - 3347, - 3348, - 3349, - 3350, - 3351, - 3352, - 3353, - 3354, - 3355, - 3356, - 3357, - 3358, - 3359, - 3360, - 3361, - 3362, - 3363, - 3364, - 3365, - 3366, - 3367, - 3368, - 3369, - 3370, - 3371, - 3372, - 3373, - 3374, - 3375, - 3376, - 3377, - 3378, - 3379, - 3380, - 3381, - 3382, - 3383, - 3384, - 3385, - 3386, - 3389, - 3406, - 3424, - 3425, - 3450, - 3451, - 3452, - 3453, - 3454, - 3455, - 3461, - 3462, - 3463, - 3464, - 3465, - 3466, - 3467, - 3468, - 3469, - 3470, - 3471, - 3472, - 3473, - 3474, - 3475, - 3476, - 3477, - 3478, - 3482, - 3483, - 3484, - 3485, - 3486, - 3487, - 3488, - 3489, - 3490, - 3491, - 3492, - 3493, - 3494, - 3495, - 3496, - 3497, - 3498, - 3499, - 3500, - 3501, - 3502, - 3503, - 3504, - 3505, - 3507, - 3508, - 3509, - 3510, - 3511, - 3512, - 3513, - 3514, - 3515, - 3517, - 3520, - 3521, - 3522, - 3523, - 3524, - 3525, - 3526, - 3585, - 3586, - 3587, - 3588, - 3589, - 3590, - 3591, - 3592, - 3593, - 3594, - 3595, - 3596, - 3597, - 3598, - 3599, - 3600, - 3601, - 3602, - 3603, - 3604, - 3605, - 3606, - 3607, - 3608, - 3609, - 3610, - 3611, - 3612, - 3613, - 3614, - 3615, - 3616, - 3617, - 3618, - 3619, - 3620, - 3621, - 3622, - 3623, - 3624, - 3625, - 3626, - 3627, - 3628, - 3629, - 3630, - 3631, - 3632, - 3634, - 3635, - 3648, - 3649, - 3650, - 3651, - 3652, - 3653, - 3654, - 3713, - 3714, - 3716, - 3719, - 3720, - 3722, - 3725, - 3732, - 3733, - 3734, - 3735, - 3737, - 3738, - 3739, - 3740, - 3741, - 3742, - 3743, - 3745, - 3746, - 3747, - 3749, - 3751, - 3754, - 3755, - 3757, - 3758, - 3759, - 3760, - 3762, - 3763, - 3773, - 3776, - 3777, - 3778, - 3779, - 3780, - 3782, - 3804, - 3805, - 3806, - 3807, - 3840, - 3904, - 3905, - 3906, - 3907, - 3908, - 3909, - 3910, - 3911, - 3913, - 3914, - 3915, - 3916, - 3917, - 3918, - 3919, - 3920, - 3921, - 3922, - 3923, - 3924, - 3925, - 3926, - 3927, - 3928, - 3929, - 3930, - 3931, - 3932, - 3933, - 3934, - 3935, - 3936, - 3937, - 3938, - 3939, - 3940, - 3941, - 3942, - 3943, - 3944, - 3945, - 3946, - 3947, - 3948, - 3976, - 3977, - 3978, - 3979, - 3980, - 4096, - 4097, - 4098, - 4099, - 4100, - 4101, - 4102, - 4103, - 4104, - 4105, - 4106, - 4107, - 4108, - 4109, - 4110, - 4111, - 4112, - 4113, - 4114, - 4115, - 4116, - 4117, - 4118, - 4119, - 4120, - 4121, - 4122, - 4123, - 4124, - 4125, - 4126, - 4127, - 4128, - 4129, - 4130, - 4131, - 4132, - 4133, - 4134, - 4135, - 4136, - 4137, - 4138, - 4159, - 4176, - 4177, - 4178, - 4179, - 4180, - 4181, - 4186, - 4187, - 4188, - 4189, - 4193, - 4197, - 4198, - 4206, - 4207, - 4208, - 4213, - 4214, - 4215, - 4216, - 4217, - 4218, - 4219, - 4220, - 4221, - 4222, - 4223, - 4224, - 4225, - 4238, - 4256, - 4257, - 4258, - 4259, - 4260, - 4261, - 4262, - 4263, - 4264, - 4265, - 4266, - 4267, - 4268, - 4269, - 4270, - 4271, - 4272, - 4273, - 4274, - 4275, - 4276, - 4277, - 4278, - 4279, - 4280, - 4281, - 4282, - 4283, - 4284, - 4285, - 4286, - 4287, - 4288, - 4289, - 4290, - 4291, - 4292, - 4293, - 4295, - 4301, - 4304, - 4305, - 4306, - 4307, - 4308, - 4309, - 4310, - 4311, - 4312, - 4313, - 4314, - 4315, - 4316, - 4317, - 4318, - 4319, - 4320, - 4321, - 4322, - 4323, - 4324, - 4325, - 4326, - 4327, - 4328, - 4329, - 4330, - 4331, - 4332, - 4333, - 4334, - 4335, - 4336, - 4337, - 4338, - 4339, - 4340, - 4341, - 4342, - 4343, - 4344, - 4345, - 4346, - 4348, - 4349, - 4350, - 4351, - 4352, - 4353, - 4354, - 4355, - 4356, - 4357, - 4358, - 4359, - 4360, - 4361, - 4362, - 4363, - 4364, - 4365, - 4366, - 4367, - 4368, - 4369, - 4370, - 4371, - 4372, - 4373, - 4374, - 4375, - 4376, - 4377, - 4378, - 4379, - 4380, - 4381, - 4382, - 4383, - 4384, - 4385, - 4386, - 4387, - 4388, - 4389, - 4390, - 4391, - 4392, - 4393, - 4394, - 4395, - 4396, - 4397, - 4398, - 4399, - 4400, - 4401, - 4402, - 4403, - 4404, - 4405, - 4406, - 4407, - 4408, - 4409, - 4410, - 4411, - 4412, - 4413, - 4414, - 4415, - 4416, - 4417, - 4418, - 4419, - 4420, - 4421, - 4422, - 4423, - 4424, - 4425, - 4426, - 4427, - 4428, - 4429, - 4430, - 4431, - 4432, - 4433, - 4434, - 4435, - 4436, - 4437, - 4438, - 4439, - 4440, - 4441, - 4442, - 4443, - 4444, - 4445, - 4446, - 4447, - 4448, - 4449, - 4450, - 4451, - 4452, - 4453, - 4454, - 4455, - 4456, - 4457, - 4458, - 4459, - 4460, - 4461, - 4462, - 4463, - 4464, - 4465, - 4466, - 4467, - 4468, - 4469, - 4470, - 4471, - 4472, - 4473, - 4474, - 4475, - 4476, - 4477, - 4478, - 4479, - 4480, - 4481, - 4482, - 4483, - 4484, - 4485, - 4486, - 4487, - 4488, - 4489, - 4490, - 4491, - 4492, - 4493, - 4494, - 4495, - 4496, - 4497, - 4498, - 4499, - 4500, - 4501, - 4502, - 4503, - 4504, - 4505, - 4506, - 4507, - 4508, - 4509, - 4510, - 4511, - 4512, - 4513, - 4514, - 4515, - 4516, - 4517, - 4518, - 4519, - 4520, - 4521, - 4522, - 4523, - 4524, - 4525, - 4526, - 4527, - 4528, - 4529, - 4530, - 4531, - 4532, - 4533, - 4534, - 4535, - 4536, - 4537, - 4538, - 4539, - 4540, - 4541, - 4542, - 4543, - 4544, - 4545, - 4546, - 4547, - 4548, - 4549, - 4550, - 4551, - 4552, - 4553, - 4554, - 4555, - 4556, - 4557, - 4558, - 4559, - 4560, - 4561, - 4562, - 4563, - 4564, - 4565, - 4566, - 4567, - 4568, - 4569, - 4570, - 4571, - 4572, - 4573, - 4574, - 4575, - 4576, - 4577, - 4578, - 4579, - 4580, - 4581, - 4582, - 4583, - 4584, - 4585, - 4586, - 4587, - 4588, - 4589, - 4590, - 4591, - 4592, - 4593, - 4594, - 4595, - 4596, - 4597, - 4598, - 4599, - 4600, - 4601, - 4602, - 4603, - 4604, - 4605, - 4606, - 4607, - 4608, - 4609, - 4610, - 4611, - 4612, - 4613, - 4614, - 4615, - 4616, - 4617, - 4618, - 4619, - 4620, - 4621, - 4622, - 4623, - 4624, - 4625, - 4626, - 4627, - 4628, - 4629, - 4630, - 4631, - 4632, - 4633, - 4634, - 4635, - 4636, - 4637, - 4638, - 4639, - 4640, - 4641, - 4642, - 4643, - 4644, - 4645, - 4646, - 4647, - 4648, - 4649, - 4650, - 4651, - 4652, - 4653, - 4654, - 4655, - 4656, - 4657, - 4658, - 4659, - 4660, - 4661, - 4662, - 4663, - 4664, - 4665, - 4666, - 4667, - 4668, - 4669, - 4670, - 4671, - 4672, - 4673, - 4674, - 4675, - 4676, - 4677, - 4678, - 4679, - 4680, - 4682, - 4683, - 4684, - 4685, - 4688, - 4689, - 4690, - 4691, - 4692, - 4693, - 4694, - 4696, - 4698, - 4699, - 4700, - 4701, - 4704, - 4705, - 4706, - 4707, - 4708, - 4709, - 4710, - 4711, - 4712, - 4713, - 4714, - 4715, - 4716, - 4717, - 4718, - 4719, - 4720, - 4721, - 4722, - 4723, - 4724, - 4725, - 4726, - 4727, - 4728, - 4729, - 4730, - 4731, - 4732, - 4733, - 4734, - 4735, - 4736, - 4737, - 4738, - 4739, - 4740, - 4741, - 4742, - 4743, - 4744, - 4746, - 4747, - 4748, - 4749, - 4752, - 4753, - 4754, - 4755, - 4756, - 4757, - 4758, - 4759, - 4760, - 4761, - 4762, - 4763, - 4764, - 4765, - 4766, - 4767, - 4768, - 4769, - 4770, - 4771, - 4772, - 4773, - 4774, - 4775, - 4776, - 4777, - 4778, - 4779, - 4780, - 4781, - 4782, - 4783, - 4784, - 4786, - 4787, - 4788, - 4789, - 4792, - 4793, - 4794, - 4795, - 4796, - 4797, - 4798, - 4800, - 4802, - 4803, - 4804, - 4805, - 4808, - 4809, - 4810, - 4811, - 4812, - 4813, - 4814, - 4815, - 4816, - 4817, - 4818, - 4819, - 4820, - 4821, - 4822, - 4824, - 4825, - 4826, - 4827, - 4828, - 4829, - 4830, - 4831, - 4832, - 4833, - 4834, - 4835, - 4836, - 4837, - 4838, - 4839, - 4840, - 4841, - 4842, - 4843, - 4844, - 4845, - 4846, - 4847, - 4848, - 4849, - 4850, - 4851, - 4852, - 4853, - 4854, - 4855, - 4856, - 4857, - 4858, - 4859, - 4860, - 4861, - 4862, - 4863, - 4864, - 4865, - 4866, - 4867, - 4868, - 4869, - 4870, - 4871, - 4872, - 4873, - 4874, - 4875, - 4876, - 4877, - 4878, - 4879, - 4880, - 4882, - 4883, - 4884, - 4885, - 4888, - 4889, - 4890, - 4891, - 4892, - 4893, - 4894, - 4895, - 4896, - 4897, - 4898, - 4899, - 4900, - 4901, - 4902, - 4903, - 4904, - 4905, - 4906, - 4907, - 4908, - 4909, - 4910, - 4911, - 4912, - 4913, - 4914, - 4915, - 4916, - 4917, - 4918, - 4919, - 4920, - 4921, - 4922, - 4923, - 4924, - 4925, - 4926, - 4927, - 4928, - 4929, - 4930, - 4931, - 4932, - 4933, - 4934, - 4935, - 4936, - 4937, - 4938, - 4939, - 4940, - 4941, - 4942, - 4943, - 4944, - 4945, - 4946, - 4947, - 4948, - 4949, - 4950, - 4951, - 4952, - 4953, - 4954, - 4992, - 4993, - 4994, - 4995, - 4996, - 4997, - 4998, - 4999, - 5000, - 5001, - 5002, - 5003, - 5004, - 5005, - 5006, - 5007, - 5024, - 5025, - 5026, - 5027, - 5028, - 5029, - 5030, - 5031, - 5032, - 5033, - 5034, - 5035, - 5036, - 5037, - 5038, - 5039, - 5040, - 5041, - 5042, - 5043, - 5044, - 5045, - 5046, - 5047, - 5048, - 5049, - 5050, - 5051, - 5052, - 5053, - 5054, - 5055, - 5056, - 5057, - 5058, - 5059, - 5060, - 5061, - 5062, - 5063, - 5064, - 5065, - 5066, - 5067, - 5068, - 5069, - 5070, - 5071, - 5072, - 5073, - 5074, - 5075, - 5076, - 5077, - 5078, - 5079, - 5080, - 5081, - 5082, - 5083, - 5084, - 5085, - 5086, - 5087, - 5088, - 5089, - 5090, - 5091, - 5092, - 5093, - 5094, - 5095, - 5096, - 5097, - 5098, - 5099, - 5100, - 5101, - 5102, - 5103, - 5104, - 5105, - 5106, - 5107, - 5108, - 5121, - 5122, - 5123, - 5124, - 5125, - 5126, - 5127, - 5128, - 5129, - 5130, - 5131, - 5132, - 5133, - 5134, - 5135, - 5136, - 5137, - 5138, - 5139, - 5140, - 5141, - 5142, - 5143, - 5144, - 5145, - 5146, - 5147, - 5148, - 5149, - 5150, - 5151, - 5152, - 5153, - 5154, - 5155, - 5156, - 5157, - 5158, - 5159, - 5160, - 5161, - 5162, - 5163, - 5164, - 5165, - 5166, - 5167, - 5168, - 5169, - 5170, - 5171, - 5172, - 5173, - 5174, - 5175, - 5176, - 5177, - 5178, - 5179, - 5180, - 5181, - 5182, - 5183, - 5184, - 5185, - 5186, - 5187, - 5188, - 5189, - 5190, - 5191, - 5192, - 5193, - 5194, - 5195, - 5196, - 5197, - 5198, - 5199, - 5200, - 5201, - 5202, - 5203, - 5204, - 5205, - 5206, - 5207, - 5208, - 5209, - 5210, - 5211, - 5212, - 5213, - 5214, - 5215, - 5216, - 5217, - 5218, - 5219, - 5220, - 5221, - 5222, - 5223, - 5224, - 5225, - 5226, - 5227, - 5228, - 5229, - 5230, - 5231, - 5232, - 5233, - 5234, - 5235, - 5236, - 5237, - 5238, - 5239, - 5240, - 5241, - 5242, - 5243, - 5244, - 5245, - 5246, - 5247, - 5248, - 5249, - 5250, - 5251, - 5252, - 5253, - 5254, - 5255, - 5256, - 5257, - 5258, - 5259, - 5260, - 5261, - 5262, - 5263, - 5264, - 5265, - 5266, - 5267, - 5268, - 5269, - 5270, - 5271, - 5272, - 5273, - 5274, - 5275, - 5276, - 5277, - 5278, - 5279, - 5280, - 5281, - 5282, - 5283, - 5284, - 5285, - 5286, - 5287, - 5288, - 5289, - 5290, - 5291, - 5292, - 5293, - 5294, - 5295, - 5296, - 5297, - 5298, - 5299, - 5300, - 5301, - 5302, - 5303, - 5304, - 5305, - 5306, - 5307, - 5308, - 5309, - 5310, - 5311, - 5312, - 5313, - 5314, - 5315, - 5316, - 5317, - 5318, - 5319, - 5320, - 5321, - 5322, - 5323, - 5324, - 5325, - 5326, - 5327, - 5328, - 5329, - 5330, - 5331, - 5332, - 5333, - 5334, - 5335, - 5336, - 5337, - 5338, - 5339, - 5340, - 5341, - 5342, - 5343, - 5344, - 5345, - 5346, - 5347, - 5348, - 5349, - 5350, - 5351, - 5352, - 5353, - 5354, - 5355, - 5356, - 5357, - 5358, - 5359, - 5360, - 5361, - 5362, - 5363, - 5364, - 5365, - 5366, - 5367, - 5368, - 5369, - 5370, - 5371, - 5372, - 5373, - 5374, - 5375, - 5376, - 5377, - 5378, - 5379, - 5380, - 5381, - 5382, - 5383, - 5384, - 5385, - 5386, - 5387, - 5388, - 5389, - 5390, - 5391, - 5392, - 5393, - 5394, - 5395, - 5396, - 5397, - 5398, - 5399, - 5400, - 5401, - 5402, - 5403, - 5404, - 5405, - 5406, - 5407, - 5408, - 5409, - 5410, - 5411, - 5412, - 5413, - 5414, - 5415, - 5416, - 5417, - 5418, - 5419, - 5420, - 5421, - 5422, - 5423, - 5424, - 5425, - 5426, - 5427, - 5428, - 5429, - 5430, - 5431, - 5432, - 5433, - 5434, - 5435, - 5436, - 5437, - 5438, - 5439, - 5440, - 5441, - 5442, - 5443, - 5444, - 5445, - 5446, - 5447, - 5448, - 5449, - 5450, - 5451, - 5452, - 5453, - 5454, - 5455, - 5456, - 5457, - 5458, - 5459, - 5460, - 5461, - 5462, - 5463, - 5464, - 5465, - 5466, - 5467, - 5468, - 5469, - 5470, - 5471, - 5472, - 5473, - 5474, - 5475, - 5476, - 5477, - 5478, - 5479, - 5480, - 5481, - 5482, - 5483, - 5484, - 5485, - 5486, - 5487, - 5488, - 5489, - 5490, - 5491, - 5492, - 5493, - 5494, - 5495, - 5496, - 5497, - 5498, - 5499, - 5500, - 5501, - 5502, - 5503, - 5504, - 5505, - 5506, - 5507, - 5508, - 5509, - 5510, - 5511, - 5512, - 5513, - 5514, - 5515, - 5516, - 5517, - 5518, - 5519, - 5520, - 5521, - 5522, - 5523, - 5524, - 5525, - 5526, - 5527, - 5528, - 5529, - 5530, - 5531, - 5532, - 5533, - 5534, - 5535, - 5536, - 5537, - 5538, - 5539, - 5540, - 5541, - 5542, - 5543, - 5544, - 5545, - 5546, - 5547, - 5548, - 5549, - 5550, - 5551, - 5552, - 5553, - 5554, - 5555, - 5556, - 5557, - 5558, - 5559, - 5560, - 5561, - 5562, - 5563, - 5564, - 5565, - 5566, - 5567, - 5568, - 5569, - 5570, - 5571, - 5572, - 5573, - 5574, - 5575, - 5576, - 5577, - 5578, - 5579, - 5580, - 5581, - 5582, - 5583, - 5584, - 5585, - 5586, - 5587, - 5588, - 5589, - 5590, - 5591, - 5592, - 5593, - 5594, - 5595, - 5596, - 5597, - 5598, - 5599, - 5600, - 5601, - 5602, - 5603, - 5604, - 5605, - 5606, - 5607, - 5608, - 5609, - 5610, - 5611, - 5612, - 5613, - 5614, - 5615, - 5616, - 5617, - 5618, - 5619, - 5620, - 5621, - 5622, - 5623, - 5624, - 5625, - 5626, - 5627, - 5628, - 5629, - 5630, - 5631, - 5632, - 5633, - 5634, - 5635, - 5636, - 5637, - 5638, - 5639, - 5640, - 5641, - 5642, - 5643, - 5644, - 5645, - 5646, - 5647, - 5648, - 5649, - 5650, - 5651, - 5652, - 5653, - 5654, - 5655, - 5656, - 5657, - 5658, - 5659, - 5660, - 5661, - 5662, - 5663, - 5664, - 5665, - 5666, - 5667, - 5668, - 5669, - 5670, - 5671, - 5672, - 5673, - 5674, - 5675, - 5676, - 5677, - 5678, - 5679, - 5680, - 5681, - 5682, - 5683, - 5684, - 5685, - 5686, - 5687, - 5688, - 5689, - 5690, - 5691, - 5692, - 5693, - 5694, - 5695, - 5696, - 5697, - 5698, - 5699, - 5700, - 5701, - 5702, - 5703, - 5704, - 5705, - 5706, - 5707, - 5708, - 5709, - 5710, - 5711, - 5712, - 5713, - 5714, - 5715, - 5716, - 5717, - 5718, - 5719, - 5720, - 5721, - 5722, - 5723, - 5724, - 5725, - 5726, - 5727, - 5728, - 5729, - 5730, - 5731, - 5732, - 5733, - 5734, - 5735, - 5736, - 5737, - 5738, - 5739, - 5740, - 5743, - 5744, - 5745, - 5746, - 5747, - 5748, - 5749, - 5750, - 5751, - 5752, - 5753, - 5754, - 5755, - 5756, - 5757, - 5758, - 5759, - 5761, - 5762, - 5763, - 5764, - 5765, - 5766, - 5767, - 5768, - 5769, - 5770, - 5771, - 5772, - 5773, - 5774, - 5775, - 5776, - 5777, - 5778, - 5779, - 5780, - 5781, - 5782, - 5783, - 5784, - 5785, - 5786, - 5792, - 5793, - 5794, - 5795, - 5796, - 5797, - 5798, - 5799, - 5800, - 5801, - 5802, - 5803, - 5804, - 5805, - 5806, - 5807, - 5808, - 5809, - 5810, - 5811, - 5812, - 5813, - 5814, - 5815, - 5816, - 5817, - 5818, - 5819, - 5820, - 5821, - 5822, - 5823, - 5824, - 5825, - 5826, - 5827, - 5828, - 5829, - 5830, - 5831, - 5832, - 5833, - 5834, - 5835, - 5836, - 5837, - 5838, - 5839, - 5840, - 5841, - 5842, - 5843, - 5844, - 5845, - 5846, - 5847, - 5848, - 5849, - 5850, - 5851, - 5852, - 5853, - 5854, - 5855, - 5856, - 5857, - 5858, - 5859, - 5860, - 5861, - 5862, - 5863, - 5864, - 5865, - 5866, - 5870, - 5871, - 5872, - 5888, - 5889, - 5890, - 5891, - 5892, - 5893, - 5894, - 5895, - 5896, - 5897, - 5898, - 5899, - 5900, - 5902, - 5903, - 5904, - 5905, - 5920, - 5921, - 5922, - 5923, - 5924, - 5925, - 5926, - 5927, - 5928, - 5929, - 5930, - 5931, - 5932, - 5933, - 5934, - 5935, - 5936, - 5937, - 5952, - 5953, - 5954, - 5955, - 5956, - 5957, - 5958, - 5959, - 5960, - 5961, - 5962, - 5963, - 5964, - 5965, - 5966, - 5967, - 5968, - 5969, - 5984, - 5985, - 5986, - 5987, - 5988, - 5989, - 5990, - 5991, - 5992, - 5993, - 5994, - 5995, - 5996, - 5998, - 5999, - 6000, - 6016, - 6017, - 6018, - 6019, - 6020, - 6021, - 6022, - 6023, - 6024, - 6025, - 6026, - 6027, - 6028, - 6029, - 6030, - 6031, - 6032, - 6033, - 6034, - 6035, - 6036, - 6037, - 6038, - 6039, - 6040, - 6041, - 6042, - 6043, - 6044, - 6045, - 6046, - 6047, - 6048, - 6049, - 6050, - 6051, - 6052, - 6053, - 6054, - 6055, - 6056, - 6057, - 6058, - 6059, - 6060, - 6061, - 6062, - 6063, - 6064, - 6065, - 6066, - 6067, - 6103, - 6108, - 6176, - 6177, - 6178, - 6179, - 6180, - 6181, - 6182, - 6183, - 6184, - 6185, - 6186, - 6187, - 6188, - 6189, - 6190, - 6191, - 6192, - 6193, - 6194, - 6195, - 6196, - 6197, - 6198, - 6199, - 6200, - 6201, - 6202, - 6203, - 6204, - 6205, - 6206, - 6207, - 6208, - 6209, - 6210, - 6211, - 6212, - 6213, - 6214, - 6215, - 6216, - 6217, - 6218, - 6219, - 6220, - 6221, - 6222, - 6223, - 6224, - 6225, - 6226, - 6227, - 6228, - 6229, - 6230, - 6231, - 6232, - 6233, - 6234, - 6235, - 6236, - 6237, - 6238, - 6239, - 6240, - 6241, - 6242, - 6243, - 6244, - 6245, - 6246, - 6247, - 6248, - 6249, - 6250, - 6251, - 6252, - 6253, - 6254, - 6255, - 6256, - 6257, - 6258, - 6259, - 6260, - 6261, - 6262, - 6263, - 6272, - 6273, - 6274, - 6275, - 6276, - 6277, - 6278, - 6279, - 6280, - 6281, - 6282, - 6283, - 6284, - 6285, - 6286, - 6287, - 6288, - 6289, - 6290, - 6291, - 6292, - 6293, - 6294, - 6295, - 6296, - 6297, - 6298, - 6299, - 6300, - 6301, - 6302, - 6303, - 6304, - 6305, - 6306, - 6307, - 6308, - 6309, - 6310, - 6311, - 6312, - 6314, - 6320, - 6321, - 6322, - 6323, - 6324, - 6325, - 6326, - 6327, - 6328, - 6329, - 6330, - 6331, - 6332, - 6333, - 6334, - 6335, - 6336, - 6337, - 6338, - 6339, - 6340, - 6341, - 6342, - 6343, - 6344, - 6345, - 6346, - 6347, - 6348, - 6349, - 6350, - 6351, - 6352, - 6353, - 6354, - 6355, - 6356, - 6357, - 6358, - 6359, - 6360, - 6361, - 6362, - 6363, - 6364, - 6365, - 6366, - 6367, - 6368, - 6369, - 6370, - 6371, - 6372, - 6373, - 6374, - 6375, - 6376, - 6377, - 6378, - 6379, - 6380, - 6381, - 6382, - 6383, - 6384, - 6385, - 6386, - 6387, - 6388, - 6389, - 6400, - 6401, - 6402, - 6403, - 6404, - 6405, - 6406, - 6407, - 6408, - 6409, - 6410, - 6411, - 6412, - 6413, - 6414, - 6415, - 6416, - 6417, - 6418, - 6419, - 6420, - 6421, - 6422, - 6423, - 6424, - 6425, - 6426, - 6427, - 6428, - 6480, - 6481, - 6482, - 6483, - 6484, - 6485, - 6486, - 6487, - 6488, - 6489, - 6490, - 6491, - 6492, - 6493, - 6494, - 6495, - 6496, - 6497, - 6498, - 6499, - 6500, - 6501, - 6502, - 6503, - 6504, - 6505, - 6506, - 6507, - 6508, - 6509, - 6512, - 6513, - 6514, - 6515, - 6516, - 6528, - 6529, - 6530, - 6531, - 6532, - 6533, - 6534, - 6535, - 6536, - 6537, - 6538, - 6539, - 6540, - 6541, - 6542, - 6543, - 6544, - 6545, - 6546, - 6547, - 6548, - 6549, - 6550, - 6551, - 6552, - 6553, - 6554, - 6555, - 6556, - 6557, - 6558, - 6559, - 6560, - 6561, - 6562, - 6563, - 6564, - 6565, - 6566, - 6567, - 6568, - 6569, - 6570, - 6571, - 6593, - 6594, - 6595, - 6596, - 6597, - 6598, - 6599, - 6656, - 6657, - 6658, - 6659, - 6660, - 6661, - 6662, - 6663, - 6664, - 6665, - 6666, - 6667, - 6668, - 6669, - 6670, - 6671, - 6672, - 6673, - 6674, - 6675, - 6676, - 6677, - 6678, - 6688, - 6689, - 6690, - 6691, - 6692, - 6693, - 6694, - 6695, - 6696, - 6697, - 6698, - 6699, - 6700, - 6701, - 6702, - 6703, - 6704, - 6705, - 6706, - 6707, - 6708, - 6709, - 6710, - 6711, - 6712, - 6713, - 6714, - 6715, - 6716, - 6717, - 6718, - 6719, - 6720, - 6721, - 6722, - 6723, - 6724, - 6725, - 6726, - 6727, - 6728, - 6729, - 6730, - 6731, - 6732, - 6733, - 6734, - 6735, - 6736, - 6737, - 6738, - 6739, - 6740, - 6823, - 6917, - 6918, - 6919, - 6920, - 6921, - 6922, - 6923, - 6924, - 6925, - 6926, - 6927, - 6928, - 6929, - 6930, - 6931, - 6932, - 6933, - 6934, - 6935, - 6936, - 6937, - 6938, - 6939, - 6940, - 6941, - 6942, - 6943, - 6944, - 6945, - 6946, - 6947, - 6948, - 6949, - 6950, - 6951, - 6952, - 6953, - 6954, - 6955, - 6956, - 6957, - 6958, - 6959, - 6960, - 6961, - 6962, - 6963, - 6981, - 6982, - 6983, - 6984, - 6985, - 6986, - 6987, - 7043, - 7044, - 7045, - 7046, - 7047, - 7048, - 7049, - 7050, - 7051, - 7052, - 7053, - 7054, - 7055, - 7056, - 7057, - 7058, - 7059, - 7060, - 7061, - 7062, - 7063, - 7064, - 7065, - 7066, - 7067, - 7068, - 7069, - 7070, - 7071, - 7072, - 7086, - 7087, - 7098, - 7099, - 7100, - 7101, - 7102, - 7103, - 7104, - 7105, - 7106, - 7107, - 7108, - 7109, - 7110, - 7111, - 7112, - 7113, - 7114, - 7115, - 7116, - 7117, - 7118, - 7119, - 7120, - 7121, - 7122, - 7123, - 7124, - 7125, - 7126, - 7127, - 7128, - 7129, - 7130, - 7131, - 7132, - 7133, - 7134, - 7135, - 7136, - 7137, - 7138, - 7139, - 7140, - 7141, - 7168, - 7169, - 7170, - 7171, - 7172, - 7173, - 7174, - 7175, - 7176, - 7177, - 7178, - 7179, - 7180, - 7181, - 7182, - 7183, - 7184, - 7185, - 7186, - 7187, - 7188, - 7189, - 7190, - 7191, - 7192, - 7193, - 7194, - 7195, - 7196, - 7197, - 7198, - 7199, - 7200, - 7201, - 7202, - 7203, - 7245, - 7246, - 7247, - 7258, - 7259, - 7260, - 7261, - 7262, - 7263, - 7264, - 7265, - 7266, - 7267, - 7268, - 7269, - 7270, - 7271, - 7272, - 7273, - 7274, - 7275, - 7276, - 7277, - 7278, - 7279, - 7280, - 7281, - 7282, - 7283, - 7284, - 7285, - 7286, - 7287, - 7288, - 7289, - 7290, - 7291, - 7292, - 7293, - 7401, - 7402, - 7403, - 7404, - 7406, - 7407, - 7408, - 7409, - 7413, - 7414, - 7424, - 7425, - 7426, - 7427, - 7428, - 7429, - 7430, - 7431, - 7432, - 7433, - 7434, - 7435, - 7436, - 7437, - 7438, - 7439, - 7440, - 7441, - 7442, - 7443, - 7444, - 7445, - 7446, - 7447, - 7448, - 7449, - 7450, - 7451, - 7452, - 7453, - 7454, - 7455, - 7456, - 7457, - 7458, - 7459, - 7460, - 7461, - 7462, - 7463, - 7464, - 7465, - 7466, - 7467, - 7468, - 7469, - 7470, - 7471, - 7472, - 7473, - 7474, - 7475, - 7476, - 7477, - 7478, - 7479, - 7480, - 7481, - 7482, - 7483, - 7484, - 7485, - 7486, - 7487, - 7488, - 7489, - 7490, - 7491, - 7492, - 7493, - 7494, - 7495, - 7496, - 7497, - 7498, - 7499, - 7500, - 7501, - 7502, - 7503, - 7504, - 7505, - 7506, - 7507, - 7508, - 7509, - 7510, - 7511, - 7512, - 7513, - 7514, - 7515, - 7516, - 7517, - 7518, - 7519, - 7520, - 7521, - 7522, - 7523, - 7524, - 7525, - 7526, - 7527, - 7528, - 7529, - 7530, - 7531, - 7532, - 7533, - 7534, - 7535, - 7536, - 7537, - 7538, - 7539, - 7540, - 7541, - 7542, - 7543, - 7544, - 7545, - 7546, - 7547, - 7548, - 7549, - 7550, - 7551, - 7552, - 7553, - 7554, - 7555, - 7556, - 7557, - 7558, - 7559, - 7560, - 7561, - 7562, - 7563, - 7564, - 7565, - 7566, - 7567, - 7568, - 7569, - 7570, - 7571, - 7572, - 7573, - 7574, - 7575, - 7576, - 7577, - 7578, - 7579, - 7580, - 7581, - 7582, - 7583, - 7584, - 7585, - 7586, - 7587, - 7588, - 7589, - 7590, - 7591, - 7592, - 7593, - 7594, - 7595, - 7596, - 7597, - 7598, - 7599, - 7600, - 7601, - 7602, - 7603, - 7604, - 7605, - 7606, - 7607, - 7608, - 7609, - 7610, - 7611, - 7612, - 7613, - 7614, - 7615, - 7680, - 7681, - 7682, - 7683, - 7684, - 7685, - 7686, - 7687, - 7688, - 7689, - 7690, - 7691, - 7692, - 7693, - 7694, - 7695, - 7696, - 7697, - 7698, - 7699, - 7700, - 7701, - 7702, - 7703, - 7704, - 7705, - 7706, - 7707, - 7708, - 7709, - 7710, - 7711, - 7712, - 7713, - 7714, - 7715, - 7716, - 7717, - 7718, - 7719, - 7720, - 7721, - 7722, - 7723, - 7724, - 7725, - 7726, - 7727, - 7728, - 7729, - 7730, - 7731, - 7732, - 7733, - 7734, - 7735, - 7736, - 7737, - 7738, - 7739, - 7740, - 7741, - 7742, - 7743, - 7744, - 7745, - 7746, - 7747, - 7748, - 7749, - 7750, - 7751, - 7752, - 7753, - 7754, - 7755, - 7756, - 7757, - 7758, - 7759, - 7760, - 7761, - 7762, - 7763, - 7764, - 7765, - 7766, - 7767, - 7768, - 7769, - 7770, - 7771, - 7772, - 7773, - 7774, - 7775, - 7776, - 7777, - 7778, - 7779, - 7780, - 7781, - 7782, - 7783, - 7784, - 7785, - 7786, - 7787, - 7788, - 7789, - 7790, - 7791, - 7792, - 7793, - 7794, - 7795, - 7796, - 7797, - 7798, - 7799, - 7800, - 7801, - 7802, - 7803, - 7804, - 7805, - 7806, - 7807, - 7808, - 7809, - 7810, - 7811, - 7812, - 7813, - 7814, - 7815, - 7816, - 7817, - 7818, - 7819, - 7820, - 7821, - 7822, - 7823, - 7824, - 7825, - 7826, - 7827, - 7828, - 7829, - 7830, - 7831, - 7832, - 7833, - 7834, - 7835, - 7836, - 7837, - 7838, - 7839, - 7840, - 7841, - 7842, - 7843, - 7844, - 7845, - 7846, - 7847, - 7848, - 7849, - 7850, - 7851, - 7852, - 7853, - 7854, - 7855, - 7856, - 7857, - 7858, - 7859, - 7860, - 7861, - 7862, - 7863, - 7864, - 7865, - 7866, - 7867, - 7868, - 7869, - 7870, - 7871, - 7872, - 7873, - 7874, - 7875, - 7876, - 7877, - 7878, - 7879, - 7880, - 7881, - 7882, - 7883, - 7884, - 7885, - 7886, - 7887, - 7888, - 7889, - 7890, - 7891, - 7892, - 7893, - 7894, - 7895, - 7896, - 7897, - 7898, - 7899, - 7900, - 7901, - 7902, - 7903, - 7904, - 7905, - 7906, - 7907, - 7908, - 7909, - 7910, - 7911, - 7912, - 7913, - 7914, - 7915, - 7916, - 7917, - 7918, - 7919, - 7920, - 7921, - 7922, - 7923, - 7924, - 7925, - 7926, - 7927, - 7928, - 7929, - 7930, - 7931, - 7932, - 7933, - 7934, - 7935, - 7936, - 7937, - 7938, - 7939, - 7940, - 7941, - 7942, - 7943, - 7944, - 7945, - 7946, - 7947, - 7948, - 7949, - 7950, - 7951, - 7952, - 7953, - 7954, - 7955, - 7956, - 7957, - 7960, - 7961, - 7962, - 7963, - 7964, - 7965, - 7968, - 7969, - 7970, - 7971, - 7972, - 7973, - 7974, - 7975, - 7976, - 7977, - 7978, - 7979, - 7980, - 7981, - 7982, - 7983, - 7984, - 7985, - 7986, - 7987, - 7988, - 7989, - 7990, - 7991, - 7992, - 7993, - 7994, - 7995, - 7996, - 7997, - 7998, - 7999, - 8000, - 8001, - 8002, - 8003, - 8004, - 8005, - 8008, - 8009, - 8010, - 8011, - 8012, - 8013, - 8016, - 8017, - 8018, - 8019, - 8020, - 8021, - 8022, - 8023, - 8025, - 8027, - 8029, - 8031, - 8032, - 8033, - 8034, - 8035, - 8036, - 8037, - 8038, - 8039, - 8040, - 8041, - 8042, - 8043, - 8044, - 8045, - 8046, - 8047, - 8048, - 8049, - 8050, - 8051, - 8052, - 8053, - 8054, - 8055, - 8056, - 8057, - 8058, - 8059, - 8060, - 8061, - 8064, - 8065, - 8066, - 8067, - 8068, - 8069, - 8070, - 8071, - 8072, - 8073, - 8074, - 8075, - 8076, - 8077, - 8078, - 8079, - 8080, - 8081, - 8082, - 8083, - 8084, - 8085, - 8086, - 8087, - 8088, - 8089, - 8090, - 8091, - 8092, - 8093, - 8094, - 8095, - 8096, - 8097, - 8098, - 8099, - 8100, - 8101, - 8102, - 8103, - 8104, - 8105, - 8106, - 8107, - 8108, - 8109, - 8110, - 8111, - 8112, - 8113, - 8114, - 8115, - 8116, - 8118, - 8119, - 8120, - 8121, - 8122, - 8123, - 8124, - 8126, - 8130, - 8131, - 8132, - 8134, - 8135, - 8136, - 8137, - 8138, - 8139, - 8140, - 8144, - 8145, - 8146, - 8147, - 8150, - 8151, - 8152, - 8153, - 8154, - 8155, - 8160, - 8161, - 8162, - 8163, - 8164, - 8165, - 8166, - 8167, - 8168, - 8169, - 8170, - 8171, - 8172, - 8178, - 8179, - 8180, - 8182, - 8183, - 8184, - 8185, - 8186, - 8187, - 8188, - 8305, - 8319, - 8336, - 8337, - 8338, - 8339, - 8340, - 8341, - 8342, - 8343, - 8344, - 8345, - 8346, - 8347, - 8348, - 8450, - 8455, - 8458, - 8459, - 8460, - 8461, - 8462, - 8463, - 8464, - 8465, - 8466, - 8467, - 8469, - 8473, - 8474, - 8475, - 8476, - 8477, - 8484, - 8486, - 8488, - 8490, - 8491, - 8492, - 8493, - 8495, - 8496, - 8497, - 8498, - 8499, - 8500, - 8501, - 8502, - 8503, - 8504, - 8505, - 8508, - 8509, - 8510, - 8511, - 8517, - 8518, - 8519, - 8520, - 8521, - 8526, - 8544, - 8545, - 8546, - 8547, - 8548, - 8549, - 8550, - 8551, - 8552, - 8553, - 8554, - 8555, - 8556, - 8557, - 8558, - 8559, - 8560, - 8561, - 8562, - 8563, - 8564, - 8565, - 8566, - 8567, - 8568, - 8569, - 8570, - 8571, - 8572, - 8573, - 8574, - 8575, - 8576, - 8577, - 8578, - 8579, - 8580, - 8581, - 8582, - 8583, - 8584, - 11264, - 11265, - 11266, - 11267, - 11268, - 11269, - 11270, - 11271, - 11272, - 11273, - 11274, - 11275, - 11276, - 11277, - 11278, - 11279, - 11280, - 11281, - 11282, - 11283, - 11284, - 11285, - 11286, - 11287, - 11288, - 11289, - 11290, - 11291, - 11292, - 11293, - 11294, - 11295, - 11296, - 11297, - 11298, - 11299, - 11300, - 11301, - 11302, - 11303, - 11304, - 11305, - 11306, - 11307, - 11308, - 11309, - 11310, - 11312, - 11313, - 11314, - 11315, - 11316, - 11317, - 11318, - 11319, - 11320, - 11321, - 11322, - 11323, - 11324, - 11325, - 11326, - 11327, - 11328, - 11329, - 11330, - 11331, - 11332, - 11333, - 11334, - 11335, - 11336, - 11337, - 11338, - 11339, - 11340, - 11341, - 11342, - 11343, - 11344, - 11345, - 11346, - 11347, - 11348, - 11349, - 11350, - 11351, - 11352, - 11353, - 11354, - 11355, - 11356, - 11357, - 11358, - 11360, - 11361, - 11362, - 11363, - 11364, - 11365, - 11366, - 11367, - 11368, - 11369, - 11370, - 11371, - 11372, - 11373, - 11374, - 11375, - 11376, - 11377, - 11378, - 11379, - 11380, - 11381, - 11382, - 11383, - 11384, - 11385, - 11386, - 11387, - 11388, - 11389, - 11390, - 11391, - 11392, - 11393, - 11394, - 11395, - 11396, - 11397, - 11398, - 11399, - 11400, - 11401, - 11402, - 11403, - 11404, - 11405, - 11406, - 11407, - 11408, - 11409, - 11410, - 11411, - 11412, - 11413, - 11414, - 11415, - 11416, - 11417, - 11418, - 11419, - 11420, - 11421, - 11422, - 11423, - 11424, - 11425, - 11426, - 11427, - 11428, - 11429, - 11430, - 11431, - 11432, - 11433, - 11434, - 11435, - 11436, - 11437, - 11438, - 11439, - 11440, - 11441, - 11442, - 11443, - 11444, - 11445, - 11446, - 11447, - 11448, - 11449, - 11450, - 11451, - 11452, - 11453, - 11454, - 11455, - 11456, - 11457, - 11458, - 11459, - 11460, - 11461, - 11462, - 11463, - 11464, - 11465, - 11466, - 11467, - 11468, - 11469, - 11470, - 11471, - 11472, - 11473, - 11474, - 11475, - 11476, - 11477, - 11478, - 11479, - 11480, - 11481, - 11482, - 11483, - 11484, - 11485, - 11486, - 11487, - 11488, - 11489, - 11490, - 11491, - 11492, - 11499, - 11500, - 11501, - 11502, - 11506, - 11507, - 11520, - 11521, - 11522, - 11523, - 11524, - 11525, - 11526, - 11527, - 11528, - 11529, - 11530, - 11531, - 11532, - 11533, - 11534, - 11535, - 11536, - 11537, - 11538, - 11539, - 11540, - 11541, - 11542, - 11543, - 11544, - 11545, - 11546, - 11547, - 11548, - 11549, - 11550, - 11551, - 11552, - 11553, - 11554, - 11555, - 11556, - 11557, - 11559, - 11565, - 11568, - 11569, - 11570, - 11571, - 11572, - 11573, - 11574, - 11575, - 11576, - 11577, - 11578, - 11579, - 11580, - 11581, - 11582, - 11583, - 11584, - 11585, - 11586, - 11587, - 11588, - 11589, - 11590, - 11591, - 11592, - 11593, - 11594, - 11595, - 11596, - 11597, - 11598, - 11599, - 11600, - 11601, - 11602, - 11603, - 11604, - 11605, - 11606, - 11607, - 11608, - 11609, - 11610, - 11611, - 11612, - 11613, - 11614, - 11615, - 11616, - 11617, - 11618, - 11619, - 11620, - 11621, - 11622, - 11623, - 11631, - 11648, - 11649, - 11650, - 11651, - 11652, - 11653, - 11654, - 11655, - 11656, - 11657, - 11658, - 11659, - 11660, - 11661, - 11662, - 11663, - 11664, - 11665, - 11666, - 11667, - 11668, - 11669, - 11670, - 11680, - 11681, - 11682, - 11683, - 11684, - 11685, - 11686, - 11688, - 11689, - 11690, - 11691, - 11692, - 11693, - 11694, - 11696, - 11697, - 11698, - 11699, - 11700, - 11701, - 11702, - 11704, - 11705, - 11706, - 11707, - 11708, - 11709, - 11710, - 11712, - 11713, - 11714, - 11715, - 11716, - 11717, - 11718, - 11720, - 11721, - 11722, - 11723, - 11724, - 11725, - 11726, - 11728, - 11729, - 11730, - 11731, - 11732, - 11733, - 11734, - 11736, - 11737, - 11738, - 11739, - 11740, - 11741, - 11742, - 11823, - 12293, - 12294, - 12295, - 12321, - 12322, - 12323, - 12324, - 12325, - 12326, - 12327, - 12328, - 12329, - 12337, - 12338, - 12339, - 12340, - 12341, - 12344, - 12345, - 12346, - 12347, - 12348, - 12353, - 12354, - 12355, - 12356, - 12357, - 12358, - 12359, - 12360, - 12361, - 12362, - 12363, - 12364, - 12365, - 12366, - 12367, - 12368, - 12369, - 12370, - 12371, - 12372, - 12373, - 12374, - 12375, - 12376, - 12377, - 12378, - 12379, - 12380, - 12381, - 12382, - 12383, - 12384, - 12385, - 12386, - 12387, - 12388, - 12389, - 12390, - 12391, - 12392, - 12393, - 12394, - 12395, - 12396, - 12397, - 12398, - 12399, - 12400, - 12401, - 12402, - 12403, - 12404, - 12405, - 12406, - 12407, - 12408, - 12409, - 12410, - 12411, - 12412, - 12413, - 12414, - 12415, - 12416, - 12417, - 12418, - 12419, - 12420, - 12421, - 12422, - 12423, - 12424, - 12425, - 12426, - 12427, - 12428, - 12429, - 12430, - 12431, - 12432, - 12433, - 12434, - 12435, - 12436, - 12437, - 12438, - 12445, - 12446, - 12447, - 12449, - 12450, - 12451, - 12452, - 12453, - 12454, - 12455, - 12456, - 12457, - 12458, - 12459, - 12460, - 12461, - 12462, - 12463, - 12464, - 12465, - 12466, - 12467, - 12468, - 12469, - 12470, - 12471, - 12472, - 12473, - 12474, - 12475, - 12476, - 12477, - 12478, - 12479, - 12480, - 12481, - 12482, - 12483, - 12484, - 12485, - 12486, - 12487, - 12488, - 12489, - 12490, - 12491, - 12492, - 12493, - 12494, - 12495, - 12496, - 12497, - 12498, - 12499, - 12500, - 12501, - 12502, - 12503, - 12504, - 12505, - 12506, - 12507, - 12508, - 12509, - 12510, - 12511, - 12512, - 12513, - 12514, - 12515, - 12516, - 12517, - 12518, - 12519, - 12520, - 12521, - 12522, - 12523, - 12524, - 12525, - 12526, - 12527, - 12528, - 12529, - 12530, - 12531, - 12532, - 12533, - 12534, - 12535, - 12536, - 12537, - 12538, - 12540, - 12541, - 12542, - 12543, - 12549, - 12550, - 12551, - 12552, - 12553, - 12554, - 12555, - 12556, - 12557, - 12558, - 12559, - 12560, - 12561, - 12562, - 12563, - 12564, - 12565, - 12566, - 12567, - 12568, - 12569, - 12570, - 12571, - 12572, - 12573, - 12574, - 12575, - 12576, - 12577, - 12578, - 12579, - 12580, - 12581, - 12582, - 12583, - 12584, - 12585, - 12586, - 12587, - 12588, - 12589, - 12593, - 12594, - 12595, - 12596, - 12597, - 12598, - 12599, - 12600, - 12601, - 12602, - 12603, - 12604, - 12605, - 12606, - 12607, - 12608, - 12609, - 12610, - 12611, - 12612, - 12613, - 12614, - 12615, - 12616, - 12617, - 12618, - 12619, - 12620, - 12621, - 12622, - 12623, - 12624, - 12625, - 12626, - 12627, - 12628, - 12629, - 12630, - 12631, - 12632, - 12633, - 12634, - 12635, - 12636, - 12637, - 12638, - 12639, - 12640, - 12641, - 12642, - 12643, - 12644, - 12645, - 12646, - 12647, - 12648, - 12649, - 12650, - 12651, - 12652, - 12653, - 12654, - 12655, - 12656, - 12657, - 12658, - 12659, - 12660, - 12661, - 12662, - 12663, - 12664, - 12665, - 12666, - 12667, - 12668, - 12669, - 12670, - 12671, - 12672, - 12673, - 12674, - 12675, - 12676, - 12677, - 12678, - 12679, - 12680, - 12681, - 12682, - 12683, - 12684, - 12685, - 12686, - 12704, - 12705, - 12706, - 12707, - 12708, - 12709, - 12710, - 12711, - 12712, - 12713, - 12714, - 12715, - 12716, - 12717, - 12718, - 12719, - 12720, - 12721, - 12722, - 12723, - 12724, - 12725, - 12726, - 12727, - 12728, - 12729, - 12730, - 12784, - 12785, - 12786, - 12787, - 12788, - 12789, - 12790, - 12791, - 12792, - 12793, - 12794, - 12795, - 12796, - 12797, - 12798, - 12799, - 13312, - 13313, - 13314, - 13315, - 13316, - 13317, - 13318, - 13319, - 13320, - 13321, - 13322, - 13323, - 13324, - 13325, - 13326, - 13327, - 13328, - 13329, - 13330, - 13331, - 13332, - 13333, - 13334, - 13335, - 13336, - 13337, - 13338, - 13339, - 13340, - 13341, - 13342, - 13343, - 13344, - 13345, - 13346, - 13347, - 13348, - 13349, - 13350, - 13351, - 13352, - 13353, - 13354, - 13355, - 13356, - 13357, - 13358, - 13359, - 13360, - 13361, - 13362, - 13363, - 13364, - 13365, - 13366, - 13367, - 13368, - 13369, - 13370, - 13371, - 13372, - 13373, - 13374, - 13375, - 13376, - 13377, - 13378, - 13379, - 13380, - 13381, - 13382, - 13383, - 13384, - 13385, - 13386, - 13387, - 13388, - 13389, - 13390, - 13391, - 13392, - 13393, - 13394, - 13395, - 13396, - 13397, - 13398, - 13399, - 13400, - 13401, - 13402, - 13403, - 13404, - 13405, - 13406, - 13407, - 13408, - 13409, - 13410, - 13411, - 13412, - 13413, - 13414, - 13415, - 13416, - 13417, - 13418, - 13419, - 13420, - 13421, - 13422, - 13423, - 13424, - 13425, - 13426, - 13427, - 13428, - 13429, - 13430, - 13431, - 13432, - 13433, - 13434, - 13435, - 13436, - 13437, - 13438, - 13439, - 13440, - 13441, - 13442, - 13443, - 13444, - 13445, - 13446, - 13447, - 13448, - 13449, - 13450, - 13451, - 13452, - 13453, - 13454, - 13455, - 13456, - 13457, - 13458, - 13459, - 13460, - 13461, - 13462, - 13463, - 13464, - 13465, - 13466, - 13467, - 13468, - 13469, - 13470, - 13471, - 13472, - 13473, - 13474, - 13475, - 13476, - 13477, - 13478, - 13479, - 13480, - 13481, - 13482, - 13483, - 13484, - 13485, - 13486, - 13487, - 13488, - 13489, - 13490, - 13491, - 13492, - 13493, - 13494, - 13495, - 13496, - 13497, - 13498, - 13499, - 13500, - 13501, - 13502, - 13503, - 13504, - 13505, - 13506, - 13507, - 13508, - 13509, - 13510, - 13511, - 13512, - 13513, - 13514, - 13515, - 13516, - 13517, - 13518, - 13519, - 13520, - 13521, - 13522, - 13523, - 13524, - 13525, - 13526, - 13527, - 13528, - 13529, - 13530, - 13531, - 13532, - 13533, - 13534, - 13535, - 13536, - 13537, - 13538, - 13539, - 13540, - 13541, - 13542, - 13543, - 13544, - 13545, - 13546, - 13547, - 13548, - 13549, - 13550, - 13551, - 13552, - 13553, - 13554, - 13555, - 13556, - 13557, - 13558, - 13559, - 13560, - 13561, - 13562, - 13563, - 13564, - 13565, - 13566, - 13567, - 13568, - 13569, - 13570, - 13571, - 13572, - 13573, - 13574, - 13575, - 13576, - 13577, - 13578, - 13579, - 13580, - 13581, - 13582, - 13583, - 13584, - 13585, - 13586, - 13587, - 13588, - 13589, - 13590, - 13591, - 13592, - 13593, - 13594, - 13595, - 13596, - 13597, - 13598, - 13599, - 13600, - 13601, - 13602, - 13603, - 13604, - 13605, - 13606, - 13607, - 13608, - 13609, - 13610, - 13611, - 13612, - 13613, - 13614, - 13615, - 13616, - 13617, - 13618, - 13619, - 13620, - 13621, - 13622, - 13623, - 13624, - 13625, - 13626, - 13627, - 13628, - 13629, - 13630, - 13631, - 13632, - 13633, - 13634, - 13635, - 13636, - 13637, - 13638, - 13639, - 13640, - 13641, - 13642, - 13643, - 13644, - 13645, - 13646, - 13647, - 13648, - 13649, - 13650, - 13651, - 13652, - 13653, - 13654, - 13655, - 13656, - 13657, - 13658, - 13659, - 13660, - 13661, - 13662, - 13663, - 13664, - 13665, - 13666, - 13667, - 13668, - 13669, - 13670, - 13671, - 13672, - 13673, - 13674, - 13675, - 13676, - 13677, - 13678, - 13679, - 13680, - 13681, - 13682, - 13683, - 13684, - 13685, - 13686, - 13687, - 13688, - 13689, - 13690, - 13691, - 13692, - 13693, - 13694, - 13695, - 13696, - 13697, - 13698, - 13699, - 13700, - 13701, - 13702, - 13703, - 13704, - 13705, - 13706, - 13707, - 13708, - 13709, - 13710, - 13711, - 13712, - 13713, - 13714, - 13715, - 13716, - 13717, - 13718, - 13719, - 13720, - 13721, - 13722, - 13723, - 13724, - 13725, - 13726, - 13727, - 13728, - 13729, - 13730, - 13731, - 13732, - 13733, - 13734, - 13735, - 13736, - 13737, - 13738, - 13739, - 13740, - 13741, - 13742, - 13743, - 13744, - 13745, - 13746, - 13747, - 13748, - 13749, - 13750, - 13751, - 13752, - 13753, - 13754, - 13755, - 13756, - 13757, - 13758, - 13759, - 13760, - 13761, - 13762, - 13763, - 13764, - 13765, - 13766, - 13767, - 13768, - 13769, - 13770, - 13771, - 13772, - 13773, - 13774, - 13775, - 13776, - 13777, - 13778, - 13779, - 13780, - 13781, - 13782, - 13783, - 13784, - 13785, - 13786, - 13787, - 13788, - 13789, - 13790, - 13791, - 13792, - 13793, - 13794, - 13795, - 13796, - 13797, - 13798, - 13799, - 13800, - 13801, - 13802, - 13803, - 13804, - 13805, - 13806, - 13807, - 13808, - 13809, - 13810, - 13811, - 13812, - 13813, - 13814, - 13815, - 13816, - 13817, - 13818, - 13819, - 13820, - 13821, - 13822, - 13823, - 13824, - 13825, - 13826, - 13827, - 13828, - 13829, - 13830, - 13831, - 13832, - 13833, - 13834, - 13835, - 13836, - 13837, - 13838, - 13839, - 13840, - 13841, - 13842, - 13843, - 13844, - 13845, - 13846, - 13847, - 13848, - 13849, - 13850, - 13851, - 13852, - 13853, - 13854, - 13855, - 13856, - 13857, - 13858, - 13859, - 13860, - 13861, - 13862, - 13863, - 13864, - 13865, - 13866, - 13867, - 13868, - 13869, - 13870, - 13871, - 13872, - 13873, - 13874, - 13875, - 13876, - 13877, - 13878, - 13879, - 13880, - 13881, - 13882, - 13883, - 13884, - 13885, - 13886, - 13887, - 13888, - 13889, - 13890, - 13891, - 13892, - 13893, - 13894, - 13895, - 13896, - 13897, - 13898, - 13899, - 13900, - 13901, - 13902, - 13903, - 13904, - 13905, - 13906, - 13907, - 13908, - 13909, - 13910, - 13911, - 13912, - 13913, - 13914, - 13915, - 13916, - 13917, - 13918, - 13919, - 13920, - 13921, - 13922, - 13923, - 13924, - 13925, - 13926, - 13927, - 13928, - 13929, - 13930, - 13931, - 13932, - 13933, - 13934, - 13935, - 13936, - 13937, - 13938, - 13939, - 13940, - 13941, - 13942, - 13943, - 13944, - 13945, - 13946, - 13947, - 13948, - 13949, - 13950, - 13951, - 13952, - 13953, - 13954, - 13955, - 13956, - 13957, - 13958, - 13959, - 13960, - 13961, - 13962, - 13963, - 13964, - 13965, - 13966, - 13967, - 13968, - 13969, - 13970, - 13971, - 13972, - 13973, - 13974, - 13975, - 13976, - 13977, - 13978, - 13979, - 13980, - 13981, - 13982, - 13983, - 13984, - 13985, - 13986, - 13987, - 13988, - 13989, - 13990, - 13991, - 13992, - 13993, - 13994, - 13995, - 13996, - 13997, - 13998, - 13999, - 14000, - 14001, - 14002, - 14003, - 14004, - 14005, - 14006, - 14007, - 14008, - 14009, - 14010, - 14011, - 14012, - 14013, - 14014, - 14015, - 14016, - 14017, - 14018, - 14019, - 14020, - 14021, - 14022, - 14023, - 14024, - 14025, - 14026, - 14027, - 14028, - 14029, - 14030, - 14031, - 14032, - 14033, - 14034, - 14035, - 14036, - 14037, - 14038, - 14039, - 14040, - 14041, - 14042, - 14043, - 14044, - 14045, - 14046, - 14047, - 14048, - 14049, - 14050, - 14051, - 14052, - 14053, - 14054, - 14055, - 14056, - 14057, - 14058, - 14059, - 14060, - 14061, - 14062, - 14063, - 14064, - 14065, - 14066, - 14067, - 14068, - 14069, - 14070, - 14071, - 14072, - 14073, - 14074, - 14075, - 14076, - 14077, - 14078, - 14079, - 14080, - 14081, - 14082, - 14083, - 14084, - 14085, - 14086, - 14087, - 14088, - 14089, - 14090, - 14091, - 14092, - 14093, - 14094, - 14095, - 14096, - 14097, - 14098, - 14099, - 14100, - 14101, - 14102, - 14103, - 14104, - 14105, - 14106, - 14107, - 14108, - 14109, - 14110, - 14111, - 14112, - 14113, - 14114, - 14115, - 14116, - 14117, - 14118, - 14119, - 14120, - 14121, - 14122, - 14123, - 14124, - 14125, - 14126, - 14127, - 14128, - 14129, - 14130, - 14131, - 14132, - 14133, - 14134, - 14135, - 14136, - 14137, - 14138, - 14139, - 14140, - 14141, - 14142, - 14143, - 14144, - 14145, - 14146, - 14147, - 14148, - 14149, - 14150, - 14151, - 14152, - 14153, - 14154, - 14155, - 14156, - 14157, - 14158, - 14159, - 14160, - 14161, - 14162, - 14163, - 14164, - 14165, - 14166, - 14167, - 14168, - 14169, - 14170, - 14171, - 14172, - 14173, - 14174, - 14175, - 14176, - 14177, - 14178, - 14179, - 14180, - 14181, - 14182, - 14183, - 14184, - 14185, - 14186, - 14187, - 14188, - 14189, - 14190, - 14191, - 14192, - 14193, - 14194, - 14195, - 14196, - 14197, - 14198, - 14199, - 14200, - 14201, - 14202, - 14203, - 14204, - 14205, - 14206, - 14207, - 14208, - 14209, - 14210, - 14211, - 14212, - 14213, - 14214, - 14215, - 14216, - 14217, - 14218, - 14219, - 14220, - 14221, - 14222, - 14223, - 14224, - 14225, - 14226, - 14227, - 14228, - 14229, - 14230, - 14231, - 14232, - 14233, - 14234, - 14235, - 14236, - 14237, - 14238, - 14239, - 14240, - 14241, - 14242, - 14243, - 14244, - 14245, - 14246, - 14247, - 14248, - 14249, - 14250, - 14251, - 14252, - 14253, - 14254, - 14255, - 14256, - 14257, - 14258, - 14259, - 14260, - 14261, - 14262, - 14263, - 14264, - 14265, - 14266, - 14267, - 14268, - 14269, - 14270, - 14271, - 14272, - 14273, - 14274, - 14275, - 14276, - 14277, - 14278, - 14279, - 14280, - 14281, - 14282, - 14283, - 14284, - 14285, - 14286, - 14287, - 14288, - 14289, - 14290, - 14291, - 14292, - 14293, - 14294, - 14295, - 14296, - 14297, - 14298, - 14299, - 14300, - 14301, - 14302, - 14303, - 14304, - 14305, - 14306, - 14307, - 14308, - 14309, - 14310, - 14311, - 14312, - 14313, - 14314, - 14315, - 14316, - 14317, - 14318, - 14319, - 14320, - 14321, - 14322, - 14323, - 14324, - 14325, - 14326, - 14327, - 14328, - 14329, - 14330, - 14331, - 14332, - 14333, - 14334, - 14335, - 14336, - 14337, - 14338, - 14339, - 14340, - 14341, - 14342, - 14343, - 14344, - 14345, - 14346, - 14347, - 14348, - 14349, - 14350, - 14351, - 14352, - 14353, - 14354, - 14355, - 14356, - 14357, - 14358, - 14359, - 14360, - 14361, - 14362, - 14363, - 14364, - 14365, - 14366, - 14367, - 14368, - 14369, - 14370, - 14371, - 14372, - 14373, - 14374, - 14375, - 14376, - 14377, - 14378, - 14379, - 14380, - 14381, - 14382, - 14383, - 14384, - 14385, - 14386, - 14387, - 14388, - 14389, - 14390, - 14391, - 14392, - 14393, - 14394, - 14395, - 14396, - 14397, - 14398, - 14399, - 14400, - 14401, - 14402, - 14403, - 14404, - 14405, - 14406, - 14407, - 14408, - 14409, - 14410, - 14411, - 14412, - 14413, - 14414, - 14415, - 14416, - 14417, - 14418, - 14419, - 14420, - 14421, - 14422, - 14423, - 14424, - 14425, - 14426, - 14427, - 14428, - 14429, - 14430, - 14431, - 14432, - 14433, - 14434, - 14435, - 14436, - 14437, - 14438, - 14439, - 14440, - 14441, - 14442, - 14443, - 14444, - 14445, - 14446, - 14447, - 14448, - 14449, - 14450, - 14451, - 14452, - 14453, - 14454, - 14455, - 14456, - 14457, - 14458, - 14459, - 14460, - 14461, - 14462, - 14463, - 14464, - 14465, - 14466, - 14467, - 14468, - 14469, - 14470, - 14471, - 14472, - 14473, - 14474, - 14475, - 14476, - 14477, - 14478, - 14479, - 14480, - 14481, - 14482, - 14483, - 14484, - 14485, - 14486, - 14487, - 14488, - 14489, - 14490, - 14491, - 14492, - 14493, - 14494, - 14495, - 14496, - 14497, - 14498, - 14499, - 14500, - 14501, - 14502, - 14503, - 14504, - 14505, - 14506, - 14507, - 14508, - 14509, - 14510, - 14511, - 14512, - 14513, - 14514, - 14515, - 14516, - 14517, - 14518, - 14519, - 14520, - 14521, - 14522, - 14523, - 14524, - 14525, - 14526, - 14527, - 14528, - 14529, - 14530, - 14531, - 14532, - 14533, - 14534, - 14535, - 14536, - 14537, - 14538, - 14539, - 14540, - 14541, - 14542, - 14543, - 14544, - 14545, - 14546, - 14547, - 14548, - 14549, - 14550, - 14551, - 14552, - 14553, - 14554, - 14555, - 14556, - 14557, - 14558, - 14559, - 14560, - 14561, - 14562, - 14563, - 14564, - 14565, - 14566, - 14567, - 14568, - 14569, - 14570, - 14571, - 14572, - 14573, - 14574, - 14575, - 14576, - 14577, - 14578, - 14579, - 14580, - 14581, - 14582, - 14583, - 14584, - 14585, - 14586, - 14587, - 14588, - 14589, - 14590, - 14591, - 14592, - 14593, - 14594, - 14595, - 14596, - 14597, - 14598, - 14599, - 14600, - 14601, - 14602, - 14603, - 14604, - 14605, - 14606, - 14607, - 14608, - 14609, - 14610, - 14611, - 14612, - 14613, - 14614, - 14615, - 14616, - 14617, - 14618, - 14619, - 14620, - 14621, - 14622, - 14623, - 14624, - 14625, - 14626, - 14627, - 14628, - 14629, - 14630, - 14631, - 14632, - 14633, - 14634, - 14635, - 14636, - 14637, - 14638, - 14639, - 14640, - 14641, - 14642, - 14643, - 14644, - 14645, - 14646, - 14647, - 14648, - 14649, - 14650, - 14651, - 14652, - 14653, - 14654, - 14655, - 14656, - 14657, - 14658, - 14659, - 14660, - 14661, - 14662, - 14663, - 14664, - 14665, - 14666, - 14667, - 14668, - 14669, - 14670, - 14671, - 14672, - 14673, - 14674, - 14675, - 14676, - 14677, - 14678, - 14679, - 14680, - 14681, - 14682, - 14683, - 14684, - 14685, - 14686, - 14687, - 14688, - 14689, - 14690, - 14691, - 14692, - 14693, - 14694, - 14695, - 14696, - 14697, - 14698, - 14699, - 14700, - 14701, - 14702, - 14703, - 14704, - 14705, - 14706, - 14707, - 14708, - 14709, - 14710, - 14711, - 14712, - 14713, - 14714, - 14715, - 14716, - 14717, - 14718, - 14719, - 14720, - 14721, - 14722, - 14723, - 14724, - 14725, - 14726, - 14727, - 14728, - 14729, - 14730, - 14731, - 14732, - 14733, - 14734, - 14735, - 14736, - 14737, - 14738, - 14739, - 14740, - 14741, - 14742, - 14743, - 14744, - 14745, - 14746, - 14747, - 14748, - 14749, - 14750, - 14751, - 14752, - 14753, - 14754, - 14755, - 14756, - 14757, - 14758, - 14759, - 14760, - 14761, - 14762, - 14763, - 14764, - 14765, - 14766, - 14767, - 14768, - 14769, - 14770, - 14771, - 14772, - 14773, - 14774, - 14775, - 14776, - 14777, - 14778, - 14779, - 14780, - 14781, - 14782, - 14783, - 14784, - 14785, - 14786, - 14787, - 14788, - 14789, - 14790, - 14791, - 14792, - 14793, - 14794, - 14795, - 14796, - 14797, - 14798, - 14799, - 14800, - 14801, - 14802, - 14803, - 14804, - 14805, - 14806, - 14807, - 14808, - 14809, - 14810, - 14811, - 14812, - 14813, - 14814, - 14815, - 14816, - 14817, - 14818, - 14819, - 14820, - 14821, - 14822, - 14823, - 14824, - 14825, - 14826, - 14827, - 14828, - 14829, - 14830, - 14831, - 14832, - 14833, - 14834, - 14835, - 14836, - 14837, - 14838, - 14839, - 14840, - 14841, - 14842, - 14843, - 14844, - 14845, - 14846, - 14847, - 14848, - 14849, - 14850, - 14851, - 14852, - 14853, - 14854, - 14855, - 14856, - 14857, - 14858, - 14859, - 14860, - 14861, - 14862, - 14863, - 14864, - 14865, - 14866, - 14867, - 14868, - 14869, - 14870, - 14871, - 14872, - 14873, - 14874, - 14875, - 14876, - 14877, - 14878, - 14879, - 14880, - 14881, - 14882, - 14883, - 14884, - 14885, - 14886, - 14887, - 14888, - 14889, - 14890, - 14891, - 14892, - 14893, - 14894, - 14895, - 14896, - 14897, - 14898, - 14899, - 14900, - 14901, - 14902, - 14903, - 14904, - 14905, - 14906, - 14907, - 14908, - 14909, - 14910, - 14911, - 14912, - 14913, - 14914, - 14915, - 14916, - 14917, - 14918, - 14919, - 14920, - 14921, - 14922, - 14923, - 14924, - 14925, - 14926, - 14927, - 14928, - 14929, - 14930, - 14931, - 14932, - 14933, - 14934, - 14935, - 14936, - 14937, - 14938, - 14939, - 14940, - 14941, - 14942, - 14943, - 14944, - 14945, - 14946, - 14947, - 14948, - 14949, - 14950, - 14951, - 14952, - 14953, - 14954, - 14955, - 14956, - 14957, - 14958, - 14959, - 14960, - 14961, - 14962, - 14963, - 14964, - 14965, - 14966, - 14967, - 14968, - 14969, - 14970, - 14971, - 14972, - 14973, - 14974, - 14975, - 14976, - 14977, - 14978, - 14979, - 14980, - 14981, - 14982, - 14983, - 14984, - 14985, - 14986, - 14987, - 14988, - 14989, - 14990, - 14991, - 14992, - 14993, - 14994, - 14995, - 14996, - 14997, - 14998, - 14999, - 15000, - 15001, - 15002, - 15003, - 15004, - 15005, - 15006, - 15007, - 15008, - 15009, - 15010, - 15011, - 15012, - 15013, - 15014, - 15015, - 15016, - 15017, - 15018, - 15019, - 15020, - 15021, - 15022, - 15023, - 15024, - 15025, - 15026, - 15027, - 15028, - 15029, - 15030, - 15031, - 15032, - 15033, - 15034, - 15035, - 15036, - 15037, - 15038, - 15039, - 15040, - 15041, - 15042, - 15043, - 15044, - 15045, - 15046, - 15047, - 15048, - 15049, - 15050, - 15051, - 15052, - 15053, - 15054, - 15055, - 15056, - 15057, - 15058, - 15059, - 15060, - 15061, - 15062, - 15063, - 15064, - 15065, - 15066, - 15067, - 15068, - 15069, - 15070, - 15071, - 15072, - 15073, - 15074, - 15075, - 15076, - 15077, - 15078, - 15079, - 15080, - 15081, - 15082, - 15083, - 15084, - 15085, - 15086, - 15087, - 15088, - 15089, - 15090, - 15091, - 15092, - 15093, - 15094, - 15095, - 15096, - 15097, - 15098, - 15099, - 15100, - 15101, - 15102, - 15103, - 15104, - 15105, - 15106, - 15107, - 15108, - 15109, - 15110, - 15111, - 15112, - 15113, - 15114, - 15115, - 15116, - 15117, - 15118, - 15119, - 15120, - 15121, - 15122, - 15123, - 15124, - 15125, - 15126, - 15127, - 15128, - 15129, - 15130, - 15131, - 15132, - 15133, - 15134, - 15135, - 15136, - 15137, - 15138, - 15139, - 15140, - 15141, - 15142, - 15143, - 15144, - 15145, - 15146, - 15147, - 15148, - 15149, - 15150, - 15151, - 15152, - 15153, - 15154, - 15155, - 15156, - 15157, - 15158, - 15159, - 15160, - 15161, - 15162, - 15163, - 15164, - 15165, - 15166, - 15167, - 15168, - 15169, - 15170, - 15171, - 15172, - 15173, - 15174, - 15175, - 15176, - 15177, - 15178, - 15179, - 15180, - 15181, - 15182, - 15183, - 15184, - 15185, - 15186, - 15187, - 15188, - 15189, - 15190, - 15191, - 15192, - 15193, - 15194, - 15195, - 15196, - 15197, - 15198, - 15199, - 15200, - 15201, - 15202, - 15203, - 15204, - 15205, - 15206, - 15207, - 15208, - 15209, - 15210, - 15211, - 15212, - 15213, - 15214, - 15215, - 15216, - 15217, - 15218, - 15219, - 15220, - 15221, - 15222, - 15223, - 15224, - 15225, - 15226, - 15227, - 15228, - 15229, - 15230, - 15231, - 15232, - 15233, - 15234, - 15235, - 15236, - 15237, - 15238, - 15239, - 15240, - 15241, - 15242, - 15243, - 15244, - 15245, - 15246, - 15247, - 15248, - 15249, - 15250, - 15251, - 15252, - 15253, - 15254, - 15255, - 15256, - 15257, - 15258, - 15259, - 15260, - 15261, - 15262, - 15263, - 15264, - 15265, - 15266, - 15267, - 15268, - 15269, - 15270, - 15271, - 15272, - 15273, - 15274, - 15275, - 15276, - 15277, - 15278, - 15279, - 15280, - 15281, - 15282, - 15283, - 15284, - 15285, - 15286, - 15287, - 15288, - 15289, - 15290, - 15291, - 15292, - 15293, - 15294, - 15295, - 15296, - 15297, - 15298, - 15299, - 15300, - 15301, - 15302, - 15303, - 15304, - 15305, - 15306, - 15307, - 15308, - 15309, - 15310, - 15311, - 15312, - 15313, - 15314, - 15315, - 15316, - 15317, - 15318, - 15319, - 15320, - 15321, - 15322, - 15323, - 15324, - 15325, - 15326, - 15327, - 15328, - 15329, - 15330, - 15331, - 15332, - 15333, - 15334, - 15335, - 15336, - 15337, - 15338, - 15339, - 15340, - 15341, - 15342, - 15343, - 15344, - 15345, - 15346, - 15347, - 15348, - 15349, - 15350, - 15351, - 15352, - 15353, - 15354, - 15355, - 15356, - 15357, - 15358, - 15359, - 15360, - 15361, - 15362, - 15363, - 15364, - 15365, - 15366, - 15367, - 15368, - 15369, - 15370, - 15371, - 15372, - 15373, - 15374, - 15375, - 15376, - 15377, - 15378, - 15379, - 15380, - 15381, - 15382, - 15383, - 15384, - 15385, - 15386, - 15387, - 15388, - 15389, - 15390, - 15391, - 15392, - 15393, - 15394, - 15395, - 15396, - 15397, - 15398, - 15399, - 15400, - 15401, - 15402, - 15403, - 15404, - 15405, - 15406, - 15407, - 15408, - 15409, - 15410, - 15411, - 15412, - 15413, - 15414, - 15415, - 15416, - 15417, - 15418, - 15419, - 15420, - 15421, - 15422, - 15423, - 15424, - 15425, - 15426, - 15427, - 15428, - 15429, - 15430, - 15431, - 15432, - 15433, - 15434, - 15435, - 15436, - 15437, - 15438, - 15439, - 15440, - 15441, - 15442, - 15443, - 15444, - 15445, - 15446, - 15447, - 15448, - 15449, - 15450, - 15451, - 15452, - 15453, - 15454, - 15455, - 15456, - 15457, - 15458, - 15459, - 15460, - 15461, - 15462, - 15463, - 15464, - 15465, - 15466, - 15467, - 15468, - 15469, - 15470, - 15471, - 15472, - 15473, - 15474, - 15475, - 15476, - 15477, - 15478, - 15479, - 15480, - 15481, - 15482, - 15483, - 15484, - 15485, - 15486, - 15487, - 15488, - 15489, - 15490, - 15491, - 15492, - 15493, - 15494, - 15495, - 15496, - 15497, - 15498, - 15499, - 15500, - 15501, - 15502, - 15503, - 15504, - 15505, - 15506, - 15507, - 15508, - 15509, - 15510, - 15511, - 15512, - 15513, - 15514, - 15515, - 15516, - 15517, - 15518, - 15519, - 15520, - 15521, - 15522, - 15523, - 15524, - 15525, - 15526, - 15527, - 15528, - 15529, - 15530, - 15531, - 15532, - 15533, - 15534, - 15535, - 15536, - 15537, - 15538, - 15539, - 15540, - 15541, - 15542, - 15543, - 15544, - 15545, - 15546, - 15547, - 15548, - 15549, - 15550, - 15551, - 15552, - 15553, - 15554, - 15555, - 15556, - 15557, - 15558, - 15559, - 15560, - 15561, - 15562, - 15563, - 15564, - 15565, - 15566, - 15567, - 15568, - 15569, - 15570, - 15571, - 15572, - 15573, - 15574, - 15575, - 15576, - 15577, - 15578, - 15579, - 15580, - 15581, - 15582, - 15583, - 15584, - 15585, - 15586, - 15587, - 15588, - 15589, - 15590, - 15591, - 15592, - 15593, - 15594, - 15595, - 15596, - 15597, - 15598, - 15599, - 15600, - 15601, - 15602, - 15603, - 15604, - 15605, - 15606, - 15607, - 15608, - 15609, - 15610, - 15611, - 15612, - 15613, - 15614, - 15615, - 15616, - 15617, - 15618, - 15619, - 15620, - 15621, - 15622, - 15623, - 15624, - 15625, - 15626, - 15627, - 15628, - 15629, - 15630, - 15631, - 15632, - 15633, - 15634, - 15635, - 15636, - 15637, - 15638, - 15639, - 15640, - 15641, - 15642, - 15643, - 15644, - 15645, - 15646, - 15647, - 15648, - 15649, - 15650, - 15651, - 15652, - 15653, - 15654, - 15655, - 15656, - 15657, - 15658, - 15659, - 15660, - 15661, - 15662, - 15663, - 15664, - 15665, - 15666, - 15667, - 15668, - 15669, - 15670, - 15671, - 15672, - 15673, - 15674, - 15675, - 15676, - 15677, - 15678, - 15679, - 15680, - 15681, - 15682, - 15683, - 15684, - 15685, - 15686, - 15687, - 15688, - 15689, - 15690, - 15691, - 15692, - 15693, - 15694, - 15695, - 15696, - 15697, - 15698, - 15699, - 15700, - 15701, - 15702, - 15703, - 15704, - 15705, - 15706, - 15707, - 15708, - 15709, - 15710, - 15711, - 15712, - 15713, - 15714, - 15715, - 15716, - 15717, - 15718, - 15719, - 15720, - 15721, - 15722, - 15723, - 15724, - 15725, - 15726, - 15727, - 15728, - 15729, - 15730, - 15731, - 15732, - 15733, - 15734, - 15735, - 15736, - 15737, - 15738, - 15739, - 15740, - 15741, - 15742, - 15743, - 15744, - 15745, - 15746, - 15747, - 15748, - 15749, - 15750, - 15751, - 15752, - 15753, - 15754, - 15755, - 15756, - 15757, - 15758, - 15759, - 15760, - 15761, - 15762, - 15763, - 15764, - 15765, - 15766, - 15767, - 15768, - 15769, - 15770, - 15771, - 15772, - 15773, - 15774, - 15775, - 15776, - 15777, - 15778, - 15779, - 15780, - 15781, - 15782, - 15783, - 15784, - 15785, - 15786, - 15787, - 15788, - 15789, - 15790, - 15791, - 15792, - 15793, - 15794, - 15795, - 15796, - 15797, - 15798, - 15799, - 15800, - 15801, - 15802, - 15803, - 15804, - 15805, - 15806, - 15807, - 15808, - 15809, - 15810, - 15811, - 15812, - 15813, - 15814, - 15815, - 15816, - 15817, - 15818, - 15819, - 15820, - 15821, - 15822, - 15823, - 15824, - 15825, - 15826, - 15827, - 15828, - 15829, - 15830, - 15831, - 15832, - 15833, - 15834, - 15835, - 15836, - 15837, - 15838, - 15839, - 15840, - 15841, - 15842, - 15843, - 15844, - 15845, - 15846, - 15847, - 15848, - 15849, - 15850, - 15851, - 15852, - 15853, - 15854, - 15855, - 15856, - 15857, - 15858, - 15859, - 15860, - 15861, - 15862, - 15863, - 15864, - 15865, - 15866, - 15867, - 15868, - 15869, - 15870, - 15871, - 15872, - 15873, - 15874, - 15875, - 15876, - 15877, - 15878, - 15879, - 15880, - 15881, - 15882, - 15883, - 15884, - 15885, - 15886, - 15887, - 15888, - 15889, - 15890, - 15891, - 15892, - 15893, - 15894, - 15895, - 15896, - 15897, - 15898, - 15899, - 15900, - 15901, - 15902, - 15903, - 15904, - 15905, - 15906, - 15907, - 15908, - 15909, - 15910, - 15911, - 15912, - 15913, - 15914, - 15915, - 15916, - 15917, - 15918, - 15919, - 15920, - 15921, - 15922, - 15923, - 15924, - 15925, - 15926, - 15927, - 15928, - 15929, - 15930, - 15931, - 15932, - 15933, - 15934, - 15935, - 15936, - 15937, - 15938, - 15939, - 15940, - 15941, - 15942, - 15943, - 15944, - 15945, - 15946, - 15947, - 15948, - 15949, - 15950, - 15951, - 15952, - 15953, - 15954, - 15955, - 15956, - 15957, - 15958, - 15959, - 15960, - 15961, - 15962, - 15963, - 15964, - 15965, - 15966, - 15967, - 15968, - 15969, - 15970, - 15971, - 15972, - 15973, - 15974, - 15975, - 15976, - 15977, - 15978, - 15979, - 15980, - 15981, - 15982, - 15983, - 15984, - 15985, - 15986, - 15987, - 15988, - 15989, - 15990, - 15991, - 15992, - 15993, - 15994, - 15995, - 15996, - 15997, - 15998, - 15999, - 16000, - 16001, - 16002, - 16003, - 16004, - 16005, - 16006, - 16007, - 16008, - 16009, - 16010, - 16011, - 16012, - 16013, - 16014, - 16015, - 16016, - 16017, - 16018, - 16019, - 16020, - 16021, - 16022, - 16023, - 16024, - 16025, - 16026, - 16027, - 16028, - 16029, - 16030, - 16031, - 16032, - 16033, - 16034, - 16035, - 16036, - 16037, - 16038, - 16039, - 16040, - 16041, - 16042, - 16043, - 16044, - 16045, - 16046, - 16047, - 16048, - 16049, - 16050, - 16051, - 16052, - 16053, - 16054, - 16055, - 16056, - 16057, - 16058, - 16059, - 16060, - 16061, - 16062, - 16063, - 16064, - 16065, - 16066, - 16067, - 16068, - 16069, - 16070, - 16071, - 16072, - 16073, - 16074, - 16075, - 16076, - 16077, - 16078, - 16079, - 16080, - 16081, - 16082, - 16083, - 16084, - 16085, - 16086, - 16087, - 16088, - 16089, - 16090, - 16091, - 16092, - 16093, - 16094, - 16095, - 16096, - 16097, - 16098, - 16099, - 16100, - 16101, - 16102, - 16103, - 16104, - 16105, - 16106, - 16107, - 16108, - 16109, - 16110, - 16111, - 16112, - 16113, - 16114, - 16115, - 16116, - 16117, - 16118, - 16119, - 16120, - 16121, - 16122, - 16123, - 16124, - 16125, - 16126, - 16127, - 16128, - 16129, - 16130, - 16131, - 16132, - 16133, - 16134, - 16135, - 16136, - 16137, - 16138, - 16139, - 16140, - 16141, - 16142, - 16143, - 16144, - 16145, - 16146, - 16147, - 16148, - 16149, - 16150, - 16151, - 16152, - 16153, - 16154, - 16155, - 16156, - 16157, - 16158, - 16159, - 16160, - 16161, - 16162, - 16163, - 16164, - 16165, - 16166, - 16167, - 16168, - 16169, - 16170, - 16171, - 16172, - 16173, - 16174, - 16175, - 16176, - 16177, - 16178, - 16179, - 16180, - 16181, - 16182, - 16183, - 16184, - 16185, - 16186, - 16187, - 16188, - 16189, - 16190, - 16191, - 16192, - 16193, - 16194, - 16195, - 16196, - 16197, - 16198, - 16199, - 16200, - 16201, - 16202, - 16203, - 16204, - 16205, - 16206, - 16207, - 16208, - 16209, - 16210, - 16211, - 16212, - 16213, - 16214, - 16215, - 16216, - 16217, - 16218, - 16219, - 16220, - 16221, - 16222, - 16223, - 16224, - 16225, - 16226, - 16227, - 16228, - 16229, - 16230, - 16231, - 16232, - 16233, - 16234, - 16235, - 16236, - 16237, - 16238, - 16239, - 16240, - 16241, - 16242, - 16243, - 16244, - 16245, - 16246, - 16247, - 16248, - 16249, - 16250, - 16251, - 16252, - 16253, - 16254, - 16255, - 16256, - 16257, - 16258, - 16259, - 16260, - 16261, - 16262, - 16263, - 16264, - 16265, - 16266, - 16267, - 16268, - 16269, - 16270, - 16271, - 16272, - 16273, - 16274, - 16275, - 16276, - 16277, - 16278, - 16279, - 16280, - 16281, - 16282, - 16283, - 16284, - 16285, - 16286, - 16287, - 16288, - 16289, - 16290, - 16291, - 16292, - 16293, - 16294, - 16295, - 16296, - 16297, - 16298, - 16299, - 16300, - 16301, - 16302, - 16303, - 16304, - 16305, - 16306, - 16307, - 16308, - 16309, - 16310, - 16311, - 16312, - 16313, - 16314, - 16315, - 16316, - 16317, - 16318, - 16319, - 16320, - 16321, - 16322, - 16323, - 16324, - 16325, - 16326, - 16327, - 16328, - 16329, - 16330, - 16331, - 16332, - 16333, - 16334, - 16335, - 16336, - 16337, - 16338, - 16339, - 16340, - 16341, - 16342, - 16343, - 16344, - 16345, - 16346, - 16347, - 16348, - 16349, - 16350, - 16351, - 16352, - 16353, - 16354, - 16355, - 16356, - 16357, - 16358, - 16359, - 16360, - 16361, - 16362, - 16363, - 16364, - 16365, - 16366, - 16367, - 16368, - 16369, - 16370, - 16371, - 16372, - 16373, - 16374, - 16375, - 16376, - 16377, - 16378, - 16379, - 16380, - 16381, - 16382, - 16383, - 16384, - 16385, - 16386, - 16387, - 16388, - 16389, - 16390, - 16391, - 16392, - 16393, - 16394, - 16395, - 16396, - 16397, - 16398, - 16399, - 16400, - 16401, - 16402, - 16403, - 16404, - 16405, - 16406, - 16407, - 16408, - 16409, - 16410, - 16411, - 16412, - 16413, - 16414, - 16415, - 16416, - 16417, - 16418, - 16419, - 16420, - 16421, - 16422, - 16423, - 16424, - 16425, - 16426, - 16427, - 16428, - 16429, - 16430, - 16431, - 16432, - 16433, - 16434, - 16435, - 16436, - 16437, - 16438, - 16439, - 16440, - 16441, - 16442, - 16443, - 16444, - 16445, - 16446, - 16447, - 16448, - 16449, - 16450, - 16451, - 16452, - 16453, - 16454, - 16455, - 16456, - 16457, - 16458, - 16459, - 16460, - 16461, - 16462, - 16463, - 16464, - 16465, - 16466, - 16467, - 16468, - 16469, - 16470, - 16471, - 16472, - 16473, - 16474, - 16475, - 16476, - 16477, - 16478, - 16479, - 16480, - 16481, - 16482, - 16483, - 16484, - 16485, - 16486, - 16487, - 16488, - 16489, - 16490, - 16491, - 16492, - 16493, - 16494, - 16495, - 16496, - 16497, - 16498, - 16499, - 16500, - 16501, - 16502, - 16503, - 16504, - 16505, - 16506, - 16507, - 16508, - 16509, - 16510, - 16511, - 16512, - 16513, - 16514, - 16515, - 16516, - 16517, - 16518, - 16519, - 16520, - 16521, - 16522, - 16523, - 16524, - 16525, - 16526, - 16527, - 16528, - 16529, - 16530, - 16531, - 16532, - 16533, - 16534, - 16535, - 16536, - 16537, - 16538, - 16539, - 16540, - 16541, - 16542, - 16543, - 16544, - 16545, - 16546, - 16547, - 16548, - 16549, - 16550, - 16551, - 16552, - 16553, - 16554, - 16555, - 16556, - 16557, - 16558, - 16559, - 16560, - 16561, - 16562, - 16563, - 16564, - 16565, - 16566, - 16567, - 16568, - 16569, - 16570, - 16571, - 16572, - 16573, - 16574, - 16575, - 16576, - 16577, - 16578, - 16579, - 16580, - 16581, - 16582, - 16583, - 16584, - 16585, - 16586, - 16587, - 16588, - 16589, - 16590, - 16591, - 16592, - 16593, - 16594, - 16595, - 16596, - 16597, - 16598, - 16599, - 16600, - 16601, - 16602, - 16603, - 16604, - 16605, - 16606, - 16607, - 16608, - 16609, - 16610, - 16611, - 16612, - 16613, - 16614, - 16615, - 16616, - 16617, - 16618, - 16619, - 16620, - 16621, - 16622, - 16623, - 16624, - 16625, - 16626, - 16627, - 16628, - 16629, - 16630, - 16631, - 16632, - 16633, - 16634, - 16635, - 16636, - 16637, - 16638, - 16639, - 16640, - 16641, - 16642, - 16643, - 16644, - 16645, - 16646, - 16647, - 16648, - 16649, - 16650, - 16651, - 16652, - 16653, - 16654, - 16655, - 16656, - 16657, - 16658, - 16659, - 16660, - 16661, - 16662, - 16663, - 16664, - 16665, - 16666, - 16667, - 16668, - 16669, - 16670, - 16671, - 16672, - 16673, - 16674, - 16675, - 16676, - 16677, - 16678, - 16679, - 16680, - 16681, - 16682, - 16683, - 16684, - 16685, - 16686, - 16687, - 16688, - 16689, - 16690, - 16691, - 16692, - 16693, - 16694, - 16695, - 16696, - 16697, - 16698, - 16699, - 16700, - 16701, - 16702, - 16703, - 16704, - 16705, - 16706, - 16707, - 16708, - 16709, - 16710, - 16711, - 16712, - 16713, - 16714, - 16715, - 16716, - 16717, - 16718, - 16719, - 16720, - 16721, - 16722, - 16723, - 16724, - 16725, - 16726, - 16727, - 16728, - 16729, - 16730, - 16731, - 16732, - 16733, - 16734, - 16735, - 16736, - 16737, - 16738, - 16739, - 16740, - 16741, - 16742, - 16743, - 16744, - 16745, - 16746, - 16747, - 16748, - 16749, - 16750, - 16751, - 16752, - 16753, - 16754, - 16755, - 16756, - 16757, - 16758, - 16759, - 16760, - 16761, - 16762, - 16763, - 16764, - 16765, - 16766, - 16767, - 16768, - 16769, - 16770, - 16771, - 16772, - 16773, - 16774, - 16775, - 16776, - 16777, - 16778, - 16779, - 16780, - 16781, - 16782, - 16783, - 16784, - 16785, - 16786, - 16787, - 16788, - 16789, - 16790, - 16791, - 16792, - 16793, - 16794, - 16795, - 16796, - 16797, - 16798, - 16799, - 16800, - 16801, - 16802, - 16803, - 16804, - 16805, - 16806, - 16807, - 16808, - 16809, - 16810, - 16811, - 16812, - 16813, - 16814, - 16815, - 16816, - 16817, - 16818, - 16819, - 16820, - 16821, - 16822, - 16823, - 16824, - 16825, - 16826, - 16827, - 16828, - 16829, - 16830, - 16831, - 16832, - 16833, - 16834, - 16835, - 16836, - 16837, - 16838, - 16839, - 16840, - 16841, - 16842, - 16843, - 16844, - 16845, - 16846, - 16847, - 16848, - 16849, - 16850, - 16851, - 16852, - 16853, - 16854, - 16855, - 16856, - 16857, - 16858, - 16859, - 16860, - 16861, - 16862, - 16863, - 16864, - 16865, - 16866, - 16867, - 16868, - 16869, - 16870, - 16871, - 16872, - 16873, - 16874, - 16875, - 16876, - 16877, - 16878, - 16879, - 16880, - 16881, - 16882, - 16883, - 16884, - 16885, - 16886, - 16887, - 16888, - 16889, - 16890, - 16891, - 16892, - 16893, - 16894, - 16895, - 16896, - 16897, - 16898, - 16899, - 16900, - 16901, - 16902, - 16903, - 16904, - 16905, - 16906, - 16907, - 16908, - 16909, - 16910, - 16911, - 16912, - 16913, - 16914, - 16915, - 16916, - 16917, - 16918, - 16919, - 16920, - 16921, - 16922, - 16923, - 16924, - 16925, - 16926, - 16927, - 16928, - 16929, - 16930, - 16931, - 16932, - 16933, - 16934, - 16935, - 16936, - 16937, - 16938, - 16939, - 16940, - 16941, - 16942, - 16943, - 16944, - 16945, - 16946, - 16947, - 16948, - 16949, - 16950, - 16951, - 16952, - 16953, - 16954, - 16955, - 16956, - 16957, - 16958, - 16959, - 16960, - 16961, - 16962, - 16963, - 16964, - 16965, - 16966, - 16967, - 16968, - 16969, - 16970, - 16971, - 16972, - 16973, - 16974, - 16975, - 16976, - 16977, - 16978, - 16979, - 16980, - 16981, - 16982, - 16983, - 16984, - 16985, - 16986, - 16987, - 16988, - 16989, - 16990, - 16991, - 16992, - 16993, - 16994, - 16995, - 16996, - 16997, - 16998, - 16999, - 17000, - 17001, - 17002, - 17003, - 17004, - 17005, - 17006, - 17007, - 17008, - 17009, - 17010, - 17011, - 17012, - 17013, - 17014, - 17015, - 17016, - 17017, - 17018, - 17019, - 17020, - 17021, - 17022, - 17023, - 17024, - 17025, - 17026, - 17027, - 17028, - 17029, - 17030, - 17031, - 17032, - 17033, - 17034, - 17035, - 17036, - 17037, - 17038, - 17039, - 17040, - 17041, - 17042, - 17043, - 17044, - 17045, - 17046, - 17047, - 17048, - 17049, - 17050, - 17051, - 17052, - 17053, - 17054, - 17055, - 17056, - 17057, - 17058, - 17059, - 17060, - 17061, - 17062, - 17063, - 17064, - 17065, - 17066, - 17067, - 17068, - 17069, - 17070, - 17071, - 17072, - 17073, - 17074, - 17075, - 17076, - 17077, - 17078, - 17079, - 17080, - 17081, - 17082, - 17083, - 17084, - 17085, - 17086, - 17087, - 17088, - 17089, - 17090, - 17091, - 17092, - 17093, - 17094, - 17095, - 17096, - 17097, - 17098, - 17099, - 17100, - 17101, - 17102, - 17103, - 17104, - 17105, - 17106, - 17107, - 17108, - 17109, - 17110, - 17111, - 17112, - 17113, - 17114, - 17115, - 17116, - 17117, - 17118, - 17119, - 17120, - 17121, - 17122, - 17123, - 17124, - 17125, - 17126, - 17127, - 17128, - 17129, - 17130, - 17131, - 17132, - 17133, - 17134, - 17135, - 17136, - 17137, - 17138, - 17139, - 17140, - 17141, - 17142, - 17143, - 17144, - 17145, - 17146, - 17147, - 17148, - 17149, - 17150, - 17151, - 17152, - 17153, - 17154, - 17155, - 17156, - 17157, - 17158, - 17159, - 17160, - 17161, - 17162, - 17163, - 17164, - 17165, - 17166, - 17167, - 17168, - 17169, - 17170, - 17171, - 17172, - 17173, - 17174, - 17175, - 17176, - 17177, - 17178, - 17179, - 17180, - 17181, - 17182, - 17183, - 17184, - 17185, - 17186, - 17187, - 17188, - 17189, - 17190, - 17191, - 17192, - 17193, - 17194, - 17195, - 17196, - 17197, - 17198, - 17199, - 17200, - 17201, - 17202, - 17203, - 17204, - 17205, - 17206, - 17207, - 17208, - 17209, - 17210, - 17211, - 17212, - 17213, - 17214, - 17215, - 17216, - 17217, - 17218, - 17219, - 17220, - 17221, - 17222, - 17223, - 17224, - 17225, - 17226, - 17227, - 17228, - 17229, - 17230, - 17231, - 17232, - 17233, - 17234, - 17235, - 17236, - 17237, - 17238, - 17239, - 17240, - 17241, - 17242, - 17243, - 17244, - 17245, - 17246, - 17247, - 17248, - 17249, - 17250, - 17251, - 17252, - 17253, - 17254, - 17255, - 17256, - 17257, - 17258, - 17259, - 17260, - 17261, - 17262, - 17263, - 17264, - 17265, - 17266, - 17267, - 17268, - 17269, - 17270, - 17271, - 17272, - 17273, - 17274, - 17275, - 17276, - 17277, - 17278, - 17279, - 17280, - 17281, - 17282, - 17283, - 17284, - 17285, - 17286, - 17287, - 17288, - 17289, - 17290, - 17291, - 17292, - 17293, - 17294, - 17295, - 17296, - 17297, - 17298, - 17299, - 17300, - 17301, - 17302, - 17303, - 17304, - 17305, - 17306, - 17307, - 17308, - 17309, - 17310, - 17311, - 17312, - 17313, - 17314, - 17315, - 17316, - 17317, - 17318, - 17319, - 17320, - 17321, - 17322, - 17323, - 17324, - 17325, - 17326, - 17327, - 17328, - 17329, - 17330, - 17331, - 17332, - 17333, - 17334, - 17335, - 17336, - 17337, - 17338, - 17339, - 17340, - 17341, - 17342, - 17343, - 17344, - 17345, - 17346, - 17347, - 17348, - 17349, - 17350, - 17351, - 17352, - 17353, - 17354, - 17355, - 17356, - 17357, - 17358, - 17359, - 17360, - 17361, - 17362, - 17363, - 17364, - 17365, - 17366, - 17367, - 17368, - 17369, - 17370, - 17371, - 17372, - 17373, - 17374, - 17375, - 17376, - 17377, - 17378, - 17379, - 17380, - 17381, - 17382, - 17383, - 17384, - 17385, - 17386, - 17387, - 17388, - 17389, - 17390, - 17391, - 17392, - 17393, - 17394, - 17395, - 17396, - 17397, - 17398, - 17399, - 17400, - 17401, - 17402, - 17403, - 17404, - 17405, - 17406, - 17407, - 17408, - 17409, - 17410, - 17411, - 17412, - 17413, - 17414, - 17415, - 17416, - 17417, - 17418, - 17419, - 17420, - 17421, - 17422, - 17423, - 17424, - 17425, - 17426, - 17427, - 17428, - 17429, - 17430, - 17431, - 17432, - 17433, - 17434, - 17435, - 17436, - 17437, - 17438, - 17439, - 17440, - 17441, - 17442, - 17443, - 17444, - 17445, - 17446, - 17447, - 17448, - 17449, - 17450, - 17451, - 17452, - 17453, - 17454, - 17455, - 17456, - 17457, - 17458, - 17459, - 17460, - 17461, - 17462, - 17463, - 17464, - 17465, - 17466, - 17467, - 17468, - 17469, - 17470, - 17471, - 17472, - 17473, - 17474, - 17475, - 17476, - 17477, - 17478, - 17479, - 17480, - 17481, - 17482, - 17483, - 17484, - 17485, - 17486, - 17487, - 17488, - 17489, - 17490, - 17491, - 17492, - 17493, - 17494, - 17495, - 17496, - 17497, - 17498, - 17499, - 17500, - 17501, - 17502, - 17503, - 17504, - 17505, - 17506, - 17507, - 17508, - 17509, - 17510, - 17511, - 17512, - 17513, - 17514, - 17515, - 17516, - 17517, - 17518, - 17519, - 17520, - 17521, - 17522, - 17523, - 17524, - 17525, - 17526, - 17527, - 17528, - 17529, - 17530, - 17531, - 17532, - 17533, - 17534, - 17535, - 17536, - 17537, - 17538, - 17539, - 17540, - 17541, - 17542, - 17543, - 17544, - 17545, - 17546, - 17547, - 17548, - 17549, - 17550, - 17551, - 17552, - 17553, - 17554, - 17555, - 17556, - 17557, - 17558, - 17559, - 17560, - 17561, - 17562, - 17563, - 17564, - 17565, - 17566, - 17567, - 17568, - 17569, - 17570, - 17571, - 17572, - 17573, - 17574, - 17575, - 17576, - 17577, - 17578, - 17579, - 17580, - 17581, - 17582, - 17583, - 17584, - 17585, - 17586, - 17587, - 17588, - 17589, - 17590, - 17591, - 17592, - 17593, - 17594, - 17595, - 17596, - 17597, - 17598, - 17599, - 17600, - 17601, - 17602, - 17603, - 17604, - 17605, - 17606, - 17607, - 17608, - 17609, - 17610, - 17611, - 17612, - 17613, - 17614, - 17615, - 17616, - 17617, - 17618, - 17619, - 17620, - 17621, - 17622, - 17623, - 17624, - 17625, - 17626, - 17627, - 17628, - 17629, - 17630, - 17631, - 17632, - 17633, - 17634, - 17635, - 17636, - 17637, - 17638, - 17639, - 17640, - 17641, - 17642, - 17643, - 17644, - 17645, - 17646, - 17647, - 17648, - 17649, - 17650, - 17651, - 17652, - 17653, - 17654, - 17655, - 17656, - 17657, - 17658, - 17659, - 17660, - 17661, - 17662, - 17663, - 17664, - 17665, - 17666, - 17667, - 17668, - 17669, - 17670, - 17671, - 17672, - 17673, - 17674, - 17675, - 17676, - 17677, - 17678, - 17679, - 17680, - 17681, - 17682, - 17683, - 17684, - 17685, - 17686, - 17687, - 17688, - 17689, - 17690, - 17691, - 17692, - 17693, - 17694, - 17695, - 17696, - 17697, - 17698, - 17699, - 17700, - 17701, - 17702, - 17703, - 17704, - 17705, - 17706, - 17707, - 17708, - 17709, - 17710, - 17711, - 17712, - 17713, - 17714, - 17715, - 17716, - 17717, - 17718, - 17719, - 17720, - 17721, - 17722, - 17723, - 17724, - 17725, - 17726, - 17727, - 17728, - 17729, - 17730, - 17731, - 17732, - 17733, - 17734, - 17735, - 17736, - 17737, - 17738, - 17739, - 17740, - 17741, - 17742, - 17743, - 17744, - 17745, - 17746, - 17747, - 17748, - 17749, - 17750, - 17751, - 17752, - 17753, - 17754, - 17755, - 17756, - 17757, - 17758, - 17759, - 17760, - 17761, - 17762, - 17763, - 17764, - 17765, - 17766, - 17767, - 17768, - 17769, - 17770, - 17771, - 17772, - 17773, - 17774, - 17775, - 17776, - 17777, - 17778, - 17779, - 17780, - 17781, - 17782, - 17783, - 17784, - 17785, - 17786, - 17787, - 17788, - 17789, - 17790, - 17791, - 17792, - 17793, - 17794, - 17795, - 17796, - 17797, - 17798, - 17799, - 17800, - 17801, - 17802, - 17803, - 17804, - 17805, - 17806, - 17807, - 17808, - 17809, - 17810, - 17811, - 17812, - 17813, - 17814, - 17815, - 17816, - 17817, - 17818, - 17819, - 17820, - 17821, - 17822, - 17823, - 17824, - 17825, - 17826, - 17827, - 17828, - 17829, - 17830, - 17831, - 17832, - 17833, - 17834, - 17835, - 17836, - 17837, - 17838, - 17839, - 17840, - 17841, - 17842, - 17843, - 17844, - 17845, - 17846, - 17847, - 17848, - 17849, - 17850, - 17851, - 17852, - 17853, - 17854, - 17855, - 17856, - 17857, - 17858, - 17859, - 17860, - 17861, - 17862, - 17863, - 17864, - 17865, - 17866, - 17867, - 17868, - 17869, - 17870, - 17871, - 17872, - 17873, - 17874, - 17875, - 17876, - 17877, - 17878, - 17879, - 17880, - 17881, - 17882, - 17883, - 17884, - 17885, - 17886, - 17887, - 17888, - 17889, - 17890, - 17891, - 17892, - 17893, - 17894, - 17895, - 17896, - 17897, - 17898, - 17899, - 17900, - 17901, - 17902, - 17903, - 17904, - 17905, - 17906, - 17907, - 17908, - 17909, - 17910, - 17911, - 17912, - 17913, - 17914, - 17915, - 17916, - 17917, - 17918, - 17919, - 17920, - 17921, - 17922, - 17923, - 17924, - 17925, - 17926, - 17927, - 17928, - 17929, - 17930, - 17931, - 17932, - 17933, - 17934, - 17935, - 17936, - 17937, - 17938, - 17939, - 17940, - 17941, - 17942, - 17943, - 17944, - 17945, - 17946, - 17947, - 17948, - 17949, - 17950, - 17951, - 17952, - 17953, - 17954, - 17955, - 17956, - 17957, - 17958, - 17959, - 17960, - 17961, - 17962, - 17963, - 17964, - 17965, - 17966, - 17967, - 17968, - 17969, - 17970, - 17971, - 17972, - 17973, - 17974, - 17975, - 17976, - 17977, - 17978, - 17979, - 17980, - 17981, - 17982, - 17983, - 17984, - 17985, - 17986, - 17987, - 17988, - 17989, - 17990, - 17991, - 17992, - 17993, - 17994, - 17995, - 17996, - 17997, - 17998, - 17999, - 18000, - 18001, - 18002, - 18003, - 18004, - 18005, - 18006, - 18007, - 18008, - 18009, - 18010, - 18011, - 18012, - 18013, - 18014, - 18015, - 18016, - 18017, - 18018, - 18019, - 18020, - 18021, - 18022, - 18023, - 18024, - 18025, - 18026, - 18027, - 18028, - 18029, - 18030, - 18031, - 18032, - 18033, - 18034, - 18035, - 18036, - 18037, - 18038, - 18039, - 18040, - 18041, - 18042, - 18043, - 18044, - 18045, - 18046, - 18047, - 18048, - 18049, - 18050, - 18051, - 18052, - 18053, - 18054, - 18055, - 18056, - 18057, - 18058, - 18059, - 18060, - 18061, - 18062, - 18063, - 18064, - 18065, - 18066, - 18067, - 18068, - 18069, - 18070, - 18071, - 18072, - 18073, - 18074, - 18075, - 18076, - 18077, - 18078, - 18079, - 18080, - 18081, - 18082, - 18083, - 18084, - 18085, - 18086, - 18087, - 18088, - 18089, - 18090, - 18091, - 18092, - 18093, - 18094, - 18095, - 18096, - 18097, - 18098, - 18099, - 18100, - 18101, - 18102, - 18103, - 18104, - 18105, - 18106, - 18107, - 18108, - 18109, - 18110, - 18111, - 18112, - 18113, - 18114, - 18115, - 18116, - 18117, - 18118, - 18119, - 18120, - 18121, - 18122, - 18123, - 18124, - 18125, - 18126, - 18127, - 18128, - 18129, - 18130, - 18131, - 18132, - 18133, - 18134, - 18135, - 18136, - 18137, - 18138, - 18139, - 18140, - 18141, - 18142, - 18143, - 18144, - 18145, - 18146, - 18147, - 18148, - 18149, - 18150, - 18151, - 18152, - 18153, - 18154, - 18155, - 18156, - 18157, - 18158, - 18159, - 18160, - 18161, - 18162, - 18163, - 18164, - 18165, - 18166, - 18167, - 18168, - 18169, - 18170, - 18171, - 18172, - 18173, - 18174, - 18175, - 18176, - 18177, - 18178, - 18179, - 18180, - 18181, - 18182, - 18183, - 18184, - 18185, - 18186, - 18187, - 18188, - 18189, - 18190, - 18191, - 18192, - 18193, - 18194, - 18195, - 18196, - 18197, - 18198, - 18199, - 18200, - 18201, - 18202, - 18203, - 18204, - 18205, - 18206, - 18207, - 18208, - 18209, - 18210, - 18211, - 18212, - 18213, - 18214, - 18215, - 18216, - 18217, - 18218, - 18219, - 18220, - 18221, - 18222, - 18223, - 18224, - 18225, - 18226, - 18227, - 18228, - 18229, - 18230, - 18231, - 18232, - 18233, - 18234, - 18235, - 18236, - 18237, - 18238, - 18239, - 18240, - 18241, - 18242, - 18243, - 18244, - 18245, - 18246, - 18247, - 18248, - 18249, - 18250, - 18251, - 18252, - 18253, - 18254, - 18255, - 18256, - 18257, - 18258, - 18259, - 18260, - 18261, - 18262, - 18263, - 18264, - 18265, - 18266, - 18267, - 18268, - 18269, - 18270, - 18271, - 18272, - 18273, - 18274, - 18275, - 18276, - 18277, - 18278, - 18279, - 18280, - 18281, - 18282, - 18283, - 18284, - 18285, - 18286, - 18287, - 18288, - 18289, - 18290, - 18291, - 18292, - 18293, - 18294, - 18295, - 18296, - 18297, - 18298, - 18299, - 18300, - 18301, - 18302, - 18303, - 18304, - 18305, - 18306, - 18307, - 18308, - 18309, - 18310, - 18311, - 18312, - 18313, - 18314, - 18315, - 18316, - 18317, - 18318, - 18319, - 18320, - 18321, - 18322, - 18323, - 18324, - 18325, - 18326, - 18327, - 18328, - 18329, - 18330, - 18331, - 18332, - 18333, - 18334, - 18335, - 18336, - 18337, - 18338, - 18339, - 18340, - 18341, - 18342, - 18343, - 18344, - 18345, - 18346, - 18347, - 18348, - 18349, - 18350, - 18351, - 18352, - 18353, - 18354, - 18355, - 18356, - 18357, - 18358, - 18359, - 18360, - 18361, - 18362, - 18363, - 18364, - 18365, - 18366, - 18367, - 18368, - 18369, - 18370, - 18371, - 18372, - 18373, - 18374, - 18375, - 18376, - 18377, - 18378, - 18379, - 18380, - 18381, - 18382, - 18383, - 18384, - 18385, - 18386, - 18387, - 18388, - 18389, - 18390, - 18391, - 18392, - 18393, - 18394, - 18395, - 18396, - 18397, - 18398, - 18399, - 18400, - 18401, - 18402, - 18403, - 18404, - 18405, - 18406, - 18407, - 18408, - 18409, - 18410, - 18411, - 18412, - 18413, - 18414, - 18415, - 18416, - 18417, - 18418, - 18419, - 18420, - 18421, - 18422, - 18423, - 18424, - 18425, - 18426, - 18427, - 18428, - 18429, - 18430, - 18431, - 18432, - 18433, - 18434, - 18435, - 18436, - 18437, - 18438, - 18439, - 18440, - 18441, - 18442, - 18443, - 18444, - 18445, - 18446, - 18447, - 18448, - 18449, - 18450, - 18451, - 18452, - 18453, - 18454, - 18455, - 18456, - 18457, - 18458, - 18459, - 18460, - 18461, - 18462, - 18463, - 18464, - 18465, - 18466, - 18467, - 18468, - 18469, - 18470, - 18471, - 18472, - 18473, - 18474, - 18475, - 18476, - 18477, - 18478, - 18479, - 18480, - 18481, - 18482, - 18483, - 18484, - 18485, - 18486, - 18487, - 18488, - 18489, - 18490, - 18491, - 18492, - 18493, - 18494, - 18495, - 18496, - 18497, - 18498, - 18499, - 18500, - 18501, - 18502, - 18503, - 18504, - 18505, - 18506, - 18507, - 18508, - 18509, - 18510, - 18511, - 18512, - 18513, - 18514, - 18515, - 18516, - 18517, - 18518, - 18519, - 18520, - 18521, - 18522, - 18523, - 18524, - 18525, - 18526, - 18527, - 18528, - 18529, - 18530, - 18531, - 18532, - 18533, - 18534, - 18535, - 18536, - 18537, - 18538, - 18539, - 18540, - 18541, - 18542, - 18543, - 18544, - 18545, - 18546, - 18547, - 18548, - 18549, - 18550, - 18551, - 18552, - 18553, - 18554, - 18555, - 18556, - 18557, - 18558, - 18559, - 18560, - 18561, - 18562, - 18563, - 18564, - 18565, - 18566, - 18567, - 18568, - 18569, - 18570, - 18571, - 18572, - 18573, - 18574, - 18575, - 18576, - 18577, - 18578, - 18579, - 18580, - 18581, - 18582, - 18583, - 18584, - 18585, - 18586, - 18587, - 18588, - 18589, - 18590, - 18591, - 18592, - 18593, - 18594, - 18595, - 18596, - 18597, - 18598, - 18599, - 18600, - 18601, - 18602, - 18603, - 18604, - 18605, - 18606, - 18607, - 18608, - 18609, - 18610, - 18611, - 18612, - 18613, - 18614, - 18615, - 18616, - 18617, - 18618, - 18619, - 18620, - 18621, - 18622, - 18623, - 18624, - 18625, - 18626, - 18627, - 18628, - 18629, - 18630, - 18631, - 18632, - 18633, - 18634, - 18635, - 18636, - 18637, - 18638, - 18639, - 18640, - 18641, - 18642, - 18643, - 18644, - 18645, - 18646, - 18647, - 18648, - 18649, - 18650, - 18651, - 18652, - 18653, - 18654, - 18655, - 18656, - 18657, - 18658, - 18659, - 18660, - 18661, - 18662, - 18663, - 18664, - 18665, - 18666, - 18667, - 18668, - 18669, - 18670, - 18671, - 18672, - 18673, - 18674, - 18675, - 18676, - 18677, - 18678, - 18679, - 18680, - 18681, - 18682, - 18683, - 18684, - 18685, - 18686, - 18687, - 18688, - 18689, - 18690, - 18691, - 18692, - 18693, - 18694, - 18695, - 18696, - 18697, - 18698, - 18699, - 18700, - 18701, - 18702, - 18703, - 18704, - 18705, - 18706, - 18707, - 18708, - 18709, - 18710, - 18711, - 18712, - 18713, - 18714, - 18715, - 18716, - 18717, - 18718, - 18719, - 18720, - 18721, - 18722, - 18723, - 18724, - 18725, - 18726, - 18727, - 18728, - 18729, - 18730, - 18731, - 18732, - 18733, - 18734, - 18735, - 18736, - 18737, - 18738, - 18739, - 18740, - 18741, - 18742, - 18743, - 18744, - 18745, - 18746, - 18747, - 18748, - 18749, - 18750, - 18751, - 18752, - 18753, - 18754, - 18755, - 18756, - 18757, - 18758, - 18759, - 18760, - 18761, - 18762, - 18763, - 18764, - 18765, - 18766, - 18767, - 18768, - 18769, - 18770, - 18771, - 18772, - 18773, - 18774, - 18775, - 18776, - 18777, - 18778, - 18779, - 18780, - 18781, - 18782, - 18783, - 18784, - 18785, - 18786, - 18787, - 18788, - 18789, - 18790, - 18791, - 18792, - 18793, - 18794, - 18795, - 18796, - 18797, - 18798, - 18799, - 18800, - 18801, - 18802, - 18803, - 18804, - 18805, - 18806, - 18807, - 18808, - 18809, - 18810, - 18811, - 18812, - 18813, - 18814, - 18815, - 18816, - 18817, - 18818, - 18819, - 18820, - 18821, - 18822, - 18823, - 18824, - 18825, - 18826, - 18827, - 18828, - 18829, - 18830, - 18831, - 18832, - 18833, - 18834, - 18835, - 18836, - 18837, - 18838, - 18839, - 18840, - 18841, - 18842, - 18843, - 18844, - 18845, - 18846, - 18847, - 18848, - 18849, - 18850, - 18851, - 18852, - 18853, - 18854, - 18855, - 18856, - 18857, - 18858, - 18859, - 18860, - 18861, - 18862, - 18863, - 18864, - 18865, - 18866, - 18867, - 18868, - 18869, - 18870, - 18871, - 18872, - 18873, - 18874, - 18875, - 18876, - 18877, - 18878, - 18879, - 18880, - 18881, - 18882, - 18883, - 18884, - 18885, - 18886, - 18887, - 18888, - 18889, - 18890, - 18891, - 18892, - 18893, - 18894, - 18895, - 18896, - 18897, - 18898, - 18899, - 18900, - 18901, - 18902, - 18903, - 18904, - 18905, - 18906, - 18907, - 18908, - 18909, - 18910, - 18911, - 18912, - 18913, - 18914, - 18915, - 18916, - 18917, - 18918, - 18919, - 18920, - 18921, - 18922, - 18923, - 18924, - 18925, - 18926, - 18927, - 18928, - 18929, - 18930, - 18931, - 18932, - 18933, - 18934, - 18935, - 18936, - 18937, - 18938, - 18939, - 18940, - 18941, - 18942, - 18943, - 18944, - 18945, - 18946, - 18947, - 18948, - 18949, - 18950, - 18951, - 18952, - 18953, - 18954, - 18955, - 18956, - 18957, - 18958, - 18959, - 18960, - 18961, - 18962, - 18963, - 18964, - 18965, - 18966, - 18967, - 18968, - 18969, - 18970, - 18971, - 18972, - 18973, - 18974, - 18975, - 18976, - 18977, - 18978, - 18979, - 18980, - 18981, - 18982, - 18983, - 18984, - 18985, - 18986, - 18987, - 18988, - 18989, - 18990, - 18991, - 18992, - 18993, - 18994, - 18995, - 18996, - 18997, - 18998, - 18999, - 19000, - 19001, - 19002, - 19003, - 19004, - 19005, - 19006, - 19007, - 19008, - 19009, - 19010, - 19011, - 19012, - 19013, - 19014, - 19015, - 19016, - 19017, - 19018, - 19019, - 19020, - 19021, - 19022, - 19023, - 19024, - 19025, - 19026, - 19027, - 19028, - 19029, - 19030, - 19031, - 19032, - 19033, - 19034, - 19035, - 19036, - 19037, - 19038, - 19039, - 19040, - 19041, - 19042, - 19043, - 19044, - 19045, - 19046, - 19047, - 19048, - 19049, - 19050, - 19051, - 19052, - 19053, - 19054, - 19055, - 19056, - 19057, - 19058, - 19059, - 19060, - 19061, - 19062, - 19063, - 19064, - 19065, - 19066, - 19067, - 19068, - 19069, - 19070, - 19071, - 19072, - 19073, - 19074, - 19075, - 19076, - 19077, - 19078, - 19079, - 19080, - 19081, - 19082, - 19083, - 19084, - 19085, - 19086, - 19087, - 19088, - 19089, - 19090, - 19091, - 19092, - 19093, - 19094, - 19095, - 19096, - 19097, - 19098, - 19099, - 19100, - 19101, - 19102, - 19103, - 19104, - 19105, - 19106, - 19107, - 19108, - 19109, - 19110, - 19111, - 19112, - 19113, - 19114, - 19115, - 19116, - 19117, - 19118, - 19119, - 19120, - 19121, - 19122, - 19123, - 19124, - 19125, - 19126, - 19127, - 19128, - 19129, - 19130, - 19131, - 19132, - 19133, - 19134, - 19135, - 19136, - 19137, - 19138, - 19139, - 19140, - 19141, - 19142, - 19143, - 19144, - 19145, - 19146, - 19147, - 19148, - 19149, - 19150, - 19151, - 19152, - 19153, - 19154, - 19155, - 19156, - 19157, - 19158, - 19159, - 19160, - 19161, - 19162, - 19163, - 19164, - 19165, - 19166, - 19167, - 19168, - 19169, - 19170, - 19171, - 19172, - 19173, - 19174, - 19175, - 19176, - 19177, - 19178, - 19179, - 19180, - 19181, - 19182, - 19183, - 19184, - 19185, - 19186, - 19187, - 19188, - 19189, - 19190, - 19191, - 19192, - 19193, - 19194, - 19195, - 19196, - 19197, - 19198, - 19199, - 19200, - 19201, - 19202, - 19203, - 19204, - 19205, - 19206, - 19207, - 19208, - 19209, - 19210, - 19211, - 19212, - 19213, - 19214, - 19215, - 19216, - 19217, - 19218, - 19219, - 19220, - 19221, - 19222, - 19223, - 19224, - 19225, - 19226, - 19227, - 19228, - 19229, - 19230, - 19231, - 19232, - 19233, - 19234, - 19235, - 19236, - 19237, - 19238, - 19239, - 19240, - 19241, - 19242, - 19243, - 19244, - 19245, - 19246, - 19247, - 19248, - 19249, - 19250, - 19251, - 19252, - 19253, - 19254, - 19255, - 19256, - 19257, - 19258, - 19259, - 19260, - 19261, - 19262, - 19263, - 19264, - 19265, - 19266, - 19267, - 19268, - 19269, - 19270, - 19271, - 19272, - 19273, - 19274, - 19275, - 19276, - 19277, - 19278, - 19279, - 19280, - 19281, - 19282, - 19283, - 19284, - 19285, - 19286, - 19287, - 19288, - 19289, - 19290, - 19291, - 19292, - 19293, - 19294, - 19295, - 19296, - 19297, - 19298, - 19299, - 19300, - 19301, - 19302, - 19303, - 19304, - 19305, - 19306, - 19307, - 19308, - 19309, - 19310, - 19311, - 19312, - 19313, - 19314, - 19315, - 19316, - 19317, - 19318, - 19319, - 19320, - 19321, - 19322, - 19323, - 19324, - 19325, - 19326, - 19327, - 19328, - 19329, - 19330, - 19331, - 19332, - 19333, - 19334, - 19335, - 19336, - 19337, - 19338, - 19339, - 19340, - 19341, - 19342, - 19343, - 19344, - 19345, - 19346, - 19347, - 19348, - 19349, - 19350, - 19351, - 19352, - 19353, - 19354, - 19355, - 19356, - 19357, - 19358, - 19359, - 19360, - 19361, - 19362, - 19363, - 19364, - 19365, - 19366, - 19367, - 19368, - 19369, - 19370, - 19371, - 19372, - 19373, - 19374, - 19375, - 19376, - 19377, - 19378, - 19379, - 19380, - 19381, - 19382, - 19383, - 19384, - 19385, - 19386, - 19387, - 19388, - 19389, - 19390, - 19391, - 19392, - 19393, - 19394, - 19395, - 19396, - 19397, - 19398, - 19399, - 19400, - 19401, - 19402, - 19403, - 19404, - 19405, - 19406, - 19407, - 19408, - 19409, - 19410, - 19411, - 19412, - 19413, - 19414, - 19415, - 19416, - 19417, - 19418, - 19419, - 19420, - 19421, - 19422, - 19423, - 19424, - 19425, - 19426, - 19427, - 19428, - 19429, - 19430, - 19431, - 19432, - 19433, - 19434, - 19435, - 19436, - 19437, - 19438, - 19439, - 19440, - 19441, - 19442, - 19443, - 19444, - 19445, - 19446, - 19447, - 19448, - 19449, - 19450, - 19451, - 19452, - 19453, - 19454, - 19455, - 19456, - 19457, - 19458, - 19459, - 19460, - 19461, - 19462, - 19463, - 19464, - 19465, - 19466, - 19467, - 19468, - 19469, - 19470, - 19471, - 19472, - 19473, - 19474, - 19475, - 19476, - 19477, - 19478, - 19479, - 19480, - 19481, - 19482, - 19483, - 19484, - 19485, - 19486, - 19487, - 19488, - 19489, - 19490, - 19491, - 19492, - 19493, - 19494, - 19495, - 19496, - 19497, - 19498, - 19499, - 19500, - 19501, - 19502, - 19503, - 19504, - 19505, - 19506, - 19507, - 19508, - 19509, - 19510, - 19511, - 19512, - 19513, - 19514, - 19515, - 19516, - 19517, - 19518, - 19519, - 19520, - 19521, - 19522, - 19523, - 19524, - 19525, - 19526, - 19527, - 19528, - 19529, - 19530, - 19531, - 19532, - 19533, - 19534, - 19535, - 19536, - 19537, - 19538, - 19539, - 19540, - 19541, - 19542, - 19543, - 19544, - 19545, - 19546, - 19547, - 19548, - 19549, - 19550, - 19551, - 19552, - 19553, - 19554, - 19555, - 19556, - 19557, - 19558, - 19559, - 19560, - 19561, - 19562, - 19563, - 19564, - 19565, - 19566, - 19567, - 19568, - 19569, - 19570, - 19571, - 19572, - 19573, - 19574, - 19575, - 19576, - 19577, - 19578, - 19579, - 19580, - 19581, - 19582, - 19583, - 19584, - 19585, - 19586, - 19587, - 19588, - 19589, - 19590, - 19591, - 19592, - 19593, - 19594, - 19595, - 19596, - 19597, - 19598, - 19599, - 19600, - 19601, - 19602, - 19603, - 19604, - 19605, - 19606, - 19607, - 19608, - 19609, - 19610, - 19611, - 19612, - 19613, - 19614, - 19615, - 19616, - 19617, - 19618, - 19619, - 19620, - 19621, - 19622, - 19623, - 19624, - 19625, - 19626, - 19627, - 19628, - 19629, - 19630, - 19631, - 19632, - 19633, - 19634, - 19635, - 19636, - 19637, - 19638, - 19639, - 19640, - 19641, - 19642, - 19643, - 19644, - 19645, - 19646, - 19647, - 19648, - 19649, - 19650, - 19651, - 19652, - 19653, - 19654, - 19655, - 19656, - 19657, - 19658, - 19659, - 19660, - 19661, - 19662, - 19663, - 19664, - 19665, - 19666, - 19667, - 19668, - 19669, - 19670, - 19671, - 19672, - 19673, - 19674, - 19675, - 19676, - 19677, - 19678, - 19679, - 19680, - 19681, - 19682, - 19683, - 19684, - 19685, - 19686, - 19687, - 19688, - 19689, - 19690, - 19691, - 19692, - 19693, - 19694, - 19695, - 19696, - 19697, - 19698, - 19699, - 19700, - 19701, - 19702, - 19703, - 19704, - 19705, - 19706, - 19707, - 19708, - 19709, - 19710, - 19711, - 19712, - 19713, - 19714, - 19715, - 19716, - 19717, - 19718, - 19719, - 19720, - 19721, - 19722, - 19723, - 19724, - 19725, - 19726, - 19727, - 19728, - 19729, - 19730, - 19731, - 19732, - 19733, - 19734, - 19735, - 19736, - 19737, - 19738, - 19739, - 19740, - 19741, - 19742, - 19743, - 19744, - 19745, - 19746, - 19747, - 19748, - 19749, - 19750, - 19751, - 19752, - 19753, - 19754, - 19755, - 19756, - 19757, - 19758, - 19759, - 19760, - 19761, - 19762, - 19763, - 19764, - 19765, - 19766, - 19767, - 19768, - 19769, - 19770, - 19771, - 19772, - 19773, - 19774, - 19775, - 19776, - 19777, - 19778, - 19779, - 19780, - 19781, - 19782, - 19783, - 19784, - 19785, - 19786, - 19787, - 19788, - 19789, - 19790, - 19791, - 19792, - 19793, - 19794, - 19795, - 19796, - 19797, - 19798, - 19799, - 19800, - 19801, - 19802, - 19803, - 19804, - 19805, - 19806, - 19807, - 19808, - 19809, - 19810, - 19811, - 19812, - 19813, - 19814, - 19815, - 19816, - 19817, - 19818, - 19819, - 19820, - 19821, - 19822, - 19823, - 19824, - 19825, - 19826, - 19827, - 19828, - 19829, - 19830, - 19831, - 19832, - 19833, - 19834, - 19835, - 19836, - 19837, - 19838, - 19839, - 19840, - 19841, - 19842, - 19843, - 19844, - 19845, - 19846, - 19847, - 19848, - 19849, - 19850, - 19851, - 19852, - 19853, - 19854, - 19855, - 19856, - 19857, - 19858, - 19859, - 19860, - 19861, - 19862, - 19863, - 19864, - 19865, - 19866, - 19867, - 19868, - 19869, - 19870, - 19871, - 19872, - 19873, - 19874, - 19875, - 19876, - 19877, - 19878, - 19879, - 19880, - 19881, - 19882, - 19883, - 19884, - 19885, - 19886, - 19887, - 19888, - 19889, - 19890, - 19891, - 19892, - 19893, - 19968, - 19969, - 19970, - 19971, - 19972, - 19973, - 19974, - 19975, - 19976, - 19977, - 19978, - 19979, - 19980, - 19981, - 19982, - 19983, - 19984, - 19985, - 19986, - 19987, - 19988, - 19989, - 19990, - 19991, - 19992, - 19993, - 19994, - 19995, - 19996, - 19997, - 19998, - 19999, - 20000, - 20001, - 20002, - 20003, - 20004, - 20005, - 20006, - 20007, - 20008, - 20009, - 20010, - 20011, - 20012, - 20013, - 20014, - 20015, - 20016, - 20017, - 20018, - 20019, - 20020, - 20021, - 20022, - 20023, - 20024, - 20025, - 20026, - 20027, - 20028, - 20029, - 20030, - 20031, - 20032, - 20033, - 20034, - 20035, - 20036, - 20037, - 20038, - 20039, - 20040, - 20041, - 20042, - 20043, - 20044, - 20045, - 20046, - 20047, - 20048, - 20049, - 20050, - 20051, - 20052, - 20053, - 20054, - 20055, - 20056, - 20057, - 20058, - 20059, - 20060, - 20061, - 20062, - 20063, - 20064, - 20065, - 20066, - 20067, - 20068, - 20069, - 20070, - 20071, - 20072, - 20073, - 20074, - 20075, - 20076, - 20077, - 20078, - 20079, - 20080, - 20081, - 20082, - 20083, - 20084, - 20085, - 20086, - 20087, - 20088, - 20089, - 20090, - 20091, - 20092, - 20093, - 20094, - 20095, - 20096, - 20097, - 20098, - 20099, - 20100, - 20101, - 20102, - 20103, - 20104, - 20105, - 20106, - 20107, - 20108, - 20109, - 20110, - 20111, - 20112, - 20113, - 20114, - 20115, - 20116, - 20117, - 20118, - 20119, - 20120, - 20121, - 20122, - 20123, - 20124, - 20125, - 20126, - 20127, - 20128, - 20129, - 20130, - 20131, - 20132, - 20133, - 20134, - 20135, - 20136, - 20137, - 20138, - 20139, - 20140, - 20141, - 20142, - 20143, - 20144, - 20145, - 20146, - 20147, - 20148, - 20149, - 20150, - 20151, - 20152, - 20153, - 20154, - 20155, - 20156, - 20157, - 20158, - 20159, - 20160, - 20161, - 20162, - 20163, - 20164, - 20165, - 20166, - 20167, - 20168, - 20169, - 20170, - 20171, - 20172, - 20173, - 20174, - 20175, - 20176, - 20177, - 20178, - 20179, - 20180, - 20181, - 20182, - 20183, - 20184, - 20185, - 20186, - 20187, - 20188, - 20189, - 20190, - 20191, - 20192, - 20193, - 20194, - 20195, - 20196, - 20197, - 20198, - 20199, - 20200, - 20201, - 20202, - 20203, - 20204, - 20205, - 20206, - 20207, - 20208, - 20209, - 20210, - 20211, - 20212, - 20213, - 20214, - 20215, - 20216, - 20217, - 20218, - 20219, - 20220, - 20221, - 20222, - 20223, - 20224, - 20225, - 20226, - 20227, - 20228, - 20229, - 20230, - 20231, - 20232, - 20233, - 20234, - 20235, - 20236, - 20237, - 20238, - 20239, - 20240, - 20241, - 20242, - 20243, - 20244, - 20245, - 20246, - 20247, - 20248, - 20249, - 20250, - 20251, - 20252, - 20253, - 20254, - 20255, - 20256, - 20257, - 20258, - 20259, - 20260, - 20261, - 20262, - 20263, - 20264, - 20265, - 20266, - 20267, - 20268, - 20269, - 20270, - 20271, - 20272, - 20273, - 20274, - 20275, - 20276, - 20277, - 20278, - 20279, - 20280, - 20281, - 20282, - 20283, - 20284, - 20285, - 20286, - 20287, - 20288, - 20289, - 20290, - 20291, - 20292, - 20293, - 20294, - 20295, - 20296, - 20297, - 20298, - 20299, - 20300, - 20301, - 20302, - 20303, - 20304, - 20305, - 20306, - 20307, - 20308, - 20309, - 20310, - 20311, - 20312, - 20313, - 20314, - 20315, - 20316, - 20317, - 20318, - 20319, - 20320, - 20321, - 20322, - 20323, - 20324, - 20325, - 20326, - 20327, - 20328, - 20329, - 20330, - 20331, - 20332, - 20333, - 20334, - 20335, - 20336, - 20337, - 20338, - 20339, - 20340, - 20341, - 20342, - 20343, - 20344, - 20345, - 20346, - 20347, - 20348, - 20349, - 20350, - 20351, - 20352, - 20353, - 20354, - 20355, - 20356, - 20357, - 20358, - 20359, - 20360, - 20361, - 20362, - 20363, - 20364, - 20365, - 20366, - 20367, - 20368, - 20369, - 20370, - 20371, - 20372, - 20373, - 20374, - 20375, - 20376, - 20377, - 20378, - 20379, - 20380, - 20381, - 20382, - 20383, - 20384, - 20385, - 20386, - 20387, - 20388, - 20389, - 20390, - 20391, - 20392, - 20393, - 20394, - 20395, - 20396, - 20397, - 20398, - 20399, - 20400, - 20401, - 20402, - 20403, - 20404, - 20405, - 20406, - 20407, - 20408, - 20409, - 20410, - 20411, - 20412, - 20413, - 20414, - 20415, - 20416, - 20417, - 20418, - 20419, - 20420, - 20421, - 20422, - 20423, - 20424, - 20425, - 20426, - 20427, - 20428, - 20429, - 20430, - 20431, - 20432, - 20433, - 20434, - 20435, - 20436, - 20437, - 20438, - 20439, - 20440, - 20441, - 20442, - 20443, - 20444, - 20445, - 20446, - 20447, - 20448, - 20449, - 20450, - 20451, - 20452, - 20453, - 20454, - 20455, - 20456, - 20457, - 20458, - 20459, - 20460, - 20461, - 20462, - 20463, - 20464, - 20465, - 20466, - 20467, - 20468, - 20469, - 20470, - 20471, - 20472, - 20473, - 20474, - 20475, - 20476, - 20477, - 20478, - 20479, - 20480, - 20481, - 20482, - 20483, - 20484, - 20485, - 20486, - 20487, - 20488, - 20489, - 20490, - 20491, - 20492, - 20493, - 20494, - 20495, - 20496, - 20497, - 20498, - 20499, - 20500, - 20501, - 20502, - 20503, - 20504, - 20505, - 20506, - 20507, - 20508, - 20509, - 20510, - 20511, - 20512, - 20513, - 20514, - 20515, - 20516, - 20517, - 20518, - 20519, - 20520, - 20521, - 20522, - 20523, - 20524, - 20525, - 20526, - 20527, - 20528, - 20529, - 20530, - 20531, - 20532, - 20533, - 20534, - 20535, - 20536, - 20537, - 20538, - 20539, - 20540, - 20541, - 20542, - 20543, - 20544, - 20545, - 20546, - 20547, - 20548, - 20549, - 20550, - 20551, - 20552, - 20553, - 20554, - 20555, - 20556, - 20557, - 20558, - 20559, - 20560, - 20561, - 20562, - 20563, - 20564, - 20565, - 20566, - 20567, - 20568, - 20569, - 20570, - 20571, - 20572, - 20573, - 20574, - 20575, - 20576, - 20577, - 20578, - 20579, - 20580, - 20581, - 20582, - 20583, - 20584, - 20585, - 20586, - 20587, - 20588, - 20589, - 20590, - 20591, - 20592, - 20593, - 20594, - 20595, - 20596, - 20597, - 20598, - 20599, - 20600, - 20601, - 20602, - 20603, - 20604, - 20605, - 20606, - 20607, - 20608, - 20609, - 20610, - 20611, - 20612, - 20613, - 20614, - 20615, - 20616, - 20617, - 20618, - 20619, - 20620, - 20621, - 20622, - 20623, - 20624, - 20625, - 20626, - 20627, - 20628, - 20629, - 20630, - 20631, - 20632, - 20633, - 20634, - 20635, - 20636, - 20637, - 20638, - 20639, - 20640, - 20641, - 20642, - 20643, - 20644, - 20645, - 20646, - 20647, - 20648, - 20649, - 20650, - 20651, - 20652, - 20653, - 20654, - 20655, - 20656, - 20657, - 20658, - 20659, - 20660, - 20661, - 20662, - 20663, - 20664, - 20665, - 20666, - 20667, - 20668, - 20669, - 20670, - 20671, - 20672, - 20673, - 20674, - 20675, - 20676, - 20677, - 20678, - 20679, - 20680, - 20681, - 20682, - 20683, - 20684, - 20685, - 20686, - 20687, - 20688, - 20689, - 20690, - 20691, - 20692, - 20693, - 20694, - 20695, - 20696, - 20697, - 20698, - 20699, - 20700, - 20701, - 20702, - 20703, - 20704, - 20705, - 20706, - 20707, - 20708, - 20709, - 20710, - 20711, - 20712, - 20713, - 20714, - 20715, - 20716, - 20717, - 20718, - 20719, - 20720, - 20721, - 20722, - 20723, - 20724, - 20725, - 20726, - 20727, - 20728, - 20729, - 20730, - 20731, - 20732, - 20733, - 20734, - 20735, - 20736, - 20737, - 20738, - 20739, - 20740, - 20741, - 20742, - 20743, - 20744, - 20745, - 20746, - 20747, - 20748, - 20749, - 20750, - 20751, - 20752, - 20753, - 20754, - 20755, - 20756, - 20757, - 20758, - 20759, - 20760, - 20761, - 20762, - 20763, - 20764, - 20765, - 20766, - 20767, - 20768, - 20769, - 20770, - 20771, - 20772, - 20773, - 20774, - 20775, - 20776, - 20777, - 20778, - 20779, - 20780, - 20781, - 20782, - 20783, - 20784, - 20785, - 20786, - 20787, - 20788, - 20789, - 20790, - 20791, - 20792, - 20793, - 20794, - 20795, - 20796, - 20797, - 20798, - 20799, - 20800, - 20801, - 20802, - 20803, - 20804, - 20805, - 20806, - 20807, - 20808, - 20809, - 20810, - 20811, - 20812, - 20813, - 20814, - 20815, - 20816, - 20817, - 20818, - 20819, - 20820, - 20821, - 20822, - 20823, - 20824, - 20825, - 20826, - 20827, - 20828, - 20829, - 20830, - 20831, - 20832, - 20833, - 20834, - 20835, - 20836, - 20837, - 20838, - 20839, - 20840, - 20841, - 20842, - 20843, - 20844, - 20845, - 20846, - 20847, - 20848, - 20849, - 20850, - 20851, - 20852, - 20853, - 20854, - 20855, - 20856, - 20857, - 20858, - 20859, - 20860, - 20861, - 20862, - 20863, - 20864, - 20865, - 20866, - 20867, - 20868, - 20869, - 20870, - 20871, - 20872, - 20873, - 20874, - 20875, - 20876, - 20877, - 20878, - 20879, - 20880, - 20881, - 20882, - 20883, - 20884, - 20885, - 20886, - 20887, - 20888, - 20889, - 20890, - 20891, - 20892, - 20893, - 20894, - 20895, - 20896, - 20897, - 20898, - 20899, - 20900, - 20901, - 20902, - 20903, - 20904, - 20905, - 20906, - 20907, - 20908, - 20909, - 20910, - 20911, - 20912, - 20913, - 20914, - 20915, - 20916, - 20917, - 20918, - 20919, - 20920, - 20921, - 20922, - 20923, - 20924, - 20925, - 20926, - 20927, - 20928, - 20929, - 20930, - 20931, - 20932, - 20933, - 20934, - 20935, - 20936, - 20937, - 20938, - 20939, - 20940, - 20941, - 20942, - 20943, - 20944, - 20945, - 20946, - 20947, - 20948, - 20949, - 20950, - 20951, - 20952, - 20953, - 20954, - 20955, - 20956, - 20957, - 20958, - 20959, - 20960, - 20961, - 20962, - 20963, - 20964, - 20965, - 20966, - 20967, - 20968, - 20969, - 20970, - 20971, - 20972, - 20973, - 20974, - 20975, - 20976, - 20977, - 20978, - 20979, - 20980, - 20981, - 20982, - 20983, - 20984, - 20985, - 20986, - 20987, - 20988, - 20989, - 20990, - 20991, - 20992, - 20993, - 20994, - 20995, - 20996, - 20997, - 20998, - 20999, - 21000, - 21001, - 21002, - 21003, - 21004, - 21005, - 21006, - 21007, - 21008, - 21009, - 21010, - 21011, - 21012, - 21013, - 21014, - 21015, - 21016, - 21017, - 21018, - 21019, - 21020, - 21021, - 21022, - 21023, - 21024, - 21025, - 21026, - 21027, - 21028, - 21029, - 21030, - 21031, - 21032, - 21033, - 21034, - 21035, - 21036, - 21037, - 21038, - 21039, - 21040, - 21041, - 21042, - 21043, - 21044, - 21045, - 21046, - 21047, - 21048, - 21049, - 21050, - 21051, - 21052, - 21053, - 21054, - 21055, - 21056, - 21057, - 21058, - 21059, - 21060, - 21061, - 21062, - 21063, - 21064, - 21065, - 21066, - 21067, - 21068, - 21069, - 21070, - 21071, - 21072, - 21073, - 21074, - 21075, - 21076, - 21077, - 21078, - 21079, - 21080, - 21081, - 21082, - 21083, - 21084, - 21085, - 21086, - 21087, - 21088, - 21089, - 21090, - 21091, - 21092, - 21093, - 21094, - 21095, - 21096, - 21097, - 21098, - 21099, - 21100, - 21101, - 21102, - 21103, - 21104, - 21105, - 21106, - 21107, - 21108, - 21109, - 21110, - 21111, - 21112, - 21113, - 21114, - 21115, - 21116, - 21117, - 21118, - 21119, - 21120, - 21121, - 21122, - 21123, - 21124, - 21125, - 21126, - 21127, - 21128, - 21129, - 21130, - 21131, - 21132, - 21133, - 21134, - 21135, - 21136, - 21137, - 21138, - 21139, - 21140, - 21141, - 21142, - 21143, - 21144, - 21145, - 21146, - 21147, - 21148, - 21149, - 21150, - 21151, - 21152, - 21153, - 21154, - 21155, - 21156, - 21157, - 21158, - 21159, - 21160, - 21161, - 21162, - 21163, - 21164, - 21165, - 21166, - 21167, - 21168, - 21169, - 21170, - 21171, - 21172, - 21173, - 21174, - 21175, - 21176, - 21177, - 21178, - 21179, - 21180, - 21181, - 21182, - 21183, - 21184, - 21185, - 21186, - 21187, - 21188, - 21189, - 21190, - 21191, - 21192, - 21193, - 21194, - 21195, - 21196, - 21197, - 21198, - 21199, - 21200, - 21201, - 21202, - 21203, - 21204, - 21205, - 21206, - 21207, - 21208, - 21209, - 21210, - 21211, - 21212, - 21213, - 21214, - 21215, - 21216, - 21217, - 21218, - 21219, - 21220, - 21221, - 21222, - 21223, - 21224, - 21225, - 21226, - 21227, - 21228, - 21229, - 21230, - 21231, - 21232, - 21233, - 21234, - 21235, - 21236, - 21237, - 21238, - 21239, - 21240, - 21241, - 21242, - 21243, - 21244, - 21245, - 21246, - 21247, - 21248, - 21249, - 21250, - 21251, - 21252, - 21253, - 21254, - 21255, - 21256, - 21257, - 21258, - 21259, - 21260, - 21261, - 21262, - 21263, - 21264, - 21265, - 21266, - 21267, - 21268, - 21269, - 21270, - 21271, - 21272, - 21273, - 21274, - 21275, - 21276, - 21277, - 21278, - 21279, - 21280, - 21281, - 21282, - 21283, - 21284, - 21285, - 21286, - 21287, - 21288, - 21289, - 21290, - 21291, - 21292, - 21293, - 21294, - 21295, - 21296, - 21297, - 21298, - 21299, - 21300, - 21301, - 21302, - 21303, - 21304, - 21305, - 21306, - 21307, - 21308, - 21309, - 21310, - 21311, - 21312, - 21313, - 21314, - 21315, - 21316, - 21317, - 21318, - 21319, - 21320, - 21321, - 21322, - 21323, - 21324, - 21325, - 21326, - 21327, - 21328, - 21329, - 21330, - 21331, - 21332, - 21333, - 21334, - 21335, - 21336, - 21337, - 21338, - 21339, - 21340, - 21341, - 21342, - 21343, - 21344, - 21345, - 21346, - 21347, - 21348, - 21349, - 21350, - 21351, - 21352, - 21353, - 21354, - 21355, - 21356, - 21357, - 21358, - 21359, - 21360, - 21361, - 21362, - 21363, - 21364, - 21365, - 21366, - 21367, - 21368, - 21369, - 21370, - 21371, - 21372, - 21373, - 21374, - 21375, - 21376, - 21377, - 21378, - 21379, - 21380, - 21381, - 21382, - 21383, - 21384, - 21385, - 21386, - 21387, - 21388, - 21389, - 21390, - 21391, - 21392, - 21393, - 21394, - 21395, - 21396, - 21397, - 21398, - 21399, - 21400, - 21401, - 21402, - 21403, - 21404, - 21405, - 21406, - 21407, - 21408, - 21409, - 21410, - 21411, - 21412, - 21413, - 21414, - 21415, - 21416, - 21417, - 21418, - 21419, - 21420, - 21421, - 21422, - 21423, - 21424, - 21425, - 21426, - 21427, - 21428, - 21429, - 21430, - 21431, - 21432, - 21433, - 21434, - 21435, - 21436, - 21437, - 21438, - 21439, - 21440, - 21441, - 21442, - 21443, - 21444, - 21445, - 21446, - 21447, - 21448, - 21449, - 21450, - 21451, - 21452, - 21453, - 21454, - 21455, - 21456, - 21457, - 21458, - 21459, - 21460, - 21461, - 21462, - 21463, - 21464, - 21465, - 21466, - 21467, - 21468, - 21469, - 21470, - 21471, - 21472, - 21473, - 21474, - 21475, - 21476, - 21477, - 21478, - 21479, - 21480, - 21481, - 21482, - 21483, - 21484, - 21485, - 21486, - 21487, - 21488, - 21489, - 21490, - 21491, - 21492, - 21493, - 21494, - 21495, - 21496, - 21497, - 21498, - 21499, - 21500, - 21501, - 21502, - 21503, - 21504, - 21505, - 21506, - 21507, - 21508, - 21509, - 21510, - 21511, - 21512, - 21513, - 21514, - 21515, - 21516, - 21517, - 21518, - 21519, - 21520, - 21521, - 21522, - 21523, - 21524, - 21525, - 21526, - 21527, - 21528, - 21529, - 21530, - 21531, - 21532, - 21533, - 21534, - 21535, - 21536, - 21537, - 21538, - 21539, - 21540, - 21541, - 21542, - 21543, - 21544, - 21545, - 21546, - 21547, - 21548, - 21549, - 21550, - 21551, - 21552, - 21553, - 21554, - 21555, - 21556, - 21557, - 21558, - 21559, - 21560, - 21561, - 21562, - 21563, - 21564, - 21565, - 21566, - 21567, - 21568, - 21569, - 21570, - 21571, - 21572, - 21573, - 21574, - 21575, - 21576, - 21577, - 21578, - 21579, - 21580, - 21581, - 21582, - 21583, - 21584, - 21585, - 21586, - 21587, - 21588, - 21589, - 21590, - 21591, - 21592, - 21593, - 21594, - 21595, - 21596, - 21597, - 21598, - 21599, - 21600, - 21601, - 21602, - 21603, - 21604, - 21605, - 21606, - 21607, - 21608, - 21609, - 21610, - 21611, - 21612, - 21613, - 21614, - 21615, - 21616, - 21617, - 21618, - 21619, - 21620, - 21621, - 21622, - 21623, - 21624, - 21625, - 21626, - 21627, - 21628, - 21629, - 21630, - 21631, - 21632, - 21633, - 21634, - 21635, - 21636, - 21637, - 21638, - 21639, - 21640, - 21641, - 21642, - 21643, - 21644, - 21645, - 21646, - 21647, - 21648, - 21649, - 21650, - 21651, - 21652, - 21653, - 21654, - 21655, - 21656, - 21657, - 21658, - 21659, - 21660, - 21661, - 21662, - 21663, - 21664, - 21665, - 21666, - 21667, - 21668, - 21669, - 21670, - 21671, - 21672, - 21673, - 21674, - 21675, - 21676, - 21677, - 21678, - 21679, - 21680, - 21681, - 21682, - 21683, - 21684, - 21685, - 21686, - 21687, - 21688, - 21689, - 21690, - 21691, - 21692, - 21693, - 21694, - 21695, - 21696, - 21697, - 21698, - 21699, - 21700, - 21701, - 21702, - 21703, - 21704, - 21705, - 21706, - 21707, - 21708, - 21709, - 21710, - 21711, - 21712, - 21713, - 21714, - 21715, - 21716, - 21717, - 21718, - 21719, - 21720, - 21721, - 21722, - 21723, - 21724, - 21725, - 21726, - 21727, - 21728, - 21729, - 21730, - 21731, - 21732, - 21733, - 21734, - 21735, - 21736, - 21737, - 21738, - 21739, - 21740, - 21741, - 21742, - 21743, - 21744, - 21745, - 21746, - 21747, - 21748, - 21749, - 21750, - 21751, - 21752, - 21753, - 21754, - 21755, - 21756, - 21757, - 21758, - 21759, - 21760, - 21761, - 21762, - 21763, - 21764, - 21765, - 21766, - 21767, - 21768, - 21769, - 21770, - 21771, - 21772, - 21773, - 21774, - 21775, - 21776, - 21777, - 21778, - 21779, - 21780, - 21781, - 21782, - 21783, - 21784, - 21785, - 21786, - 21787, - 21788, - 21789, - 21790, - 21791, - 21792, - 21793, - 21794, - 21795, - 21796, - 21797, - 21798, - 21799, - 21800, - 21801, - 21802, - 21803, - 21804, - 21805, - 21806, - 21807, - 21808, - 21809, - 21810, - 21811, - 21812, - 21813, - 21814, - 21815, - 21816, - 21817, - 21818, - 21819, - 21820, - 21821, - 21822, - 21823, - 21824, - 21825, - 21826, - 21827, - 21828, - 21829, - 21830, - 21831, - 21832, - 21833, - 21834, - 21835, - 21836, - 21837, - 21838, - 21839, - 21840, - 21841, - 21842, - 21843, - 21844, - 21845, - 21846, - 21847, - 21848, - 21849, - 21850, - 21851, - 21852, - 21853, - 21854, - 21855, - 21856, - 21857, - 21858, - 21859, - 21860, - 21861, - 21862, - 21863, - 21864, - 21865, - 21866, - 21867, - 21868, - 21869, - 21870, - 21871, - 21872, - 21873, - 21874, - 21875, - 21876, - 21877, - 21878, - 21879, - 21880, - 21881, - 21882, - 21883, - 21884, - 21885, - 21886, - 21887, - 21888, - 21889, - 21890, - 21891, - 21892, - 21893, - 21894, - 21895, - 21896, - 21897, - 21898, - 21899, - 21900, - 21901, - 21902, - 21903, - 21904, - 21905, - 21906, - 21907, - 21908, - 21909, - 21910, - 21911, - 21912, - 21913, - 21914, - 21915, - 21916, - 21917, - 21918, - 21919, - 21920, - 21921, - 21922, - 21923, - 21924, - 21925, - 21926, - 21927, - 21928, - 21929, - 21930, - 21931, - 21932, - 21933, - 21934, - 21935, - 21936, - 21937, - 21938, - 21939, - 21940, - 21941, - 21942, - 21943, - 21944, - 21945, - 21946, - 21947, - 21948, - 21949, - 21950, - 21951, - 21952, - 21953, - 21954, - 21955, - 21956, - 21957, - 21958, - 21959, - 21960, - 21961, - 21962, - 21963, - 21964, - 21965, - 21966, - 21967, - 21968, - 21969, - 21970, - 21971, - 21972, - 21973, - 21974, - 21975, - 21976, - 21977, - 21978, - 21979, - 21980, - 21981, - 21982, - 21983, - 21984, - 21985, - 21986, - 21987, - 21988, - 21989, - 21990, - 21991, - 21992, - 21993, - 21994, - 21995, - 21996, - 21997, - 21998, - 21999, - 22000, - 22001, - 22002, - 22003, - 22004, - 22005, - 22006, - 22007, - 22008, - 22009, - 22010, - 22011, - 22012, - 22013, - 22014, - 22015, - 22016, - 22017, - 22018, - 22019, - 22020, - 22021, - 22022, - 22023, - 22024, - 22025, - 22026, - 22027, - 22028, - 22029, - 22030, - 22031, - 22032, - 22033, - 22034, - 22035, - 22036, - 22037, - 22038, - 22039, - 22040, - 22041, - 22042, - 22043, - 22044, - 22045, - 22046, - 22047, - 22048, - 22049, - 22050, - 22051, - 22052, - 22053, - 22054, - 22055, - 22056, - 22057, - 22058, - 22059, - 22060, - 22061, - 22062, - 22063, - 22064, - 22065, - 22066, - 22067, - 22068, - 22069, - 22070, - 22071, - 22072, - 22073, - 22074, - 22075, - 22076, - 22077, - 22078, - 22079, - 22080, - 22081, - 22082, - 22083, - 22084, - 22085, - 22086, - 22087, - 22088, - 22089, - 22090, - 22091, - 22092, - 22093, - 22094, - 22095, - 22096, - 22097, - 22098, - 22099, - 22100, - 22101, - 22102, - 22103, - 22104, - 22105, - 22106, - 22107, - 22108, - 22109, - 22110, - 22111, - 22112, - 22113, - 22114, - 22115, - 22116, - 22117, - 22118, - 22119, - 22120, - 22121, - 22122, - 22123, - 22124, - 22125, - 22126, - 22127, - 22128, - 22129, - 22130, - 22131, - 22132, - 22133, - 22134, - 22135, - 22136, - 22137, - 22138, - 22139, - 22140, - 22141, - 22142, - 22143, - 22144, - 22145, - 22146, - 22147, - 22148, - 22149, - 22150, - 22151, - 22152, - 22153, - 22154, - 22155, - 22156, - 22157, - 22158, - 22159, - 22160, - 22161, - 22162, - 22163, - 22164, - 22165, - 22166, - 22167, - 22168, - 22169, - 22170, - 22171, - 22172, - 22173, - 22174, - 22175, - 22176, - 22177, - 22178, - 22179, - 22180, - 22181, - 22182, - 22183, - 22184, - 22185, - 22186, - 22187, - 22188, - 22189, - 22190, - 22191, - 22192, - 22193, - 22194, - 22195, - 22196, - 22197, - 22198, - 22199, - 22200, - 22201, - 22202, - 22203, - 22204, - 22205, - 22206, - 22207, - 22208, - 22209, - 22210, - 22211, - 22212, - 22213, - 22214, - 22215, - 22216, - 22217, - 22218, - 22219, - 22220, - 22221, - 22222, - 22223, - 22224, - 22225, - 22226, - 22227, - 22228, - 22229, - 22230, - 22231, - 22232, - 22233, - 22234, - 22235, - 22236, - 22237, - 22238, - 22239, - 22240, - 22241, - 22242, - 22243, - 22244, - 22245, - 22246, - 22247, - 22248, - 22249, - 22250, - 22251, - 22252, - 22253, - 22254, - 22255, - 22256, - 22257, - 22258, - 22259, - 22260, - 22261, - 22262, - 22263, - 22264, - 22265, - 22266, - 22267, - 22268, - 22269, - 22270, - 22271, - 22272, - 22273, - 22274, - 22275, - 22276, - 22277, - 22278, - 22279, - 22280, - 22281, - 22282, - 22283, - 22284, - 22285, - 22286, - 22287, - 22288, - 22289, - 22290, - 22291, - 22292, - 22293, - 22294, - 22295, - 22296, - 22297, - 22298, - 22299, - 22300, - 22301, - 22302, - 22303, - 22304, - 22305, - 22306, - 22307, - 22308, - 22309, - 22310, - 22311, - 22312, - 22313, - 22314, - 22315, - 22316, - 22317, - 22318, - 22319, - 22320, - 22321, - 22322, - 22323, - 22324, - 22325, - 22326, - 22327, - 22328, - 22329, - 22330, - 22331, - 22332, - 22333, - 22334, - 22335, - 22336, - 22337, - 22338, - 22339, - 22340, - 22341, - 22342, - 22343, - 22344, - 22345, - 22346, - 22347, - 22348, - 22349, - 22350, - 22351, - 22352, - 22353, - 22354, - 22355, - 22356, - 22357, - 22358, - 22359, - 22360, - 22361, - 22362, - 22363, - 22364, - 22365, - 22366, - 22367, - 22368, - 22369, - 22370, - 22371, - 22372, - 22373, - 22374, - 22375, - 22376, - 22377, - 22378, - 22379, - 22380, - 22381, - 22382, - 22383, - 22384, - 22385, - 22386, - 22387, - 22388, - 22389, - 22390, - 22391, - 22392, - 22393, - 22394, - 22395, - 22396, - 22397, - 22398, - 22399, - 22400, - 22401, - 22402, - 22403, - 22404, - 22405, - 22406, - 22407, - 22408, - 22409, - 22410, - 22411, - 22412, - 22413, - 22414, - 22415, - 22416, - 22417, - 22418, - 22419, - 22420, - 22421, - 22422, - 22423, - 22424, - 22425, - 22426, - 22427, - 22428, - 22429, - 22430, - 22431, - 22432, - 22433, - 22434, - 22435, - 22436, - 22437, - 22438, - 22439, - 22440, - 22441, - 22442, - 22443, - 22444, - 22445, - 22446, - 22447, - 22448, - 22449, - 22450, - 22451, - 22452, - 22453, - 22454, - 22455, - 22456, - 22457, - 22458, - 22459, - 22460, - 22461, - 22462, - 22463, - 22464, - 22465, - 22466, - 22467, - 22468, - 22469, - 22470, - 22471, - 22472, - 22473, - 22474, - 22475, - 22476, - 22477, - 22478, - 22479, - 22480, - 22481, - 22482, - 22483, - 22484, - 22485, - 22486, - 22487, - 22488, - 22489, - 22490, - 22491, - 22492, - 22493, - 22494, - 22495, - 22496, - 22497, - 22498, - 22499, - 22500, - 22501, - 22502, - 22503, - 22504, - 22505, - 22506, - 22507, - 22508, - 22509, - 22510, - 22511, - 22512, - 22513, - 22514, - 22515, - 22516, - 22517, - 22518, - 22519, - 22520, - 22521, - 22522, - 22523, - 22524, - 22525, - 22526, - 22527, - 22528, - 22529, - 22530, - 22531, - 22532, - 22533, - 22534, - 22535, - 22536, - 22537, - 22538, - 22539, - 22540, - 22541, - 22542, - 22543, - 22544, - 22545, - 22546, - 22547, - 22548, - 22549, - 22550, - 22551, - 22552, - 22553, - 22554, - 22555, - 22556, - 22557, - 22558, - 22559, - 22560, - 22561, - 22562, - 22563, - 22564, - 22565, - 22566, - 22567, - 22568, - 22569, - 22570, - 22571, - 22572, - 22573, - 22574, - 22575, - 22576, - 22577, - 22578, - 22579, - 22580, - 22581, - 22582, - 22583, - 22584, - 22585, - 22586, - 22587, - 22588, - 22589, - 22590, - 22591, - 22592, - 22593, - 22594, - 22595, - 22596, - 22597, - 22598, - 22599, - 22600, - 22601, - 22602, - 22603, - 22604, - 22605, - 22606, - 22607, - 22608, - 22609, - 22610, - 22611, - 22612, - 22613, - 22614, - 22615, - 22616, - 22617, - 22618, - 22619, - 22620, - 22621, - 22622, - 22623, - 22624, - 22625, - 22626, - 22627, - 22628, - 22629, - 22630, - 22631, - 22632, - 22633, - 22634, - 22635, - 22636, - 22637, - 22638, - 22639, - 22640, - 22641, - 22642, - 22643, - 22644, - 22645, - 22646, - 22647, - 22648, - 22649, - 22650, - 22651, - 22652, - 22653, - 22654, - 22655, - 22656, - 22657, - 22658, - 22659, - 22660, - 22661, - 22662, - 22663, - 22664, - 22665, - 22666, - 22667, - 22668, - 22669, - 22670, - 22671, - 22672, - 22673, - 22674, - 22675, - 22676, - 22677, - 22678, - 22679, - 22680, - 22681, - 22682, - 22683, - 22684, - 22685, - 22686, - 22687, - 22688, - 22689, - 22690, - 22691, - 22692, - 22693, - 22694, - 22695, - 22696, - 22697, - 22698, - 22699, - 22700, - 22701, - 22702, - 22703, - 22704, - 22705, - 22706, - 22707, - 22708, - 22709, - 22710, - 22711, - 22712, - 22713, - 22714, - 22715, - 22716, - 22717, - 22718, - 22719, - 22720, - 22721, - 22722, - 22723, - 22724, - 22725, - 22726, - 22727, - 22728, - 22729, - 22730, - 22731, - 22732, - 22733, - 22734, - 22735, - 22736, - 22737, - 22738, - 22739, - 22740, - 22741, - 22742, - 22743, - 22744, - 22745, - 22746, - 22747, - 22748, - 22749, - 22750, - 22751, - 22752, - 22753, - 22754, - 22755, - 22756, - 22757, - 22758, - 22759, - 22760, - 22761, - 22762, - 22763, - 22764, - 22765, - 22766, - 22767, - 22768, - 22769, - 22770, - 22771, - 22772, - 22773, - 22774, - 22775, - 22776, - 22777, - 22778, - 22779, - 22780, - 22781, - 22782, - 22783, - 22784, - 22785, - 22786, - 22787, - 22788, - 22789, - 22790, - 22791, - 22792, - 22793, - 22794, - 22795, - 22796, - 22797, - 22798, - 22799, - 22800, - 22801, - 22802, - 22803, - 22804, - 22805, - 22806, - 22807, - 22808, - 22809, - 22810, - 22811, - 22812, - 22813, - 22814, - 22815, - 22816, - 22817, - 22818, - 22819, - 22820, - 22821, - 22822, - 22823, - 22824, - 22825, - 22826, - 22827, - 22828, - 22829, - 22830, - 22831, - 22832, - 22833, - 22834, - 22835, - 22836, - 22837, - 22838, - 22839, - 22840, - 22841, - 22842, - 22843, - 22844, - 22845, - 22846, - 22847, - 22848, - 22849, - 22850, - 22851, - 22852, - 22853, - 22854, - 22855, - 22856, - 22857, - 22858, - 22859, - 22860, - 22861, - 22862, - 22863, - 22864, - 22865, - 22866, - 22867, - 22868, - 22869, - 22870, - 22871, - 22872, - 22873, - 22874, - 22875, - 22876, - 22877, - 22878, - 22879, - 22880, - 22881, - 22882, - 22883, - 22884, - 22885, - 22886, - 22887, - 22888, - 22889, - 22890, - 22891, - 22892, - 22893, - 22894, - 22895, - 22896, - 22897, - 22898, - 22899, - 22900, - 22901, - 22902, - 22903, - 22904, - 22905, - 22906, - 22907, - 22908, - 22909, - 22910, - 22911, - 22912, - 22913, - 22914, - 22915, - 22916, - 22917, - 22918, - 22919, - 22920, - 22921, - 22922, - 22923, - 22924, - 22925, - 22926, - 22927, - 22928, - 22929, - 22930, - 22931, - 22932, - 22933, - 22934, - 22935, - 22936, - 22937, - 22938, - 22939, - 22940, - 22941, - 22942, - 22943, - 22944, - 22945, - 22946, - 22947, - 22948, - 22949, - 22950, - 22951, - 22952, - 22953, - 22954, - 22955, - 22956, - 22957, - 22958, - 22959, - 22960, - 22961, - 22962, - 22963, - 22964, - 22965, - 22966, - 22967, - 22968, - 22969, - 22970, - 22971, - 22972, - 22973, - 22974, - 22975, - 22976, - 22977, - 22978, - 22979, - 22980, - 22981, - 22982, - 22983, - 22984, - 22985, - 22986, - 22987, - 22988, - 22989, - 22990, - 22991, - 22992, - 22993, - 22994, - 22995, - 22996, - 22997, - 22998, - 22999, - 23000, - 23001, - 23002, - 23003, - 23004, - 23005, - 23006, - 23007, - 23008, - 23009, - 23010, - 23011, - 23012, - 23013, - 23014, - 23015, - 23016, - 23017, - 23018, - 23019, - 23020, - 23021, - 23022, - 23023, - 23024, - 23025, - 23026, - 23027, - 23028, - 23029, - 23030, - 23031, - 23032, - 23033, - 23034, - 23035, - 23036, - 23037, - 23038, - 23039, - 23040, - 23041, - 23042, - 23043, - 23044, - 23045, - 23046, - 23047, - 23048, - 23049, - 23050, - 23051, - 23052, - 23053, - 23054, - 23055, - 23056, - 23057, - 23058, - 23059, - 23060, - 23061, - 23062, - 23063, - 23064, - 23065, - 23066, - 23067, - 23068, - 23069, - 23070, - 23071, - 23072, - 23073, - 23074, - 23075, - 23076, - 23077, - 23078, - 23079, - 23080, - 23081, - 23082, - 23083, - 23084, - 23085, - 23086, - 23087, - 23088, - 23089, - 23090, - 23091, - 23092, - 23093, - 23094, - 23095, - 23096, - 23097, - 23098, - 23099, - 23100, - 23101, - 23102, - 23103, - 23104, - 23105, - 23106, - 23107, - 23108, - 23109, - 23110, - 23111, - 23112, - 23113, - 23114, - 23115, - 23116, - 23117, - 23118, - 23119, - 23120, - 23121, - 23122, - 23123, - 23124, - 23125, - 23126, - 23127, - 23128, - 23129, - 23130, - 23131, - 23132, - 23133, - 23134, - 23135, - 23136, - 23137, - 23138, - 23139, - 23140, - 23141, - 23142, - 23143, - 23144, - 23145, - 23146, - 23147, - 23148, - 23149, - 23150, - 23151, - 23152, - 23153, - 23154, - 23155, - 23156, - 23157, - 23158, - 23159, - 23160, - 23161, - 23162, - 23163, - 23164, - 23165, - 23166, - 23167, - 23168, - 23169, - 23170, - 23171, - 23172, - 23173, - 23174, - 23175, - 23176, - 23177, - 23178, - 23179, - 23180, - 23181, - 23182, - 23183, - 23184, - 23185, - 23186, - 23187, - 23188, - 23189, - 23190, - 23191, - 23192, - 23193, - 23194, - 23195, - 23196, - 23197, - 23198, - 23199, - 23200, - 23201, - 23202, - 23203, - 23204, - 23205, - 23206, - 23207, - 23208, - 23209, - 23210, - 23211, - 23212, - 23213, - 23214, - 23215, - 23216, - 23217, - 23218, - 23219, - 23220, - 23221, - 23222, - 23223, - 23224, - 23225, - 23226, - 23227, - 23228, - 23229, - 23230, - 23231, - 23232, - 23233, - 23234, - 23235, - 23236, - 23237, - 23238, - 23239, - 23240, - 23241, - 23242, - 23243, - 23244, - 23245, - 23246, - 23247, - 23248, - 23249, - 23250, - 23251, - 23252, - 23253, - 23254, - 23255, - 23256, - 23257, - 23258, - 23259, - 23260, - 23261, - 23262, - 23263, - 23264, - 23265, - 23266, - 23267, - 23268, - 23269, - 23270, - 23271, - 23272, - 23273, - 23274, - 23275, - 23276, - 23277, - 23278, - 23279, - 23280, - 23281, - 23282, - 23283, - 23284, - 23285, - 23286, - 23287, - 23288, - 23289, - 23290, - 23291, - 23292, - 23293, - 23294, - 23295, - 23296, - 23297, - 23298, - 23299, - 23300, - 23301, - 23302, - 23303, - 23304, - 23305, - 23306, - 23307, - 23308, - 23309, - 23310, - 23311, - 23312, - 23313, - 23314, - 23315, - 23316, - 23317, - 23318, - 23319, - 23320, - 23321, - 23322, - 23323, - 23324, - 23325, - 23326, - 23327, - 23328, - 23329, - 23330, - 23331, - 23332, - 23333, - 23334, - 23335, - 23336, - 23337, - 23338, - 23339, - 23340, - 23341, - 23342, - 23343, - 23344, - 23345, - 23346, - 23347, - 23348, - 23349, - 23350, - 23351, - 23352, - 23353, - 23354, - 23355, - 23356, - 23357, - 23358, - 23359, - 23360, - 23361, - 23362, - 23363, - 23364, - 23365, - 23366, - 23367, - 23368, - 23369, - 23370, - 23371, - 23372, - 23373, - 23374, - 23375, - 23376, - 23377, - 23378, - 23379, - 23380, - 23381, - 23382, - 23383, - 23384, - 23385, - 23386, - 23387, - 23388, - 23389, - 23390, - 23391, - 23392, - 23393, - 23394, - 23395, - 23396, - 23397, - 23398, - 23399, - 23400, - 23401, - 23402, - 23403, - 23404, - 23405, - 23406, - 23407, - 23408, - 23409, - 23410, - 23411, - 23412, - 23413, - 23414, - 23415, - 23416, - 23417, - 23418, - 23419, - 23420, - 23421, - 23422, - 23423, - 23424, - 23425, - 23426, - 23427, - 23428, - 23429, - 23430, - 23431, - 23432, - 23433, - 23434, - 23435, - 23436, - 23437, - 23438, - 23439, - 23440, - 23441, - 23442, - 23443, - 23444, - 23445, - 23446, - 23447, - 23448, - 23449, - 23450, - 23451, - 23452, - 23453, - 23454, - 23455, - 23456, - 23457, - 23458, - 23459, - 23460, - 23461, - 23462, - 23463, - 23464, - 23465, - 23466, - 23467, - 23468, - 23469, - 23470, - 23471, - 23472, - 23473, - 23474, - 23475, - 23476, - 23477, - 23478, - 23479, - 23480, - 23481, - 23482, - 23483, - 23484, - 23485, - 23486, - 23487, - 23488, - 23489, - 23490, - 23491, - 23492, - 23493, - 23494, - 23495, - 23496, - 23497, - 23498, - 23499, - 23500, - 23501, - 23502, - 23503, - 23504, - 23505, - 23506, - 23507, - 23508, - 23509, - 23510, - 23511, - 23512, - 23513, - 23514, - 23515, - 23516, - 23517, - 23518, - 23519, - 23520, - 23521, - 23522, - 23523, - 23524, - 23525, - 23526, - 23527, - 23528, - 23529, - 23530, - 23531, - 23532, - 23533, - 23534, - 23535, - 23536, - 23537, - 23538, - 23539, - 23540, - 23541, - 23542, - 23543, - 23544, - 23545, - 23546, - 23547, - 23548, - 23549, - 23550, - 23551, - 23552, - 23553, - 23554, - 23555, - 23556, - 23557, - 23558, - 23559, - 23560, - 23561, - 23562, - 23563, - 23564, - 23565, - 23566, - 23567, - 23568, - 23569, - 23570, - 23571, - 23572, - 23573, - 23574, - 23575, - 23576, - 23577, - 23578, - 23579, - 23580, - 23581, - 23582, - 23583, - 23584, - 23585, - 23586, - 23587, - 23588, - 23589, - 23590, - 23591, - 23592, - 23593, - 23594, - 23595, - 23596, - 23597, - 23598, - 23599, - 23600, - 23601, - 23602, - 23603, - 23604, - 23605, - 23606, - 23607, - 23608, - 23609, - 23610, - 23611, - 23612, - 23613, - 23614, - 23615, - 23616, - 23617, - 23618, - 23619, - 23620, - 23621, - 23622, - 23623, - 23624, - 23625, - 23626, - 23627, - 23628, - 23629, - 23630, - 23631, - 23632, - 23633, - 23634, - 23635, - 23636, - 23637, - 23638, - 23639, - 23640, - 23641, - 23642, - 23643, - 23644, - 23645, - 23646, - 23647, - 23648, - 23649, - 23650, - 23651, - 23652, - 23653, - 23654, - 23655, - 23656, - 23657, - 23658, - 23659, - 23660, - 23661, - 23662, - 23663, - 23664, - 23665, - 23666, - 23667, - 23668, - 23669, - 23670, - 23671, - 23672, - 23673, - 23674, - 23675, - 23676, - 23677, - 23678, - 23679, - 23680, - 23681, - 23682, - 23683, - 23684, - 23685, - 23686, - 23687, - 23688, - 23689, - 23690, - 23691, - 23692, - 23693, - 23694, - 23695, - 23696, - 23697, - 23698, - 23699, - 23700, - 23701, - 23702, - 23703, - 23704, - 23705, - 23706, - 23707, - 23708, - 23709, - 23710, - 23711, - 23712, - 23713, - 23714, - 23715, - 23716, - 23717, - 23718, - 23719, - 23720, - 23721, - 23722, - 23723, - 23724, - 23725, - 23726, - 23727, - 23728, - 23729, - 23730, - 23731, - 23732, - 23733, - 23734, - 23735, - 23736, - 23737, - 23738, - 23739, - 23740, - 23741, - 23742, - 23743, - 23744, - 23745, - 23746, - 23747, - 23748, - 23749, - 23750, - 23751, - 23752, - 23753, - 23754, - 23755, - 23756, - 23757, - 23758, - 23759, - 23760, - 23761, - 23762, - 23763, - 23764, - 23765, - 23766, - 23767, - 23768, - 23769, - 23770, - 23771, - 23772, - 23773, - 23774, - 23775, - 23776, - 23777, - 23778, - 23779, - 23780, - 23781, - 23782, - 23783, - 23784, - 23785, - 23786, - 23787, - 23788, - 23789, - 23790, - 23791, - 23792, - 23793, - 23794, - 23795, - 23796, - 23797, - 23798, - 23799, - 23800, - 23801, - 23802, - 23803, - 23804, - 23805, - 23806, - 23807, - 23808, - 23809, - 23810, - 23811, - 23812, - 23813, - 23814, - 23815, - 23816, - 23817, - 23818, - 23819, - 23820, - 23821, - 23822, - 23823, - 23824, - 23825, - 23826, - 23827, - 23828, - 23829, - 23830, - 23831, - 23832, - 23833, - 23834, - 23835, - 23836, - 23837, - 23838, - 23839, - 23840, - 23841, - 23842, - 23843, - 23844, - 23845, - 23846, - 23847, - 23848, - 23849, - 23850, - 23851, - 23852, - 23853, - 23854, - 23855, - 23856, - 23857, - 23858, - 23859, - 23860, - 23861, - 23862, - 23863, - 23864, - 23865, - 23866, - 23867, - 23868, - 23869, - 23870, - 23871, - 23872, - 23873, - 23874, - 23875, - 23876, - 23877, - 23878, - 23879, - 23880, - 23881, - 23882, - 23883, - 23884, - 23885, - 23886, - 23887, - 23888, - 23889, - 23890, - 23891, - 23892, - 23893, - 23894, - 23895, - 23896, - 23897, - 23898, - 23899, - 23900, - 23901, - 23902, - 23903, - 23904, - 23905, - 23906, - 23907, - 23908, - 23909, - 23910, - 23911, - 23912, - 23913, - 23914, - 23915, - 23916, - 23917, - 23918, - 23919, - 23920, - 23921, - 23922, - 23923, - 23924, - 23925, - 23926, - 23927, - 23928, - 23929, - 23930, - 23931, - 23932, - 23933, - 23934, - 23935, - 23936, - 23937, - 23938, - 23939, - 23940, - 23941, - 23942, - 23943, - 23944, - 23945, - 23946, - 23947, - 23948, - 23949, - 23950, - 23951, - 23952, - 23953, - 23954, - 23955, - 23956, - 23957, - 23958, - 23959, - 23960, - 23961, - 23962, - 23963, - 23964, - 23965, - 23966, - 23967, - 23968, - 23969, - 23970, - 23971, - 23972, - 23973, - 23974, - 23975, - 23976, - 23977, - 23978, - 23979, - 23980, - 23981, - 23982, - 23983, - 23984, - 23985, - 23986, - 23987, - 23988, - 23989, - 23990, - 23991, - 23992, - 23993, - 23994, - 23995, - 23996, - 23997, - 23998, - 23999, - 24000, - 24001, - 24002, - 24003, - 24004, - 24005, - 24006, - 24007, - 24008, - 24009, - 24010, - 24011, - 24012, - 24013, - 24014, - 24015, - 24016, - 24017, - 24018, - 24019, - 24020, - 24021, - 24022, - 24023, - 24024, - 24025, - 24026, - 24027, - 24028, - 24029, - 24030, - 24031, - 24032, - 24033, - 24034, - 24035, - 24036, - 24037, - 24038, - 24039, - 24040, - 24041, - 24042, - 24043, - 24044, - 24045, - 24046, - 24047, - 24048, - 24049, - 24050, - 24051, - 24052, - 24053, - 24054, - 24055, - 24056, - 24057, - 24058, - 24059, - 24060, - 24061, - 24062, - 24063, - 24064, - 24065, - 24066, - 24067, - 24068, - 24069, - 24070, - 24071, - 24072, - 24073, - 24074, - 24075, - 24076, - 24077, - 24078, - 24079, - 24080, - 24081, - 24082, - 24083, - 24084, - 24085, - 24086, - 24087, - 24088, - 24089, - 24090, - 24091, - 24092, - 24093, - 24094, - 24095, - 24096, - 24097, - 24098, - 24099, - 24100, - 24101, - 24102, - 24103, - 24104, - 24105, - 24106, - 24107, - 24108, - 24109, - 24110, - 24111, - 24112, - 24113, - 24114, - 24115, - 24116, - 24117, - 24118, - 24119, - 24120, - 24121, - 24122, - 24123, - 24124, - 24125, - 24126, - 24127, - 24128, - 24129, - 24130, - 24131, - 24132, - 24133, - 24134, - 24135, - 24136, - 24137, - 24138, - 24139, - 24140, - 24141, - 24142, - 24143, - 24144, - 24145, - 24146, - 24147, - 24148, - 24149, - 24150, - 24151, - 24152, - 24153, - 24154, - 24155, - 24156, - 24157, - 24158, - 24159, - 24160, - 24161, - 24162, - 24163, - 24164, - 24165, - 24166, - 24167, - 24168, - 24169, - 24170, - 24171, - 24172, - 24173, - 24174, - 24175, - 24176, - 24177, - 24178, - 24179, - 24180, - 24181, - 24182, - 24183, - 24184, - 24185, - 24186, - 24187, - 24188, - 24189, - 24190, - 24191, - 24192, - 24193, - 24194, - 24195, - 24196, - 24197, - 24198, - 24199, - 24200, - 24201, - 24202, - 24203, - 24204, - 24205, - 24206, - 24207, - 24208, - 24209, - 24210, - 24211, - 24212, - 24213, - 24214, - 24215, - 24216, - 24217, - 24218, - 24219, - 24220, - 24221, - 24222, - 24223, - 24224, - 24225, - 24226, - 24227, - 24228, - 24229, - 24230, - 24231, - 24232, - 24233, - 24234, - 24235, - 24236, - 24237, - 24238, - 24239, - 24240, - 24241, - 24242, - 24243, - 24244, - 24245, - 24246, - 24247, - 24248, - 24249, - 24250, - 24251, - 24252, - 24253, - 24254, - 24255, - 24256, - 24257, - 24258, - 24259, - 24260, - 24261, - 24262, - 24263, - 24264, - 24265, - 24266, - 24267, - 24268, - 24269, - 24270, - 24271, - 24272, - 24273, - 24274, - 24275, - 24276, - 24277, - 24278, - 24279, - 24280, - 24281, - 24282, - 24283, - 24284, - 24285, - 24286, - 24287, - 24288, - 24289, - 24290, - 24291, - 24292, - 24293, - 24294, - 24295, - 24296, - 24297, - 24298, - 24299, - 24300, - 24301, - 24302, - 24303, - 24304, - 24305, - 24306, - 24307, - 24308, - 24309, - 24310, - 24311, - 24312, - 24313, - 24314, - 24315, - 24316, - 24317, - 24318, - 24319, - 24320, - 24321, - 24322, - 24323, - 24324, - 24325, - 24326, - 24327, - 24328, - 24329, - 24330, - 24331, - 24332, - 24333, - 24334, - 24335, - 24336, - 24337, - 24338, - 24339, - 24340, - 24341, - 24342, - 24343, - 24344, - 24345, - 24346, - 24347, - 24348, - 24349, - 24350, - 24351, - 24352, - 24353, - 24354, - 24355, - 24356, - 24357, - 24358, - 24359, - 24360, - 24361, - 24362, - 24363, - 24364, - 24365, - 24366, - 24367, - 24368, - 24369, - 24370, - 24371, - 24372, - 24373, - 24374, - 24375, - 24376, - 24377, - 24378, - 24379, - 24380, - 24381, - 24382, - 24383, - 24384, - 24385, - 24386, - 24387, - 24388, - 24389, - 24390, - 24391, - 24392, - 24393, - 24394, - 24395, - 24396, - 24397, - 24398, - 24399, - 24400, - 24401, - 24402, - 24403, - 24404, - 24405, - 24406, - 24407, - 24408, - 24409, - 24410, - 24411, - 24412, - 24413, - 24414, - 24415, - 24416, - 24417, - 24418, - 24419, - 24420, - 24421, - 24422, - 24423, - 24424, - 24425, - 24426, - 24427, - 24428, - 24429, - 24430, - 24431, - 24432, - 24433, - 24434, - 24435, - 24436, - 24437, - 24438, - 24439, - 24440, - 24441, - 24442, - 24443, - 24444, - 24445, - 24446, - 24447, - 24448, - 24449, - 24450, - 24451, - 24452, - 24453, - 24454, - 24455, - 24456, - 24457, - 24458, - 24459, - 24460, - 24461, - 24462, - 24463, - 24464, - 24465, - 24466, - 24467, - 24468, - 24469, - 24470, - 24471, - 24472, - 24473, - 24474, - 24475, - 24476, - 24477, - 24478, - 24479, - 24480, - 24481, - 24482, - 24483, - 24484, - 24485, - 24486, - 24487, - 24488, - 24489, - 24490, - 24491, - 24492, - 24493, - 24494, - 24495, - 24496, - 24497, - 24498, - 24499, - 24500, - 24501, - 24502, - 24503, - 24504, - 24505, - 24506, - 24507, - 24508, - 24509, - 24510, - 24511, - 24512, - 24513, - 24514, - 24515, - 24516, - 24517, - 24518, - 24519, - 24520, - 24521, - 24522, - 24523, - 24524, - 24525, - 24526, - 24527, - 24528, - 24529, - 24530, - 24531, - 24532, - 24533, - 24534, - 24535, - 24536, - 24537, - 24538, - 24539, - 24540, - 24541, - 24542, - 24543, - 24544, - 24545, - 24546, - 24547, - 24548, - 24549, - 24550, - 24551, - 24552, - 24553, - 24554, - 24555, - 24556, - 24557, - 24558, - 24559, - 24560, - 24561, - 24562, - 24563, - 24564, - 24565, - 24566, - 24567, - 24568, - 24569, - 24570, - 24571, - 24572, - 24573, - 24574, - 24575, - 24576, - 24577, - 24578, - 24579, - 24580, - 24581, - 24582, - 24583, - 24584, - 24585, - 24586, - 24587, - 24588, - 24589, - 24590, - 24591, - 24592, - 24593, - 24594, - 24595, - 24596, - 24597, - 24598, - 24599, - 24600, - 24601, - 24602, - 24603, - 24604, - 24605, - 24606, - 24607, - 24608, - 24609, - 24610, - 24611, - 24612, - 24613, - 24614, - 24615, - 24616, - 24617, - 24618, - 24619, - 24620, - 24621, - 24622, - 24623, - 24624, - 24625, - 24626, - 24627, - 24628, - 24629, - 24630, - 24631, - 24632, - 24633, - 24634, - 24635, - 24636, - 24637, - 24638, - 24639, - 24640, - 24641, - 24642, - 24643, - 24644, - 24645, - 24646, - 24647, - 24648, - 24649, - 24650, - 24651, - 24652, - 24653, - 24654, - 24655, - 24656, - 24657, - 24658, - 24659, - 24660, - 24661, - 24662, - 24663, - 24664, - 24665, - 24666, - 24667, - 24668, - 24669, - 24670, - 24671, - 24672, - 24673, - 24674, - 24675, - 24676, - 24677, - 24678, - 24679, - 24680, - 24681, - 24682, - 24683, - 24684, - 24685, - 24686, - 24687, - 24688, - 24689, - 24690, - 24691, - 24692, - 24693, - 24694, - 24695, - 24696, - 24697, - 24698, - 24699, - 24700, - 24701, - 24702, - 24703, - 24704, - 24705, - 24706, - 24707, - 24708, - 24709, - 24710, - 24711, - 24712, - 24713, - 24714, - 24715, - 24716, - 24717, - 24718, - 24719, - 24720, - 24721, - 24722, - 24723, - 24724, - 24725, - 24726, - 24727, - 24728, - 24729, - 24730, - 24731, - 24732, - 24733, - 24734, - 24735, - 24736, - 24737, - 24738, - 24739, - 24740, - 24741, - 24742, - 24743, - 24744, - 24745, - 24746, - 24747, - 24748, - 24749, - 24750, - 24751, - 24752, - 24753, - 24754, - 24755, - 24756, - 24757, - 24758, - 24759, - 24760, - 24761, - 24762, - 24763, - 24764, - 24765, - 24766, - 24767, - 24768, - 24769, - 24770, - 24771, - 24772, - 24773, - 24774, - 24775, - 24776, - 24777, - 24778, - 24779, - 24780, - 24781, - 24782, - 24783, - 24784, - 24785, - 24786, - 24787, - 24788, - 24789, - 24790, - 24791, - 24792, - 24793, - 24794, - 24795, - 24796, - 24797, - 24798, - 24799, - 24800, - 24801, - 24802, - 24803, - 24804, - 24805, - 24806, - 24807, - 24808, - 24809, - 24810, - 24811, - 24812, - 24813, - 24814, - 24815, - 24816, - 24817, - 24818, - 24819, - 24820, - 24821, - 24822, - 24823, - 24824, - 24825, - 24826, - 24827, - 24828, - 24829, - 24830, - 24831, - 24832, - 24833, - 24834, - 24835, - 24836, - 24837, - 24838, - 24839, - 24840, - 24841, - 24842, - 24843, - 24844, - 24845, - 24846, - 24847, - 24848, - 24849, - 24850, - 24851, - 24852, - 24853, - 24854, - 24855, - 24856, - 24857, - 24858, - 24859, - 24860, - 24861, - 24862, - 24863, - 24864, - 24865, - 24866, - 24867, - 24868, - 24869, - 24870, - 24871, - 24872, - 24873, - 24874, - 24875, - 24876, - 24877, - 24878, - 24879, - 24880, - 24881, - 24882, - 24883, - 24884, - 24885, - 24886, - 24887, - 24888, - 24889, - 24890, - 24891, - 24892, - 24893, - 24894, - 24895, - 24896, - 24897, - 24898, - 24899, - 24900, - 24901, - 24902, - 24903, - 24904, - 24905, - 24906, - 24907, - 24908, - 24909, - 24910, - 24911, - 24912, - 24913, - 24914, - 24915, - 24916, - 24917, - 24918, - 24919, - 24920, - 24921, - 24922, - 24923, - 24924, - 24925, - 24926, - 24927, - 24928, - 24929, - 24930, - 24931, - 24932, - 24933, - 24934, - 24935, - 24936, - 24937, - 24938, - 24939, - 24940, - 24941, - 24942, - 24943, - 24944, - 24945, - 24946, - 24947, - 24948, - 24949, - 24950, - 24951, - 24952, - 24953, - 24954, - 24955, - 24956, - 24957, - 24958, - 24959, - 24960, - 24961, - 24962, - 24963, - 24964, - 24965, - 24966, - 24967, - 24968, - 24969, - 24970, - 24971, - 24972, - 24973, - 24974, - 24975, - 24976, - 24977, - 24978, - 24979, - 24980, - 24981, - 24982, - 24983, - 24984, - 24985, - 24986, - 24987, - 24988, - 24989, - 24990, - 24991, - 24992, - 24993, - 24994, - 24995, - 24996, - 24997, - 24998, - 24999, - 25000, - 25001, - 25002, - 25003, - 25004, - 25005, - 25006, - 25007, - 25008, - 25009, - 25010, - 25011, - 25012, - 25013, - 25014, - 25015, - 25016, - 25017, - 25018, - 25019, - 25020, - 25021, - 25022, - 25023, - 25024, - 25025, - 25026, - 25027, - 25028, - 25029, - 25030, - 25031, - 25032, - 25033, - 25034, - 25035, - 25036, - 25037, - 25038, - 25039, - 25040, - 25041, - 25042, - 25043, - 25044, - 25045, - 25046, - 25047, - 25048, - 25049, - 25050, - 25051, - 25052, - 25053, - 25054, - 25055, - 25056, - 25057, - 25058, - 25059, - 25060, - 25061, - 25062, - 25063, - 25064, - 25065, - 25066, - 25067, - 25068, - 25069, - 25070, - 25071, - 25072, - 25073, - 25074, - 25075, - 25076, - 25077, - 25078, - 25079, - 25080, - 25081, - 25082, - 25083, - 25084, - 25085, - 25086, - 25087, - 25088, - 25089, - 25090, - 25091, - 25092, - 25093, - 25094, - 25095, - 25096, - 25097, - 25098, - 25099, - 25100, - 25101, - 25102, - 25103, - 25104, - 25105, - 25106, - 25107, - 25108, - 25109, - 25110, - 25111, - 25112, - 25113, - 25114, - 25115, - 25116, - 25117, - 25118, - 25119, - 25120, - 25121, - 25122, - 25123, - 25124, - 25125, - 25126, - 25127, - 25128, - 25129, - 25130, - 25131, - 25132, - 25133, - 25134, - 25135, - 25136, - 25137, - 25138, - 25139, - 25140, - 25141, - 25142, - 25143, - 25144, - 25145, - 25146, - 25147, - 25148, - 25149, - 25150, - 25151, - 25152, - 25153, - 25154, - 25155, - 25156, - 25157, - 25158, - 25159, - 25160, - 25161, - 25162, - 25163, - 25164, - 25165, - 25166, - 25167, - 25168, - 25169, - 25170, - 25171, - 25172, - 25173, - 25174, - 25175, - 25176, - 25177, - 25178, - 25179, - 25180, - 25181, - 25182, - 25183, - 25184, - 25185, - 25186, - 25187, - 25188, - 25189, - 25190, - 25191, - 25192, - 25193, - 25194, - 25195, - 25196, - 25197, - 25198, - 25199, - 25200, - 25201, - 25202, - 25203, - 25204, - 25205, - 25206, - 25207, - 25208, - 25209, - 25210, - 25211, - 25212, - 25213, - 25214, - 25215, - 25216, - 25217, - 25218, - 25219, - 25220, - 25221, - 25222, - 25223, - 25224, - 25225, - 25226, - 25227, - 25228, - 25229, - 25230, - 25231, - 25232, - 25233, - 25234, - 25235, - 25236, - 25237, - 25238, - 25239, - 25240, - 25241, - 25242, - 25243, - 25244, - 25245, - 25246, - 25247, - 25248, - 25249, - 25250, - 25251, - 25252, - 25253, - 25254, - 25255, - 25256, - 25257, - 25258, - 25259, - 25260, - 25261, - 25262, - 25263, - 25264, - 25265, - 25266, - 25267, - 25268, - 25269, - 25270, - 25271, - 25272, - 25273, - 25274, - 25275, - 25276, - 25277, - 25278, - 25279, - 25280, - 25281, - 25282, - 25283, - 25284, - 25285, - 25286, - 25287, - 25288, - 25289, - 25290, - 25291, - 25292, - 25293, - 25294, - 25295, - 25296, - 25297, - 25298, - 25299, - 25300, - 25301, - 25302, - 25303, - 25304, - 25305, - 25306, - 25307, - 25308, - 25309, - 25310, - 25311, - 25312, - 25313, - 25314, - 25315, - 25316, - 25317, - 25318, - 25319, - 25320, - 25321, - 25322, - 25323, - 25324, - 25325, - 25326, - 25327, - 25328, - 25329, - 25330, - 25331, - 25332, - 25333, - 25334, - 25335, - 25336, - 25337, - 25338, - 25339, - 25340, - 25341, - 25342, - 25343, - 25344, - 25345, - 25346, - 25347, - 25348, - 25349, - 25350, - 25351, - 25352, - 25353, - 25354, - 25355, - 25356, - 25357, - 25358, - 25359, - 25360, - 25361, - 25362, - 25363, - 25364, - 25365, - 25366, - 25367, - 25368, - 25369, - 25370, - 25371, - 25372, - 25373, - 25374, - 25375, - 25376, - 25377, - 25378, - 25379, - 25380, - 25381, - 25382, - 25383, - 25384, - 25385, - 25386, - 25387, - 25388, - 25389, - 25390, - 25391, - 25392, - 25393, - 25394, - 25395, - 25396, - 25397, - 25398, - 25399, - 25400, - 25401, - 25402, - 25403, - 25404, - 25405, - 25406, - 25407, - 25408, - 25409, - 25410, - 25411, - 25412, - 25413, - 25414, - 25415, - 25416, - 25417, - 25418, - 25419, - 25420, - 25421, - 25422, - 25423, - 25424, - 25425, - 25426, - 25427, - 25428, - 25429, - 25430, - 25431, - 25432, - 25433, - 25434, - 25435, - 25436, - 25437, - 25438, - 25439, - 25440, - 25441, - 25442, - 25443, - 25444, - 25445, - 25446, - 25447, - 25448, - 25449, - 25450, - 25451, - 25452, - 25453, - 25454, - 25455, - 25456, - 25457, - 25458, - 25459, - 25460, - 25461, - 25462, - 25463, - 25464, - 25465, - 25466, - 25467, - 25468, - 25469, - 25470, - 25471, - 25472, - 25473, - 25474, - 25475, - 25476, - 25477, - 25478, - 25479, - 25480, - 25481, - 25482, - 25483, - 25484, - 25485, - 25486, - 25487, - 25488, - 25489, - 25490, - 25491, - 25492, - 25493, - 25494, - 25495, - 25496, - 25497, - 25498, - 25499, - 25500, - 25501, - 25502, - 25503, - 25504, - 25505, - 25506, - 25507, - 25508, - 25509, - 25510, - 25511, - 25512, - 25513, - 25514, - 25515, - 25516, - 25517, - 25518, - 25519, - 25520, - 25521, - 25522, - 25523, - 25524, - 25525, - 25526, - 25527, - 25528, - 25529, - 25530, - 25531, - 25532, - 25533, - 25534, - 25535, - 25536, - 25537, - 25538, - 25539, - 25540, - 25541, - 25542, - 25543, - 25544, - 25545, - 25546, - 25547, - 25548, - 25549, - 25550, - 25551, - 25552, - 25553, - 25554, - 25555, - 25556, - 25557, - 25558, - 25559, - 25560, - 25561, - 25562, - 25563, - 25564, - 25565, - 25566, - 25567, - 25568, - 25569, - 25570, - 25571, - 25572, - 25573, - 25574, - 25575, - 25576, - 25577, - 25578, - 25579, - 25580, - 25581, - 25582, - 25583, - 25584, - 25585, - 25586, - 25587, - 25588, - 25589, - 25590, - 25591, - 25592, - 25593, - 25594, - 25595, - 25596, - 25597, - 25598, - 25599, - 25600, - 25601, - 25602, - 25603, - 25604, - 25605, - 25606, - 25607, - 25608, - 25609, - 25610, - 25611, - 25612, - 25613, - 25614, - 25615, - 25616, - 25617, - 25618, - 25619, - 25620, - 25621, - 25622, - 25623, - 25624, - 25625, - 25626, - 25627, - 25628, - 25629, - 25630, - 25631, - 25632, - 25633, - 25634, - 25635, - 25636, - 25637, - 25638, - 25639, - 25640, - 25641, - 25642, - 25643, - 25644, - 25645, - 25646, - 25647, - 25648, - 25649, - 25650, - 25651, - 25652, - 25653, - 25654, - 25655, - 25656, - 25657, - 25658, - 25659, - 25660, - 25661, - 25662, - 25663, - 25664, - 25665, - 25666, - 25667, - 25668, - 25669, - 25670, - 25671, - 25672, - 25673, - 25674, - 25675, - 25676, - 25677, - 25678, - 25679, - 25680, - 25681, - 25682, - 25683, - 25684, - 25685, - 25686, - 25687, - 25688, - 25689, - 25690, - 25691, - 25692, - 25693, - 25694, - 25695, - 25696, - 25697, - 25698, - 25699, - 25700, - 25701, - 25702, - 25703, - 25704, - 25705, - 25706, - 25707, - 25708, - 25709, - 25710, - 25711, - 25712, - 25713, - 25714, - 25715, - 25716, - 25717, - 25718, - 25719, - 25720, - 25721, - 25722, - 25723, - 25724, - 25725, - 25726, - 25727, - 25728, - 25729, - 25730, - 25731, - 25732, - 25733, - 25734, - 25735, - 25736, - 25737, - 25738, - 25739, - 25740, - 25741, - 25742, - 25743, - 25744, - 25745, - 25746, - 25747, - 25748, - 25749, - 25750, - 25751, - 25752, - 25753, - 25754, - 25755, - 25756, - 25757, - 25758, - 25759, - 25760, - 25761, - 25762, - 25763, - 25764, - 25765, - 25766, - 25767, - 25768, - 25769, - 25770, - 25771, - 25772, - 25773, - 25774, - 25775, - 25776, - 25777, - 25778, - 25779, - 25780, - 25781, - 25782, - 25783, - 25784, - 25785, - 25786, - 25787, - 25788, - 25789, - 25790, - 25791, - 25792, - 25793, - 25794, - 25795, - 25796, - 25797, - 25798, - 25799, - 25800, - 25801, - 25802, - 25803, - 25804, - 25805, - 25806, - 25807, - 25808, - 25809, - 25810, - 25811, - 25812, - 25813, - 25814, - 25815, - 25816, - 25817, - 25818, - 25819, - 25820, - 25821, - 25822, - 25823, - 25824, - 25825, - 25826, - 25827, - 25828, - 25829, - 25830, - 25831, - 25832, - 25833, - 25834, - 25835, - 25836, - 25837, - 25838, - 25839, - 25840, - 25841, - 25842, - 25843, - 25844, - 25845, - 25846, - 25847, - 25848, - 25849, - 25850, - 25851, - 25852, - 25853, - 25854, - 25855, - 25856, - 25857, - 25858, - 25859, - 25860, - 25861, - 25862, - 25863, - 25864, - 25865, - 25866, - 25867, - 25868, - 25869, - 25870, - 25871, - 25872, - 25873, - 25874, - 25875, - 25876, - 25877, - 25878, - 25879, - 25880, - 25881, - 25882, - 25883, - 25884, - 25885, - 25886, - 25887, - 25888, - 25889, - 25890, - 25891, - 25892, - 25893, - 25894, - 25895, - 25896, - 25897, - 25898, - 25899, - 25900, - 25901, - 25902, - 25903, - 25904, - 25905, - 25906, - 25907, - 25908, - 25909, - 25910, - 25911, - 25912, - 25913, - 25914, - 25915, - 25916, - 25917, - 25918, - 25919, - 25920, - 25921, - 25922, - 25923, - 25924, - 25925, - 25926, - 25927, - 25928, - 25929, - 25930, - 25931, - 25932, - 25933, - 25934, - 25935, - 25936, - 25937, - 25938, - 25939, - 25940, - 25941, - 25942, - 25943, - 25944, - 25945, - 25946, - 25947, - 25948, - 25949, - 25950, - 25951, - 25952, - 25953, - 25954, - 25955, - 25956, - 25957, - 25958, - 25959, - 25960, - 25961, - 25962, - 25963, - 25964, - 25965, - 25966, - 25967, - 25968, - 25969, - 25970, - 25971, - 25972, - 25973, - 25974, - 25975, - 25976, - 25977, - 25978, - 25979, - 25980, - 25981, - 25982, - 25983, - 25984, - 25985, - 25986, - 25987, - 25988, - 25989, - 25990, - 25991, - 25992, - 25993, - 25994, - 25995, - 25996, - 25997, - 25998, - 25999, - 26000, - 26001, - 26002, - 26003, - 26004, - 26005, - 26006, - 26007, - 26008, - 26009, - 26010, - 26011, - 26012, - 26013, - 26014, - 26015, - 26016, - 26017, - 26018, - 26019, - 26020, - 26021, - 26022, - 26023, - 26024, - 26025, - 26026, - 26027, - 26028, - 26029, - 26030, - 26031, - 26032, - 26033, - 26034, - 26035, - 26036, - 26037, - 26038, - 26039, - 26040, - 26041, - 26042, - 26043, - 26044, - 26045, - 26046, - 26047, - 26048, - 26049, - 26050, - 26051, - 26052, - 26053, - 26054, - 26055, - 26056, - 26057, - 26058, - 26059, - 26060, - 26061, - 26062, - 26063, - 26064, - 26065, - 26066, - 26067, - 26068, - 26069, - 26070, - 26071, - 26072, - 26073, - 26074, - 26075, - 26076, - 26077, - 26078, - 26079, - 26080, - 26081, - 26082, - 26083, - 26084, - 26085, - 26086, - 26087, - 26088, - 26089, - 26090, - 26091, - 26092, - 26093, - 26094, - 26095, - 26096, - 26097, - 26098, - 26099, - 26100, - 26101, - 26102, - 26103, - 26104, - 26105, - 26106, - 26107, - 26108, - 26109, - 26110, - 26111, - 26112, - 26113, - 26114, - 26115, - 26116, - 26117, - 26118, - 26119, - 26120, - 26121, - 26122, - 26123, - 26124, - 26125, - 26126, - 26127, - 26128, - 26129, - 26130, - 26131, - 26132, - 26133, - 26134, - 26135, - 26136, - 26137, - 26138, - 26139, - 26140, - 26141, - 26142, - 26143, - 26144, - 26145, - 26146, - 26147, - 26148, - 26149, - 26150, - 26151, - 26152, - 26153, - 26154, - 26155, - 26156, - 26157, - 26158, - 26159, - 26160, - 26161, - 26162, - 26163, - 26164, - 26165, - 26166, - 26167, - 26168, - 26169, - 26170, - 26171, - 26172, - 26173, - 26174, - 26175, - 26176, - 26177, - 26178, - 26179, - 26180, - 26181, - 26182, - 26183, - 26184, - 26185, - 26186, - 26187, - 26188, - 26189, - 26190, - 26191, - 26192, - 26193, - 26194, - 26195, - 26196, - 26197, - 26198, - 26199, - 26200, - 26201, - 26202, - 26203, - 26204, - 26205, - 26206, - 26207, - 26208, - 26209, - 26210, - 26211, - 26212, - 26213, - 26214, - 26215, - 26216, - 26217, - 26218, - 26219, - 26220, - 26221, - 26222, - 26223, - 26224, - 26225, - 26226, - 26227, - 26228, - 26229, - 26230, - 26231, - 26232, - 26233, - 26234, - 26235, - 26236, - 26237, - 26238, - 26239, - 26240, - 26241, - 26242, - 26243, - 26244, - 26245, - 26246, - 26247, - 26248, - 26249, - 26250, - 26251, - 26252, - 26253, - 26254, - 26255, - 26256, - 26257, - 26258, - 26259, - 26260, - 26261, - 26262, - 26263, - 26264, - 26265, - 26266, - 26267, - 26268, - 26269, - 26270, - 26271, - 26272, - 26273, - 26274, - 26275, - 26276, - 26277, - 26278, - 26279, - 26280, - 26281, - 26282, - 26283, - 26284, - 26285, - 26286, - 26287, - 26288, - 26289, - 26290, - 26291, - 26292, - 26293, - 26294, - 26295, - 26296, - 26297, - 26298, - 26299, - 26300, - 26301, - 26302, - 26303, - 26304, - 26305, - 26306, - 26307, - 26308, - 26309, - 26310, - 26311, - 26312, - 26313, - 26314, - 26315, - 26316, - 26317, - 26318, - 26319, - 26320, - 26321, - 26322, - 26323, - 26324, - 26325, - 26326, - 26327, - 26328, - 26329, - 26330, - 26331, - 26332, - 26333, - 26334, - 26335, - 26336, - 26337, - 26338, - 26339, - 26340, - 26341, - 26342, - 26343, - 26344, - 26345, - 26346, - 26347, - 26348, - 26349, - 26350, - 26351, - 26352, - 26353, - 26354, - 26355, - 26356, - 26357, - 26358, - 26359, - 26360, - 26361, - 26362, - 26363, - 26364, - 26365, - 26366, - 26367, - 26368, - 26369, - 26370, - 26371, - 26372, - 26373, - 26374, - 26375, - 26376, - 26377, - 26378, - 26379, - 26380, - 26381, - 26382, - 26383, - 26384, - 26385, - 26386, - 26387, - 26388, - 26389, - 26390, - 26391, - 26392, - 26393, - 26394, - 26395, - 26396, - 26397, - 26398, - 26399, - 26400, - 26401, - 26402, - 26403, - 26404, - 26405, - 26406, - 26407, - 26408, - 26409, - 26410, - 26411, - 26412, - 26413, - 26414, - 26415, - 26416, - 26417, - 26418, - 26419, - 26420, - 26421, - 26422, - 26423, - 26424, - 26425, - 26426, - 26427, - 26428, - 26429, - 26430, - 26431, - 26432, - 26433, - 26434, - 26435, - 26436, - 26437, - 26438, - 26439, - 26440, - 26441, - 26442, - 26443, - 26444, - 26445, - 26446, - 26447, - 26448, - 26449, - 26450, - 26451, - 26452, - 26453, - 26454, - 26455, - 26456, - 26457, - 26458, - 26459, - 26460, - 26461, - 26462, - 26463, - 26464, - 26465, - 26466, - 26467, - 26468, - 26469, - 26470, - 26471, - 26472, - 26473, - 26474, - 26475, - 26476, - 26477, - 26478, - 26479, - 26480, - 26481, - 26482, - 26483, - 26484, - 26485, - 26486, - 26487, - 26488, - 26489, - 26490, - 26491, - 26492, - 26493, - 26494, - 26495, - 26496, - 26497, - 26498, - 26499, - 26500, - 26501, - 26502, - 26503, - 26504, - 26505, - 26506, - 26507, - 26508, - 26509, - 26510, - 26511, - 26512, - 26513, - 26514, - 26515, - 26516, - 26517, - 26518, - 26519, - 26520, - 26521, - 26522, - 26523, - 26524, - 26525, - 26526, - 26527, - 26528, - 26529, - 26530, - 26531, - 26532, - 26533, - 26534, - 26535, - 26536, - 26537, - 26538, - 26539, - 26540, - 26541, - 26542, - 26543, - 26544, - 26545, - 26546, - 26547, - 26548, - 26549, - 26550, - 26551, - 26552, - 26553, - 26554, - 26555, - 26556, - 26557, - 26558, - 26559, - 26560, - 26561, - 26562, - 26563, - 26564, - 26565, - 26566, - 26567, - 26568, - 26569, - 26570, - 26571, - 26572, - 26573, - 26574, - 26575, - 26576, - 26577, - 26578, - 26579, - 26580, - 26581, - 26582, - 26583, - 26584, - 26585, - 26586, - 26587, - 26588, - 26589, - 26590, - 26591, - 26592, - 26593, - 26594, - 26595, - 26596, - 26597, - 26598, - 26599, - 26600, - 26601, - 26602, - 26603, - 26604, - 26605, - 26606, - 26607, - 26608, - 26609, - 26610, - 26611, - 26612, - 26613, - 26614, - 26615, - 26616, - 26617, - 26618, - 26619, - 26620, - 26621, - 26622, - 26623, - 26624, - 26625, - 26626, - 26627, - 26628, - 26629, - 26630, - 26631, - 26632, - 26633, - 26634, - 26635, - 26636, - 26637, - 26638, - 26639, - 26640, - 26641, - 26642, - 26643, - 26644, - 26645, - 26646, - 26647, - 26648, - 26649, - 26650, - 26651, - 26652, - 26653, - 26654, - 26655, - 26656, - 26657, - 26658, - 26659, - 26660, - 26661, - 26662, - 26663, - 26664, - 26665, - 26666, - 26667, - 26668, - 26669, - 26670, - 26671, - 26672, - 26673, - 26674, - 26675, - 26676, - 26677, - 26678, - 26679, - 26680, - 26681, - 26682, - 26683, - 26684, - 26685, - 26686, - 26687, - 26688, - 26689, - 26690, - 26691, - 26692, - 26693, - 26694, - 26695, - 26696, - 26697, - 26698, - 26699, - 26700, - 26701, - 26702, - 26703, - 26704, - 26705, - 26706, - 26707, - 26708, - 26709, - 26710, - 26711, - 26712, - 26713, - 26714, - 26715, - 26716, - 26717, - 26718, - 26719, - 26720, - 26721, - 26722, - 26723, - 26724, - 26725, - 26726, - 26727, - 26728, - 26729, - 26730, - 26731, - 26732, - 26733, - 26734, - 26735, - 26736, - 26737, - 26738, - 26739, - 26740, - 26741, - 26742, - 26743, - 26744, - 26745, - 26746, - 26747, - 26748, - 26749, - 26750, - 26751, - 26752, - 26753, - 26754, - 26755, - 26756, - 26757, - 26758, - 26759, - 26760, - 26761, - 26762, - 26763, - 26764, - 26765, - 26766, - 26767, - 26768, - 26769, - 26770, - 26771, - 26772, - 26773, - 26774, - 26775, - 26776, - 26777, - 26778, - 26779, - 26780, - 26781, - 26782, - 26783, - 26784, - 26785, - 26786, - 26787, - 26788, - 26789, - 26790, - 26791, - 26792, - 26793, - 26794, - 26795, - 26796, - 26797, - 26798, - 26799, - 26800, - 26801, - 26802, - 26803, - 26804, - 26805, - 26806, - 26807, - 26808, - 26809, - 26810, - 26811, - 26812, - 26813, - 26814, - 26815, - 26816, - 26817, - 26818, - 26819, - 26820, - 26821, - 26822, - 26823, - 26824, - 26825, - 26826, - 26827, - 26828, - 26829, - 26830, - 26831, - 26832, - 26833, - 26834, - 26835, - 26836, - 26837, - 26838, - 26839, - 26840, - 26841, - 26842, - 26843, - 26844, - 26845, - 26846, - 26847, - 26848, - 26849, - 26850, - 26851, - 26852, - 26853, - 26854, - 26855, - 26856, - 26857, - 26858, - 26859, - 26860, - 26861, - 26862, - 26863, - 26864, - 26865, - 26866, - 26867, - 26868, - 26869, - 26870, - 26871, - 26872, - 26873, - 26874, - 26875, - 26876, - 26877, - 26878, - 26879, - 26880, - 26881, - 26882, - 26883, - 26884, - 26885, - 26886, - 26887, - 26888, - 26889, - 26890, - 26891, - 26892, - 26893, - 26894, - 26895, - 26896, - 26897, - 26898, - 26899, - 26900, - 26901, - 26902, - 26903, - 26904, - 26905, - 26906, - 26907, - 26908, - 26909, - 26910, - 26911, - 26912, - 26913, - 26914, - 26915, - 26916, - 26917, - 26918, - 26919, - 26920, - 26921, - 26922, - 26923, - 26924, - 26925, - 26926, - 26927, - 26928, - 26929, - 26930, - 26931, - 26932, - 26933, - 26934, - 26935, - 26936, - 26937, - 26938, - 26939, - 26940, - 26941, - 26942, - 26943, - 26944, - 26945, - 26946, - 26947, - 26948, - 26949, - 26950, - 26951, - 26952, - 26953, - 26954, - 26955, - 26956, - 26957, - 26958, - 26959, - 26960, - 26961, - 26962, - 26963, - 26964, - 26965, - 26966, - 26967, - 26968, - 26969, - 26970, - 26971, - 26972, - 26973, - 26974, - 26975, - 26976, - 26977, - 26978, - 26979, - 26980, - 26981, - 26982, - 26983, - 26984, - 26985, - 26986, - 26987, - 26988, - 26989, - 26990, - 26991, - 26992, - 26993, - 26994, - 26995, - 26996, - 26997, - 26998, - 26999, - 27000, - 27001, - 27002, - 27003, - 27004, - 27005, - 27006, - 27007, - 27008, - 27009, - 27010, - 27011, - 27012, - 27013, - 27014, - 27015, - 27016, - 27017, - 27018, - 27019, - 27020, - 27021, - 27022, - 27023, - 27024, - 27025, - 27026, - 27027, - 27028, - 27029, - 27030, - 27031, - 27032, - 27033, - 27034, - 27035, - 27036, - 27037, - 27038, - 27039, - 27040, - 27041, - 27042, - 27043, - 27044, - 27045, - 27046, - 27047, - 27048, - 27049, - 27050, - 27051, - 27052, - 27053, - 27054, - 27055, - 27056, - 27057, - 27058, - 27059, - 27060, - 27061, - 27062, - 27063, - 27064, - 27065, - 27066, - 27067, - 27068, - 27069, - 27070, - 27071, - 27072, - 27073, - 27074, - 27075, - 27076, - 27077, - 27078, - 27079, - 27080, - 27081, - 27082, - 27083, - 27084, - 27085, - 27086, - 27087, - 27088, - 27089, - 27090, - 27091, - 27092, - 27093, - 27094, - 27095, - 27096, - 27097, - 27098, - 27099, - 27100, - 27101, - 27102, - 27103, - 27104, - 27105, - 27106, - 27107, - 27108, - 27109, - 27110, - 27111, - 27112, - 27113, - 27114, - 27115, - 27116, - 27117, - 27118, - 27119, - 27120, - 27121, - 27122, - 27123, - 27124, - 27125, - 27126, - 27127, - 27128, - 27129, - 27130, - 27131, - 27132, - 27133, - 27134, - 27135, - 27136, - 27137, - 27138, - 27139, - 27140, - 27141, - 27142, - 27143, - 27144, - 27145, - 27146, - 27147, - 27148, - 27149, - 27150, - 27151, - 27152, - 27153, - 27154, - 27155, - 27156, - 27157, - 27158, - 27159, - 27160, - 27161, - 27162, - 27163, - 27164, - 27165, - 27166, - 27167, - 27168, - 27169, - 27170, - 27171, - 27172, - 27173, - 27174, - 27175, - 27176, - 27177, - 27178, - 27179, - 27180, - 27181, - 27182, - 27183, - 27184, - 27185, - 27186, - 27187, - 27188, - 27189, - 27190, - 27191, - 27192, - 27193, - 27194, - 27195, - 27196, - 27197, - 27198, - 27199, - 27200, - 27201, - 27202, - 27203, - 27204, - 27205, - 27206, - 27207, - 27208, - 27209, - 27210, - 27211, - 27212, - 27213, - 27214, - 27215, - 27216, - 27217, - 27218, - 27219, - 27220, - 27221, - 27222, - 27223, - 27224, - 27225, - 27226, - 27227, - 27228, - 27229, - 27230, - 27231, - 27232, - 27233, - 27234, - 27235, - 27236, - 27237, - 27238, - 27239, - 27240, - 27241, - 27242, - 27243, - 27244, - 27245, - 27246, - 27247, - 27248, - 27249, - 27250, - 27251, - 27252, - 27253, - 27254, - 27255, - 27256, - 27257, - 27258, - 27259, - 27260, - 27261, - 27262, - 27263, - 27264, - 27265, - 27266, - 27267, - 27268, - 27269, - 27270, - 27271, - 27272, - 27273, - 27274, - 27275, - 27276, - 27277, - 27278, - 27279, - 27280, - 27281, - 27282, - 27283, - 27284, - 27285, - 27286, - 27287, - 27288, - 27289, - 27290, - 27291, - 27292, - 27293, - 27294, - 27295, - 27296, - 27297, - 27298, - 27299, - 27300, - 27301, - 27302, - 27303, - 27304, - 27305, - 27306, - 27307, - 27308, - 27309, - 27310, - 27311, - 27312, - 27313, - 27314, - 27315, - 27316, - 27317, - 27318, - 27319, - 27320, - 27321, - 27322, - 27323, - 27324, - 27325, - 27326, - 27327, - 27328, - 27329, - 27330, - 27331, - 27332, - 27333, - 27334, - 27335, - 27336, - 27337, - 27338, - 27339, - 27340, - 27341, - 27342, - 27343, - 27344, - 27345, - 27346, - 27347, - 27348, - 27349, - 27350, - 27351, - 27352, - 27353, - 27354, - 27355, - 27356, - 27357, - 27358, - 27359, - 27360, - 27361, - 27362, - 27363, - 27364, - 27365, - 27366, - 27367, - 27368, - 27369, - 27370, - 27371, - 27372, - 27373, - 27374, - 27375, - 27376, - 27377, - 27378, - 27379, - 27380, - 27381, - 27382, - 27383, - 27384, - 27385, - 27386, - 27387, - 27388, - 27389, - 27390, - 27391, - 27392, - 27393, - 27394, - 27395, - 27396, - 27397, - 27398, - 27399, - 27400, - 27401, - 27402, - 27403, - 27404, - 27405, - 27406, - 27407, - 27408, - 27409, - 27410, - 27411, - 27412, - 27413, - 27414, - 27415, - 27416, - 27417, - 27418, - 27419, - 27420, - 27421, - 27422, - 27423, - 27424, - 27425, - 27426, - 27427, - 27428, - 27429, - 27430, - 27431, - 27432, - 27433, - 27434, - 27435, - 27436, - 27437, - 27438, - 27439, - 27440, - 27441, - 27442, - 27443, - 27444, - 27445, - 27446, - 27447, - 27448, - 27449, - 27450, - 27451, - 27452, - 27453, - 27454, - 27455, - 27456, - 27457, - 27458, - 27459, - 27460, - 27461, - 27462, - 27463, - 27464, - 27465, - 27466, - 27467, - 27468, - 27469, - 27470, - 27471, - 27472, - 27473, - 27474, - 27475, - 27476, - 27477, - 27478, - 27479, - 27480, - 27481, - 27482, - 27483, - 27484, - 27485, - 27486, - 27487, - 27488, - 27489, - 27490, - 27491, - 27492, - 27493, - 27494, - 27495, - 27496, - 27497, - 27498, - 27499, - 27500, - 27501, - 27502, - 27503, - 27504, - 27505, - 27506, - 27507, - 27508, - 27509, - 27510, - 27511, - 27512, - 27513, - 27514, - 27515, - 27516, - 27517, - 27518, - 27519, - 27520, - 27521, - 27522, - 27523, - 27524, - 27525, - 27526, - 27527, - 27528, - 27529, - 27530, - 27531, - 27532, - 27533, - 27534, - 27535, - 27536, - 27537, - 27538, - 27539, - 27540, - 27541, - 27542, - 27543, - 27544, - 27545, - 27546, - 27547, - 27548, - 27549, - 27550, - 27551, - 27552, - 27553, - 27554, - 27555, - 27556, - 27557, - 27558, - 27559, - 27560, - 27561, - 27562, - 27563, - 27564, - 27565, - 27566, - 27567, - 27568, - 27569, - 27570, - 27571, - 27572, - 27573, - 27574, - 27575, - 27576, - 27577, - 27578, - 27579, - 27580, - 27581, - 27582, - 27583, - 27584, - 27585, - 27586, - 27587, - 27588, - 27589, - 27590, - 27591, - 27592, - 27593, - 27594, - 27595, - 27596, - 27597, - 27598, - 27599, - 27600, - 27601, - 27602, - 27603, - 27604, - 27605, - 27606, - 27607, - 27608, - 27609, - 27610, - 27611, - 27612, - 27613, - 27614, - 27615, - 27616, - 27617, - 27618, - 27619, - 27620, - 27621, - 27622, - 27623, - 27624, - 27625, - 27626, - 27627, - 27628, - 27629, - 27630, - 27631, - 27632, - 27633, - 27634, - 27635, - 27636, - 27637, - 27638, - 27639, - 27640, - 27641, - 27642, - 27643, - 27644, - 27645, - 27646, - 27647, - 27648, - 27649, - 27650, - 27651, - 27652, - 27653, - 27654, - 27655, - 27656, - 27657, - 27658, - 27659, - 27660, - 27661, - 27662, - 27663, - 27664, - 27665, - 27666, - 27667, - 27668, - 27669, - 27670, - 27671, - 27672, - 27673, - 27674, - 27675, - 27676, - 27677, - 27678, - 27679, - 27680, - 27681, - 27682, - 27683, - 27684, - 27685, - 27686, - 27687, - 27688, - 27689, - 27690, - 27691, - 27692, - 27693, - 27694, - 27695, - 27696, - 27697, - 27698, - 27699, - 27700, - 27701, - 27702, - 27703, - 27704, - 27705, - 27706, - 27707, - 27708, - 27709, - 27710, - 27711, - 27712, - 27713, - 27714, - 27715, - 27716, - 27717, - 27718, - 27719, - 27720, - 27721, - 27722, - 27723, - 27724, - 27725, - 27726, - 27727, - 27728, - 27729, - 27730, - 27731, - 27732, - 27733, - 27734, - 27735, - 27736, - 27737, - 27738, - 27739, - 27740, - 27741, - 27742, - 27743, - 27744, - 27745, - 27746, - 27747, - 27748, - 27749, - 27750, - 27751, - 27752, - 27753, - 27754, - 27755, - 27756, - 27757, - 27758, - 27759, - 27760, - 27761, - 27762, - 27763, - 27764, - 27765, - 27766, - 27767, - 27768, - 27769, - 27770, - 27771, - 27772, - 27773, - 27774, - 27775, - 27776, - 27777, - 27778, - 27779, - 27780, - 27781, - 27782, - 27783, - 27784, - 27785, - 27786, - 27787, - 27788, - 27789, - 27790, - 27791, - 27792, - 27793, - 27794, - 27795, - 27796, - 27797, - 27798, - 27799, - 27800, - 27801, - 27802, - 27803, - 27804, - 27805, - 27806, - 27807, - 27808, - 27809, - 27810, - 27811, - 27812, - 27813, - 27814, - 27815, - 27816, - 27817, - 27818, - 27819, - 27820, - 27821, - 27822, - 27823, - 27824, - 27825, - 27826, - 27827, - 27828, - 27829, - 27830, - 27831, - 27832, - 27833, - 27834, - 27835, - 27836, - 27837, - 27838, - 27839, - 27840, - 27841, - 27842, - 27843, - 27844, - 27845, - 27846, - 27847, - 27848, - 27849, - 27850, - 27851, - 27852, - 27853, - 27854, - 27855, - 27856, - 27857, - 27858, - 27859, - 27860, - 27861, - 27862, - 27863, - 27864, - 27865, - 27866, - 27867, - 27868, - 27869, - 27870, - 27871, - 27872, - 27873, - 27874, - 27875, - 27876, - 27877, - 27878, - 27879, - 27880, - 27881, - 27882, - 27883, - 27884, - 27885, - 27886, - 27887, - 27888, - 27889, - 27890, - 27891, - 27892, - 27893, - 27894, - 27895, - 27896, - 27897, - 27898, - 27899, - 27900, - 27901, - 27902, - 27903, - 27904, - 27905, - 27906, - 27907, - 27908, - 27909, - 27910, - 27911, - 27912, - 27913, - 27914, - 27915, - 27916, - 27917, - 27918, - 27919, - 27920, - 27921, - 27922, - 27923, - 27924, - 27925, - 27926, - 27927, - 27928, - 27929, - 27930, - 27931, - 27932, - 27933, - 27934, - 27935, - 27936, - 27937, - 27938, - 27939, - 27940, - 27941, - 27942, - 27943, - 27944, - 27945, - 27946, - 27947, - 27948, - 27949, - 27950, - 27951, - 27952, - 27953, - 27954, - 27955, - 27956, - 27957, - 27958, - 27959, - 27960, - 27961, - 27962, - 27963, - 27964, - 27965, - 27966, - 27967, - 27968, - 27969, - 27970, - 27971, - 27972, - 27973, - 27974, - 27975, - 27976, - 27977, - 27978, - 27979, - 27980, - 27981, - 27982, - 27983, - 27984, - 27985, - 27986, - 27987, - 27988, - 27989, - 27990, - 27991, - 27992, - 27993, - 27994, - 27995, - 27996, - 27997, - 27998, - 27999, - 28000, - 28001, - 28002, - 28003, - 28004, - 28005, - 28006, - 28007, - 28008, - 28009, - 28010, - 28011, - 28012, - 28013, - 28014, - 28015, - 28016, - 28017, - 28018, - 28019, - 28020, - 28021, - 28022, - 28023, - 28024, - 28025, - 28026, - 28027, - 28028, - 28029, - 28030, - 28031, - 28032, - 28033, - 28034, - 28035, - 28036, - 28037, - 28038, - 28039, - 28040, - 28041, - 28042, - 28043, - 28044, - 28045, - 28046, - 28047, - 28048, - 28049, - 28050, - 28051, - 28052, - 28053, - 28054, - 28055, - 28056, - 28057, - 28058, - 28059, - 28060, - 28061, - 28062, - 28063, - 28064, - 28065, - 28066, - 28067, - 28068, - 28069, - 28070, - 28071, - 28072, - 28073, - 28074, - 28075, - 28076, - 28077, - 28078, - 28079, - 28080, - 28081, - 28082, - 28083, - 28084, - 28085, - 28086, - 28087, - 28088, - 28089, - 28090, - 28091, - 28092, - 28093, - 28094, - 28095, - 28096, - 28097, - 28098, - 28099, - 28100, - 28101, - 28102, - 28103, - 28104, - 28105, - 28106, - 28107, - 28108, - 28109, - 28110, - 28111, - 28112, - 28113, - 28114, - 28115, - 28116, - 28117, - 28118, - 28119, - 28120, - 28121, - 28122, - 28123, - 28124, - 28125, - 28126, - 28127, - 28128, - 28129, - 28130, - 28131, - 28132, - 28133, - 28134, - 28135, - 28136, - 28137, - 28138, - 28139, - 28140, - 28141, - 28142, - 28143, - 28144, - 28145, - 28146, - 28147, - 28148, - 28149, - 28150, - 28151, - 28152, - 28153, - 28154, - 28155, - 28156, - 28157, - 28158, - 28159, - 28160, - 28161, - 28162, - 28163, - 28164, - 28165, - 28166, - 28167, - 28168, - 28169, - 28170, - 28171, - 28172, - 28173, - 28174, - 28175, - 28176, - 28177, - 28178, - 28179, - 28180, - 28181, - 28182, - 28183, - 28184, - 28185, - 28186, - 28187, - 28188, - 28189, - 28190, - 28191, - 28192, - 28193, - 28194, - 28195, - 28196, - 28197, - 28198, - 28199, - 28200, - 28201, - 28202, - 28203, - 28204, - 28205, - 28206, - 28207, - 28208, - 28209, - 28210, - 28211, - 28212, - 28213, - 28214, - 28215, - 28216, - 28217, - 28218, - 28219, - 28220, - 28221, - 28222, - 28223, - 28224, - 28225, - 28226, - 28227, - 28228, - 28229, - 28230, - 28231, - 28232, - 28233, - 28234, - 28235, - 28236, - 28237, - 28238, - 28239, - 28240, - 28241, - 28242, - 28243, - 28244, - 28245, - 28246, - 28247, - 28248, - 28249, - 28250, - 28251, - 28252, - 28253, - 28254, - 28255, - 28256, - 28257, - 28258, - 28259, - 28260, - 28261, - 28262, - 28263, - 28264, - 28265, - 28266, - 28267, - 28268, - 28269, - 28270, - 28271, - 28272, - 28273, - 28274, - 28275, - 28276, - 28277, - 28278, - 28279, - 28280, - 28281, - 28282, - 28283, - 28284, - 28285, - 28286, - 28287, - 28288, - 28289, - 28290, - 28291, - 28292, - 28293, - 28294, - 28295, - 28296, - 28297, - 28298, - 28299, - 28300, - 28301, - 28302, - 28303, - 28304, - 28305, - 28306, - 28307, - 28308, - 28309, - 28310, - 28311, - 28312, - 28313, - 28314, - 28315, - 28316, - 28317, - 28318, - 28319, - 28320, - 28321, - 28322, - 28323, - 28324, - 28325, - 28326, - 28327, - 28328, - 28329, - 28330, - 28331, - 28332, - 28333, - 28334, - 28335, - 28336, - 28337, - 28338, - 28339, - 28340, - 28341, - 28342, - 28343, - 28344, - 28345, - 28346, - 28347, - 28348, - 28349, - 28350, - 28351, - 28352, - 28353, - 28354, - 28355, - 28356, - 28357, - 28358, - 28359, - 28360, - 28361, - 28362, - 28363, - 28364, - 28365, - 28366, - 28367, - 28368, - 28369, - 28370, - 28371, - 28372, - 28373, - 28374, - 28375, - 28376, - 28377, - 28378, - 28379, - 28380, - 28381, - 28382, - 28383, - 28384, - 28385, - 28386, - 28387, - 28388, - 28389, - 28390, - 28391, - 28392, - 28393, - 28394, - 28395, - 28396, - 28397, - 28398, - 28399, - 28400, - 28401, - 28402, - 28403, - 28404, - 28405, - 28406, - 28407, - 28408, - 28409, - 28410, - 28411, - 28412, - 28413, - 28414, - 28415, - 28416, - 28417, - 28418, - 28419, - 28420, - 28421, - 28422, - 28423, - 28424, - 28425, - 28426, - 28427, - 28428, - 28429, - 28430, - 28431, - 28432, - 28433, - 28434, - 28435, - 28436, - 28437, - 28438, - 28439, - 28440, - 28441, - 28442, - 28443, - 28444, - 28445, - 28446, - 28447, - 28448, - 28449, - 28450, - 28451, - 28452, - 28453, - 28454, - 28455, - 28456, - 28457, - 28458, - 28459, - 28460, - 28461, - 28462, - 28463, - 28464, - 28465, - 28466, - 28467, - 28468, - 28469, - 28470, - 28471, - 28472, - 28473, - 28474, - 28475, - 28476, - 28477, - 28478, - 28479, - 28480, - 28481, - 28482, - 28483, - 28484, - 28485, - 28486, - 28487, - 28488, - 28489, - 28490, - 28491, - 28492, - 28493, - 28494, - 28495, - 28496, - 28497, - 28498, - 28499, - 28500, - 28501, - 28502, - 28503, - 28504, - 28505, - 28506, - 28507, - 28508, - 28509, - 28510, - 28511, - 28512, - 28513, - 28514, - 28515, - 28516, - 28517, - 28518, - 28519, - 28520, - 28521, - 28522, - 28523, - 28524, - 28525, - 28526, - 28527, - 28528, - 28529, - 28530, - 28531, - 28532, - 28533, - 28534, - 28535, - 28536, - 28537, - 28538, - 28539, - 28540, - 28541, - 28542, - 28543, - 28544, - 28545, - 28546, - 28547, - 28548, - 28549, - 28550, - 28551, - 28552, - 28553, - 28554, - 28555, - 28556, - 28557, - 28558, - 28559, - 28560, - 28561, - 28562, - 28563, - 28564, - 28565, - 28566, - 28567, - 28568, - 28569, - 28570, - 28571, - 28572, - 28573, - 28574, - 28575, - 28576, - 28577, - 28578, - 28579, - 28580, - 28581, - 28582, - 28583, - 28584, - 28585, - 28586, - 28587, - 28588, - 28589, - 28590, - 28591, - 28592, - 28593, - 28594, - 28595, - 28596, - 28597, - 28598, - 28599, - 28600, - 28601, - 28602, - 28603, - 28604, - 28605, - 28606, - 28607, - 28608, - 28609, - 28610, - 28611, - 28612, - 28613, - 28614, - 28615, - 28616, - 28617, - 28618, - 28619, - 28620, - 28621, - 28622, - 28623, - 28624, - 28625, - 28626, - 28627, - 28628, - 28629, - 28630, - 28631, - 28632, - 28633, - 28634, - 28635, - 28636, - 28637, - 28638, - 28639, - 28640, - 28641, - 28642, - 28643, - 28644, - 28645, - 28646, - 28647, - 28648, - 28649, - 28650, - 28651, - 28652, - 28653, - 28654, - 28655, - 28656, - 28657, - 28658, - 28659, - 28660, - 28661, - 28662, - 28663, - 28664, - 28665, - 28666, - 28667, - 28668, - 28669, - 28670, - 28671, - 28672, - 28673, - 28674, - 28675, - 28676, - 28677, - 28678, - 28679, - 28680, - 28681, - 28682, - 28683, - 28684, - 28685, - 28686, - 28687, - 28688, - 28689, - 28690, - 28691, - 28692, - 28693, - 28694, - 28695, - 28696, - 28697, - 28698, - 28699, - 28700, - 28701, - 28702, - 28703, - 28704, - 28705, - 28706, - 28707, - 28708, - 28709, - 28710, - 28711, - 28712, - 28713, - 28714, - 28715, - 28716, - 28717, - 28718, - 28719, - 28720, - 28721, - 28722, - 28723, - 28724, - 28725, - 28726, - 28727, - 28728, - 28729, - 28730, - 28731, - 28732, - 28733, - 28734, - 28735, - 28736, - 28737, - 28738, - 28739, - 28740, - 28741, - 28742, - 28743, - 28744, - 28745, - 28746, - 28747, - 28748, - 28749, - 28750, - 28751, - 28752, - 28753, - 28754, - 28755, - 28756, - 28757, - 28758, - 28759, - 28760, - 28761, - 28762, - 28763, - 28764, - 28765, - 28766, - 28767, - 28768, - 28769, - 28770, - 28771, - 28772, - 28773, - 28774, - 28775, - 28776, - 28777, - 28778, - 28779, - 28780, - 28781, - 28782, - 28783, - 28784, - 28785, - 28786, - 28787, - 28788, - 28789, - 28790, - 28791, - 28792, - 28793, - 28794, - 28795, - 28796, - 28797, - 28798, - 28799, - 28800, - 28801, - 28802, - 28803, - 28804, - 28805, - 28806, - 28807, - 28808, - 28809, - 28810, - 28811, - 28812, - 28813, - 28814, - 28815, - 28816, - 28817, - 28818, - 28819, - 28820, - 28821, - 28822, - 28823, - 28824, - 28825, - 28826, - 28827, - 28828, - 28829, - 28830, - 28831, - 28832, - 28833, - 28834, - 28835, - 28836, - 28837, - 28838, - 28839, - 28840, - 28841, - 28842, - 28843, - 28844, - 28845, - 28846, - 28847, - 28848, - 28849, - 28850, - 28851, - 28852, - 28853, - 28854, - 28855, - 28856, - 28857, - 28858, - 28859, - 28860, - 28861, - 28862, - 28863, - 28864, - 28865, - 28866, - 28867, - 28868, - 28869, - 28870, - 28871, - 28872, - 28873, - 28874, - 28875, - 28876, - 28877, - 28878, - 28879, - 28880, - 28881, - 28882, - 28883, - 28884, - 28885, - 28886, - 28887, - 28888, - 28889, - 28890, - 28891, - 28892, - 28893, - 28894, - 28895, - 28896, - 28897, - 28898, - 28899, - 28900, - 28901, - 28902, - 28903, - 28904, - 28905, - 28906, - 28907, - 28908, - 28909, - 28910, - 28911, - 28912, - 28913, - 28914, - 28915, - 28916, - 28917, - 28918, - 28919, - 28920, - 28921, - 28922, - 28923, - 28924, - 28925, - 28926, - 28927, - 28928, - 28929, - 28930, - 28931, - 28932, - 28933, - 28934, - 28935, - 28936, - 28937, - 28938, - 28939, - 28940, - 28941, - 28942, - 28943, - 28944, - 28945, - 28946, - 28947, - 28948, - 28949, - 28950, - 28951, - 28952, - 28953, - 28954, - 28955, - 28956, - 28957, - 28958, - 28959, - 28960, - 28961, - 28962, - 28963, - 28964, - 28965, - 28966, - 28967, - 28968, - 28969, - 28970, - 28971, - 28972, - 28973, - 28974, - 28975, - 28976, - 28977, - 28978, - 28979, - 28980, - 28981, - 28982, - 28983, - 28984, - 28985, - 28986, - 28987, - 28988, - 28989, - 28990, - 28991, - 28992, - 28993, - 28994, - 28995, - 28996, - 28997, - 28998, - 28999, - 29000, - 29001, - 29002, - 29003, - 29004, - 29005, - 29006, - 29007, - 29008, - 29009, - 29010, - 29011, - 29012, - 29013, - 29014, - 29015, - 29016, - 29017, - 29018, - 29019, - 29020, - 29021, - 29022, - 29023, - 29024, - 29025, - 29026, - 29027, - 29028, - 29029, - 29030, - 29031, - 29032, - 29033, - 29034, - 29035, - 29036, - 29037, - 29038, - 29039, - 29040, - 29041, - 29042, - 29043, - 29044, - 29045, - 29046, - 29047, - 29048, - 29049, - 29050, - 29051, - 29052, - 29053, - 29054, - 29055, - 29056, - 29057, - 29058, - 29059, - 29060, - 29061, - 29062, - 29063, - 29064, - 29065, - 29066, - 29067, - 29068, - 29069, - 29070, - 29071, - 29072, - 29073, - 29074, - 29075, - 29076, - 29077, - 29078, - 29079, - 29080, - 29081, - 29082, - 29083, - 29084, - 29085, - 29086, - 29087, - 29088, - 29089, - 29090, - 29091, - 29092, - 29093, - 29094, - 29095, - 29096, - 29097, - 29098, - 29099, - 29100, - 29101, - 29102, - 29103, - 29104, - 29105, - 29106, - 29107, - 29108, - 29109, - 29110, - 29111, - 29112, - 29113, - 29114, - 29115, - 29116, - 29117, - 29118, - 29119, - 29120, - 29121, - 29122, - 29123, - 29124, - 29125, - 29126, - 29127, - 29128, - 29129, - 29130, - 29131, - 29132, - 29133, - 29134, - 29135, - 29136, - 29137, - 29138, - 29139, - 29140, - 29141, - 29142, - 29143, - 29144, - 29145, - 29146, - 29147, - 29148, - 29149, - 29150, - 29151, - 29152, - 29153, - 29154, - 29155, - 29156, - 29157, - 29158, - 29159, - 29160, - 29161, - 29162, - 29163, - 29164, - 29165, - 29166, - 29167, - 29168, - 29169, - 29170, - 29171, - 29172, - 29173, - 29174, - 29175, - 29176, - 29177, - 29178, - 29179, - 29180, - 29181, - 29182, - 29183, - 29184, - 29185, - 29186, - 29187, - 29188, - 29189, - 29190, - 29191, - 29192, - 29193, - 29194, - 29195, - 29196, - 29197, - 29198, - 29199, - 29200, - 29201, - 29202, - 29203, - 29204, - 29205, - 29206, - 29207, - 29208, - 29209, - 29210, - 29211, - 29212, - 29213, - 29214, - 29215, - 29216, - 29217, - 29218, - 29219, - 29220, - 29221, - 29222, - 29223, - 29224, - 29225, - 29226, - 29227, - 29228, - 29229, - 29230, - 29231, - 29232, - 29233, - 29234, - 29235, - 29236, - 29237, - 29238, - 29239, - 29240, - 29241, - 29242, - 29243, - 29244, - 29245, - 29246, - 29247, - 29248, - 29249, - 29250, - 29251, - 29252, - 29253, - 29254, - 29255, - 29256, - 29257, - 29258, - 29259, - 29260, - 29261, - 29262, - 29263, - 29264, - 29265, - 29266, - 29267, - 29268, - 29269, - 29270, - 29271, - 29272, - 29273, - 29274, - 29275, - 29276, - 29277, - 29278, - 29279, - 29280, - 29281, - 29282, - 29283, - 29284, - 29285, - 29286, - 29287, - 29288, - 29289, - 29290, - 29291, - 29292, - 29293, - 29294, - 29295, - 29296, - 29297, - 29298, - 29299, - 29300, - 29301, - 29302, - 29303, - 29304, - 29305, - 29306, - 29307, - 29308, - 29309, - 29310, - 29311, - 29312, - 29313, - 29314, - 29315, - 29316, - 29317, - 29318, - 29319, - 29320, - 29321, - 29322, - 29323, - 29324, - 29325, - 29326, - 29327, - 29328, - 29329, - 29330, - 29331, - 29332, - 29333, - 29334, - 29335, - 29336, - 29337, - 29338, - 29339, - 29340, - 29341, - 29342, - 29343, - 29344, - 29345, - 29346, - 29347, - 29348, - 29349, - 29350, - 29351, - 29352, - 29353, - 29354, - 29355, - 29356, - 29357, - 29358, - 29359, - 29360, - 29361, - 29362, - 29363, - 29364, - 29365, - 29366, - 29367, - 29368, - 29369, - 29370, - 29371, - 29372, - 29373, - 29374, - 29375, - 29376, - 29377, - 29378, - 29379, - 29380, - 29381, - 29382, - 29383, - 29384, - 29385, - 29386, - 29387, - 29388, - 29389, - 29390, - 29391, - 29392, - 29393, - 29394, - 29395, - 29396, - 29397, - 29398, - 29399, - 29400, - 29401, - 29402, - 29403, - 29404, - 29405, - 29406, - 29407, - 29408, - 29409, - 29410, - 29411, - 29412, - 29413, - 29414, - 29415, - 29416, - 29417, - 29418, - 29419, - 29420, - 29421, - 29422, - 29423, - 29424, - 29425, - 29426, - 29427, - 29428, - 29429, - 29430, - 29431, - 29432, - 29433, - 29434, - 29435, - 29436, - 29437, - 29438, - 29439, - 29440, - 29441, - 29442, - 29443, - 29444, - 29445, - 29446, - 29447, - 29448, - 29449, - 29450, - 29451, - 29452, - 29453, - 29454, - 29455, - 29456, - 29457, - 29458, - 29459, - 29460, - 29461, - 29462, - 29463, - 29464, - 29465, - 29466, - 29467, - 29468, - 29469, - 29470, - 29471, - 29472, - 29473, - 29474, - 29475, - 29476, - 29477, - 29478, - 29479, - 29480, - 29481, - 29482, - 29483, - 29484, - 29485, - 29486, - 29487, - 29488, - 29489, - 29490, - 29491, - 29492, - 29493, - 29494, - 29495, - 29496, - 29497, - 29498, - 29499, - 29500, - 29501, - 29502, - 29503, - 29504, - 29505, - 29506, - 29507, - 29508, - 29509, - 29510, - 29511, - 29512, - 29513, - 29514, - 29515, - 29516, - 29517, - 29518, - 29519, - 29520, - 29521, - 29522, - 29523, - 29524, - 29525, - 29526, - 29527, - 29528, - 29529, - 29530, - 29531, - 29532, - 29533, - 29534, - 29535, - 29536, - 29537, - 29538, - 29539, - 29540, - 29541, - 29542, - 29543, - 29544, - 29545, - 29546, - 29547, - 29548, - 29549, - 29550, - 29551, - 29552, - 29553, - 29554, - 29555, - 29556, - 29557, - 29558, - 29559, - 29560, - 29561, - 29562, - 29563, - 29564, - 29565, - 29566, - 29567, - 29568, - 29569, - 29570, - 29571, - 29572, - 29573, - 29574, - 29575, - 29576, - 29577, - 29578, - 29579, - 29580, - 29581, - 29582, - 29583, - 29584, - 29585, - 29586, - 29587, - 29588, - 29589, - 29590, - 29591, - 29592, - 29593, - 29594, - 29595, - 29596, - 29597, - 29598, - 29599, - 29600, - 29601, - 29602, - 29603, - 29604, - 29605, - 29606, - 29607, - 29608, - 29609, - 29610, - 29611, - 29612, - 29613, - 29614, - 29615, - 29616, - 29617, - 29618, - 29619, - 29620, - 29621, - 29622, - 29623, - 29624, - 29625, - 29626, - 29627, - 29628, - 29629, - 29630, - 29631, - 29632, - 29633, - 29634, - 29635, - 29636, - 29637, - 29638, - 29639, - 29640, - 29641, - 29642, - 29643, - 29644, - 29645, - 29646, - 29647, - 29648, - 29649, - 29650, - 29651, - 29652, - 29653, - 29654, - 29655, - 29656, - 29657, - 29658, - 29659, - 29660, - 29661, - 29662, - 29663, - 29664, - 29665, - 29666, - 29667, - 29668, - 29669, - 29670, - 29671, - 29672, - 29673, - 29674, - 29675, - 29676, - 29677, - 29678, - 29679, - 29680, - 29681, - 29682, - 29683, - 29684, - 29685, - 29686, - 29687, - 29688, - 29689, - 29690, - 29691, - 29692, - 29693, - 29694, - 29695, - 29696, - 29697, - 29698, - 29699, - 29700, - 29701, - 29702, - 29703, - 29704, - 29705, - 29706, - 29707, - 29708, - 29709, - 29710, - 29711, - 29712, - 29713, - 29714, - 29715, - 29716, - 29717, - 29718, - 29719, - 29720, - 29721, - 29722, - 29723, - 29724, - 29725, - 29726, - 29727, - 29728, - 29729, - 29730, - 29731, - 29732, - 29733, - 29734, - 29735, - 29736, - 29737, - 29738, - 29739, - 29740, - 29741, - 29742, - 29743, - 29744, - 29745, - 29746, - 29747, - 29748, - 29749, - 29750, - 29751, - 29752, - 29753, - 29754, - 29755, - 29756, - 29757, - 29758, - 29759, - 29760, - 29761, - 29762, - 29763, - 29764, - 29765, - 29766, - 29767, - 29768, - 29769, - 29770, - 29771, - 29772, - 29773, - 29774, - 29775, - 29776, - 29777, - 29778, - 29779, - 29780, - 29781, - 29782, - 29783, - 29784, - 29785, - 29786, - 29787, - 29788, - 29789, - 29790, - 29791, - 29792, - 29793, - 29794, - 29795, - 29796, - 29797, - 29798, - 29799, - 29800, - 29801, - 29802, - 29803, - 29804, - 29805, - 29806, - 29807, - 29808, - 29809, - 29810, - 29811, - 29812, - 29813, - 29814, - 29815, - 29816, - 29817, - 29818, - 29819, - 29820, - 29821, - 29822, - 29823, - 29824, - 29825, - 29826, - 29827, - 29828, - 29829, - 29830, - 29831, - 29832, - 29833, - 29834, - 29835, - 29836, - 29837, - 29838, - 29839, - 29840, - 29841, - 29842, - 29843, - 29844, - 29845, - 29846, - 29847, - 29848, - 29849, - 29850, - 29851, - 29852, - 29853, - 29854, - 29855, - 29856, - 29857, - 29858, - 29859, - 29860, - 29861, - 29862, - 29863, - 29864, - 29865, - 29866, - 29867, - 29868, - 29869, - 29870, - 29871, - 29872, - 29873, - 29874, - 29875, - 29876, - 29877, - 29878, - 29879, - 29880, - 29881, - 29882, - 29883, - 29884, - 29885, - 29886, - 29887, - 29888, - 29889, - 29890, - 29891, - 29892, - 29893, - 29894, - 29895, - 29896, - 29897, - 29898, - 29899, - 29900, - 29901, - 29902, - 29903, - 29904, - 29905, - 29906, - 29907, - 29908, - 29909, - 29910, - 29911, - 29912, - 29913, - 29914, - 29915, - 29916, - 29917, - 29918, - 29919, - 29920, - 29921, - 29922, - 29923, - 29924, - 29925, - 29926, - 29927, - 29928, - 29929, - 29930, - 29931, - 29932, - 29933, - 29934, - 29935, - 29936, - 29937, - 29938, - 29939, - 29940, - 29941, - 29942, - 29943, - 29944, - 29945, - 29946, - 29947, - 29948, - 29949, - 29950, - 29951, - 29952, - 29953, - 29954, - 29955, - 29956, - 29957, - 29958, - 29959, - 29960, - 29961, - 29962, - 29963, - 29964, - 29965, - 29966, - 29967, - 29968, - 29969, - 29970, - 29971, - 29972, - 29973, - 29974, - 29975, - 29976, - 29977, - 29978, - 29979, - 29980, - 29981, - 29982, - 29983, - 29984, - 29985, - 29986, - 29987, - 29988, - 29989, - 29990, - 29991, - 29992, - 29993, - 29994, - 29995, - 29996, - 29997, - 29998, - 29999, - 30000, - 30001, - 30002, - 30003, - 30004, - 30005, - 30006, - 30007, - 30008, - 30009, - 30010, - 30011, - 30012, - 30013, - 30014, - 30015, - 30016, - 30017, - 30018, - 30019, - 30020, - 30021, - 30022, - 30023, - 30024, - 30025, - 30026, - 30027, - 30028, - 30029, - 30030, - 30031, - 30032, - 30033, - 30034, - 30035, - 30036, - 30037, - 30038, - 30039, - 30040, - 30041, - 30042, - 30043, - 30044, - 30045, - 30046, - 30047, - 30048, - 30049, - 30050, - 30051, - 30052, - 30053, - 30054, - 30055, - 30056, - 30057, - 30058, - 30059, - 30060, - 30061, - 30062, - 30063, - 30064, - 30065, - 30066, - 30067, - 30068, - 30069, - 30070, - 30071, - 30072, - 30073, - 30074, - 30075, - 30076, - 30077, - 30078, - 30079, - 30080, - 30081, - 30082, - 30083, - 30084, - 30085, - 30086, - 30087, - 30088, - 30089, - 30090, - 30091, - 30092, - 30093, - 30094, - 30095, - 30096, - 30097, - 30098, - 30099, - 30100, - 30101, - 30102, - 30103, - 30104, - 30105, - 30106, - 30107, - 30108, - 30109, - 30110, - 30111, - 30112, - 30113, - 30114, - 30115, - 30116, - 30117, - 30118, - 30119, - 30120, - 30121, - 30122, - 30123, - 30124, - 30125, - 30126, - 30127, - 30128, - 30129, - 30130, - 30131, - 30132, - 30133, - 30134, - 30135, - 30136, - 30137, - 30138, - 30139, - 30140, - 30141, - 30142, - 30143, - 30144, - 30145, - 30146, - 30147, - 30148, - 30149, - 30150, - 30151, - 30152, - 30153, - 30154, - 30155, - 30156, - 30157, - 30158, - 30159, - 30160, - 30161, - 30162, - 30163, - 30164, - 30165, - 30166, - 30167, - 30168, - 30169, - 30170, - 30171, - 30172, - 30173, - 30174, - 30175, - 30176, - 30177, - 30178, - 30179, - 30180, - 30181, - 30182, - 30183, - 30184, - 30185, - 30186, - 30187, - 30188, - 30189, - 30190, - 30191, - 30192, - 30193, - 30194, - 30195, - 30196, - 30197, - 30198, - 30199, - 30200, - 30201, - 30202, - 30203, - 30204, - 30205, - 30206, - 30207, - 30208, - 30209, - 30210, - 30211, - 30212, - 30213, - 30214, - 30215, - 30216, - 30217, - 30218, - 30219, - 30220, - 30221, - 30222, - 30223, - 30224, - 30225, - 30226, - 30227, - 30228, - 30229, - 30230, - 30231, - 30232, - 30233, - 30234, - 30235, - 30236, - 30237, - 30238, - 30239, - 30240, - 30241, - 30242, - 30243, - 30244, - 30245, - 30246, - 30247, - 30248, - 30249, - 30250, - 30251, - 30252, - 30253, - 30254, - 30255, - 30256, - 30257, - 30258, - 30259, - 30260, - 30261, - 30262, - 30263, - 30264, - 30265, - 30266, - 30267, - 30268, - 30269, - 30270, - 30271, - 30272, - 30273, - 30274, - 30275, - 30276, - 30277, - 30278, - 30279, - 30280, - 30281, - 30282, - 30283, - 30284, - 30285, - 30286, - 30287, - 30288, - 30289, - 30290, - 30291, - 30292, - 30293, - 30294, - 30295, - 30296, - 30297, - 30298, - 30299, - 30300, - 30301, - 30302, - 30303, - 30304, - 30305, - 30306, - 30307, - 30308, - 30309, - 30310, - 30311, - 30312, - 30313, - 30314, - 30315, - 30316, - 30317, - 30318, - 30319, - 30320, - 30321, - 30322, - 30323, - 30324, - 30325, - 30326, - 30327, - 30328, - 30329, - 30330, - 30331, - 30332, - 30333, - 30334, - 30335, - 30336, - 30337, - 30338, - 30339, - 30340, - 30341, - 30342, - 30343, - 30344, - 30345, - 30346, - 30347, - 30348, - 30349, - 30350, - 30351, - 30352, - 30353, - 30354, - 30355, - 30356, - 30357, - 30358, - 30359, - 30360, - 30361, - 30362, - 30363, - 30364, - 30365, - 30366, - 30367, - 30368, - 30369, - 30370, - 30371, - 30372, - 30373, - 30374, - 30375, - 30376, - 30377, - 30378, - 30379, - 30380, - 30381, - 30382, - 30383, - 30384, - 30385, - 30386, - 30387, - 30388, - 30389, - 30390, - 30391, - 30392, - 30393, - 30394, - 30395, - 30396, - 30397, - 30398, - 30399, - 30400, - 30401, - 30402, - 30403, - 30404, - 30405, - 30406, - 30407, - 30408, - 30409, - 30410, - 30411, - 30412, - 30413, - 30414, - 30415, - 30416, - 30417, - 30418, - 30419, - 30420, - 30421, - 30422, - 30423, - 30424, - 30425, - 30426, - 30427, - 30428, - 30429, - 30430, - 30431, - 30432, - 30433, - 30434, - 30435, - 30436, - 30437, - 30438, - 30439, - 30440, - 30441, - 30442, - 30443, - 30444, - 30445, - 30446, - 30447, - 30448, - 30449, - 30450, - 30451, - 30452, - 30453, - 30454, - 30455, - 30456, - 30457, - 30458, - 30459, - 30460, - 30461, - 30462, - 30463, - 30464, - 30465, - 30466, - 30467, - 30468, - 30469, - 30470, - 30471, - 30472, - 30473, - 30474, - 30475, - 30476, - 30477, - 30478, - 30479, - 30480, - 30481, - 30482, - 30483, - 30484, - 30485, - 30486, - 30487, - 30488, - 30489, - 30490, - 30491, - 30492, - 30493, - 30494, - 30495, - 30496, - 30497, - 30498, - 30499, - 30500, - 30501, - 30502, - 30503, - 30504, - 30505, - 30506, - 30507, - 30508, - 30509, - 30510, - 30511, - 30512, - 30513, - 30514, - 30515, - 30516, - 30517, - 30518, - 30519, - 30520, - 30521, - 30522, - 30523, - 30524, - 30525, - 30526, - 30527, - 30528, - 30529, - 30530, - 30531, - 30532, - 30533, - 30534, - 30535, - 30536, - 30537, - 30538, - 30539, - 30540, - 30541, - 30542, - 30543, - 30544, - 30545, - 30546, - 30547, - 30548, - 30549, - 30550, - 30551, - 30552, - 30553, - 30554, - 30555, - 30556, - 30557, - 30558, - 30559, - 30560, - 30561, - 30562, - 30563, - 30564, - 30565, - 30566, - 30567, - 30568, - 30569, - 30570, - 30571, - 30572, - 30573, - 30574, - 30575, - 30576, - 30577, - 30578, - 30579, - 30580, - 30581, - 30582, - 30583, - 30584, - 30585, - 30586, - 30587, - 30588, - 30589, - 30590, - 30591, - 30592, - 30593, - 30594, - 30595, - 30596, - 30597, - 30598, - 30599, - 30600, - 30601, - 30602, - 30603, - 30604, - 30605, - 30606, - 30607, - 30608, - 30609, - 30610, - 30611, - 30612, - 30613, - 30614, - 30615, - 30616, - 30617, - 30618, - 30619, - 30620, - 30621, - 30622, - 30623, - 30624, - 30625, - 30626, - 30627, - 30628, - 30629, - 30630, - 30631, - 30632, - 30633, - 30634, - 30635, - 30636, - 30637, - 30638, - 30639, - 30640, - 30641, - 30642, - 30643, - 30644, - 30645, - 30646, - 30647, - 30648, - 30649, - 30650, - 30651, - 30652, - 30653, - 30654, - 30655, - 30656, - 30657, - 30658, - 30659, - 30660, - 30661, - 30662, - 30663, - 30664, - 30665, - 30666, - 30667, - 30668, - 30669, - 30670, - 30671, - 30672, - 30673, - 30674, - 30675, - 30676, - 30677, - 30678, - 30679, - 30680, - 30681, - 30682, - 30683, - 30684, - 30685, - 30686, - 30687, - 30688, - 30689, - 30690, - 30691, - 30692, - 30693, - 30694, - 30695, - 30696, - 30697, - 30698, - 30699, - 30700, - 30701, - 30702, - 30703, - 30704, - 30705, - 30706, - 30707, - 30708, - 30709, - 30710, - 30711, - 30712, - 30713, - 30714, - 30715, - 30716, - 30717, - 30718, - 30719, - 30720, - 30721, - 30722, - 30723, - 30724, - 30725, - 30726, - 30727, - 30728, - 30729, - 30730, - 30731, - 30732, - 30733, - 30734, - 30735, - 30736, - 30737, - 30738, - 30739, - 30740, - 30741, - 30742, - 30743, - 30744, - 30745, - 30746, - 30747, - 30748, - 30749, - 30750, - 30751, - 30752, - 30753, - 30754, - 30755, - 30756, - 30757, - 30758, - 30759, - 30760, - 30761, - 30762, - 30763, - 30764, - 30765, - 30766, - 30767, - 30768, - 30769, - 30770, - 30771, - 30772, - 30773, - 30774, - 30775, - 30776, - 30777, - 30778, - 30779, - 30780, - 30781, - 30782, - 30783, - 30784, - 30785, - 30786, - 30787, - 30788, - 30789, - 30790, - 30791, - 30792, - 30793, - 30794, - 30795, - 30796, - 30797, - 30798, - 30799, - 30800, - 30801, - 30802, - 30803, - 30804, - 30805, - 30806, - 30807, - 30808, - 30809, - 30810, - 30811, - 30812, - 30813, - 30814, - 30815, - 30816, - 30817, - 30818, - 30819, - 30820, - 30821, - 30822, - 30823, - 30824, - 30825, - 30826, - 30827, - 30828, - 30829, - 30830, - 30831, - 30832, - 30833, - 30834, - 30835, - 30836, - 30837, - 30838, - 30839, - 30840, - 30841, - 30842, - 30843, - 30844, - 30845, - 30846, - 30847, - 30848, - 30849, - 30850, - 30851, - 30852, - 30853, - 30854, - 30855, - 30856, - 30857, - 30858, - 30859, - 30860, - 30861, - 30862, - 30863, - 30864, - 30865, - 30866, - 30867, - 30868, - 30869, - 30870, - 30871, - 30872, - 30873, - 30874, - 30875, - 30876, - 30877, - 30878, - 30879, - 30880, - 30881, - 30882, - 30883, - 30884, - 30885, - 30886, - 30887, - 30888, - 30889, - 30890, - 30891, - 30892, - 30893, - 30894, - 30895, - 30896, - 30897, - 30898, - 30899, - 30900, - 30901, - 30902, - 30903, - 30904, - 30905, - 30906, - 30907, - 30908, - 30909, - 30910, - 30911, - 30912, - 30913, - 30914, - 30915, - 30916, - 30917, - 30918, - 30919, - 30920, - 30921, - 30922, - 30923, - 30924, - 30925, - 30926, - 30927, - 30928, - 30929, - 30930, - 30931, - 30932, - 30933, - 30934, - 30935, - 30936, - 30937, - 30938, - 30939, - 30940, - 30941, - 30942, - 30943, - 30944, - 30945, - 30946, - 30947, - 30948, - 30949, - 30950, - 30951, - 30952, - 30953, - 30954, - 30955, - 30956, - 30957, - 30958, - 30959, - 30960, - 30961, - 30962, - 30963, - 30964, - 30965, - 30966, - 30967, - 30968, - 30969, - 30970, - 30971, - 30972, - 30973, - 30974, - 30975, - 30976, - 30977, - 30978, - 30979, - 30980, - 30981, - 30982, - 30983, - 30984, - 30985, - 30986, - 30987, - 30988, - 30989, - 30990, - 30991, - 30992, - 30993, - 30994, - 30995, - 30996, - 30997, - 30998, - 30999, - 31000, - 31001, - 31002, - 31003, - 31004, - 31005, - 31006, - 31007, - 31008, - 31009, - 31010, - 31011, - 31012, - 31013, - 31014, - 31015, - 31016, - 31017, - 31018, - 31019, - 31020, - 31021, - 31022, - 31023, - 31024, - 31025, - 31026, - 31027, - 31028, - 31029, - 31030, - 31031, - 31032, - 31033, - 31034, - 31035, - 31036, - 31037, - 31038, - 31039, - 31040, - 31041, - 31042, - 31043, - 31044, - 31045, - 31046, - 31047, - 31048, - 31049, - 31050, - 31051, - 31052, - 31053, - 31054, - 31055, - 31056, - 31057, - 31058, - 31059, - 31060, - 31061, - 31062, - 31063, - 31064, - 31065, - 31066, - 31067, - 31068, - 31069, - 31070, - 31071, - 31072, - 31073, - 31074, - 31075, - 31076, - 31077, - 31078, - 31079, - 31080, - 31081, - 31082, - 31083, - 31084, - 31085, - 31086, - 31087, - 31088, - 31089, - 31090, - 31091, - 31092, - 31093, - 31094, - 31095, - 31096, - 31097, - 31098, - 31099, - 31100, - 31101, - 31102, - 31103, - 31104, - 31105, - 31106, - 31107, - 31108, - 31109, - 31110, - 31111, - 31112, - 31113, - 31114, - 31115, - 31116, - 31117, - 31118, - 31119, - 31120, - 31121, - 31122, - 31123, - 31124, - 31125, - 31126, - 31127, - 31128, - 31129, - 31130, - 31131, - 31132, - 31133, - 31134, - 31135, - 31136, - 31137, - 31138, - 31139, - 31140, - 31141, - 31142, - 31143, - 31144, - 31145, - 31146, - 31147, - 31148, - 31149, - 31150, - 31151, - 31152, - 31153, - 31154, - 31155, - 31156, - 31157, - 31158, - 31159, - 31160, - 31161, - 31162, - 31163, - 31164, - 31165, - 31166, - 31167, - 31168, - 31169, - 31170, - 31171, - 31172, - 31173, - 31174, - 31175, - 31176, - 31177, - 31178, - 31179, - 31180, - 31181, - 31182, - 31183, - 31184, - 31185, - 31186, - 31187, - 31188, - 31189, - 31190, - 31191, - 31192, - 31193, - 31194, - 31195, - 31196, - 31197, - 31198, - 31199, - 31200, - 31201, - 31202, - 31203, - 31204, - 31205, - 31206, - 31207, - 31208, - 31209, - 31210, - 31211, - 31212, - 31213, - 31214, - 31215, - 31216, - 31217, - 31218, - 31219, - 31220, - 31221, - 31222, - 31223, - 31224, - 31225, - 31226, - 31227, - 31228, - 31229, - 31230, - 31231, - 31232, - 31233, - 31234, - 31235, - 31236, - 31237, - 31238, - 31239, - 31240, - 31241, - 31242, - 31243, - 31244, - 31245, - 31246, - 31247, - 31248, - 31249, - 31250, - 31251, - 31252, - 31253, - 31254, - 31255, - 31256, - 31257, - 31258, - 31259, - 31260, - 31261, - 31262, - 31263, - 31264, - 31265, - 31266, - 31267, - 31268, - 31269, - 31270, - 31271, - 31272, - 31273, - 31274, - 31275, - 31276, - 31277, - 31278, - 31279, - 31280, - 31281, - 31282, - 31283, - 31284, - 31285, - 31286, - 31287, - 31288, - 31289, - 31290, - 31291, - 31292, - 31293, - 31294, - 31295, - 31296, - 31297, - 31298, - 31299, - 31300, - 31301, - 31302, - 31303, - 31304, - 31305, - 31306, - 31307, - 31308, - 31309, - 31310, - 31311, - 31312, - 31313, - 31314, - 31315, - 31316, - 31317, - 31318, - 31319, - 31320, - 31321, - 31322, - 31323, - 31324, - 31325, - 31326, - 31327, - 31328, - 31329, - 31330, - 31331, - 31332, - 31333, - 31334, - 31335, - 31336, - 31337, - 31338, - 31339, - 31340, - 31341, - 31342, - 31343, - 31344, - 31345, - 31346, - 31347, - 31348, - 31349, - 31350, - 31351, - 31352, - 31353, - 31354, - 31355, - 31356, - 31357, - 31358, - 31359, - 31360, - 31361, - 31362, - 31363, - 31364, - 31365, - 31366, - 31367, - 31368, - 31369, - 31370, - 31371, - 31372, - 31373, - 31374, - 31375, - 31376, - 31377, - 31378, - 31379, - 31380, - 31381, - 31382, - 31383, - 31384, - 31385, - 31386, - 31387, - 31388, - 31389, - 31390, - 31391, - 31392, - 31393, - 31394, - 31395, - 31396, - 31397, - 31398, - 31399, - 31400, - 31401, - 31402, - 31403, - 31404, - 31405, - 31406, - 31407, - 31408, - 31409, - 31410, - 31411, - 31412, - 31413, - 31414, - 31415, - 31416, - 31417, - 31418, - 31419, - 31420, - 31421, - 31422, - 31423, - 31424, - 31425, - 31426, - 31427, - 31428, - 31429, - 31430, - 31431, - 31432, - 31433, - 31434, - 31435, - 31436, - 31437, - 31438, - 31439, - 31440, - 31441, - 31442, - 31443, - 31444, - 31445, - 31446, - 31447, - 31448, - 31449, - 31450, - 31451, - 31452, - 31453, - 31454, - 31455, - 31456, - 31457, - 31458, - 31459, - 31460, - 31461, - 31462, - 31463, - 31464, - 31465, - 31466, - 31467, - 31468, - 31469, - 31470, - 31471, - 31472, - 31473, - 31474, - 31475, - 31476, - 31477, - 31478, - 31479, - 31480, - 31481, - 31482, - 31483, - 31484, - 31485, - 31486, - 31487, - 31488, - 31489, - 31490, - 31491, - 31492, - 31493, - 31494, - 31495, - 31496, - 31497, - 31498, - 31499, - 31500, - 31501, - 31502, - 31503, - 31504, - 31505, - 31506, - 31507, - 31508, - 31509, - 31510, - 31511, - 31512, - 31513, - 31514, - 31515, - 31516, - 31517, - 31518, - 31519, - 31520, - 31521, - 31522, - 31523, - 31524, - 31525, - 31526, - 31527, - 31528, - 31529, - 31530, - 31531, - 31532, - 31533, - 31534, - 31535, - 31536, - 31537, - 31538, - 31539, - 31540, - 31541, - 31542, - 31543, - 31544, - 31545, - 31546, - 31547, - 31548, - 31549, - 31550, - 31551, - 31552, - 31553, - 31554, - 31555, - 31556, - 31557, - 31558, - 31559, - 31560, - 31561, - 31562, - 31563, - 31564, - 31565, - 31566, - 31567, - 31568, - 31569, - 31570, - 31571, - 31572, - 31573, - 31574, - 31575, - 31576, - 31577, - 31578, - 31579, - 31580, - 31581, - 31582, - 31583, - 31584, - 31585, - 31586, - 31587, - 31588, - 31589, - 31590, - 31591, - 31592, - 31593, - 31594, - 31595, - 31596, - 31597, - 31598, - 31599, - 31600, - 31601, - 31602, - 31603, - 31604, - 31605, - 31606, - 31607, - 31608, - 31609, - 31610, - 31611, - 31612, - 31613, - 31614, - 31615, - 31616, - 31617, - 31618, - 31619, - 31620, - 31621, - 31622, - 31623, - 31624, - 31625, - 31626, - 31627, - 31628, - 31629, - 31630, - 31631, - 31632, - 31633, - 31634, - 31635, - 31636, - 31637, - 31638, - 31639, - 31640, - 31641, - 31642, - 31643, - 31644, - 31645, - 31646, - 31647, - 31648, - 31649, - 31650, - 31651, - 31652, - 31653, - 31654, - 31655, - 31656, - 31657, - 31658, - 31659, - 31660, - 31661, - 31662, - 31663, - 31664, - 31665, - 31666, - 31667, - 31668, - 31669, - 31670, - 31671, - 31672, - 31673, - 31674, - 31675, - 31676, - 31677, - 31678, - 31679, - 31680, - 31681, - 31682, - 31683, - 31684, - 31685, - 31686, - 31687, - 31688, - 31689, - 31690, - 31691, - 31692, - 31693, - 31694, - 31695, - 31696, - 31697, - 31698, - 31699, - 31700, - 31701, - 31702, - 31703, - 31704, - 31705, - 31706, - 31707, - 31708, - 31709, - 31710, - 31711, - 31712, - 31713, - 31714, - 31715, - 31716, - 31717, - 31718, - 31719, - 31720, - 31721, - 31722, - 31723, - 31724, - 31725, - 31726, - 31727, - 31728, - 31729, - 31730, - 31731, - 31732, - 31733, - 31734, - 31735, - 31736, - 31737, - 31738, - 31739, - 31740, - 31741, - 31742, - 31743, - 31744, - 31745, - 31746, - 31747, - 31748, - 31749, - 31750, - 31751, - 31752, - 31753, - 31754, - 31755, - 31756, - 31757, - 31758, - 31759, - 31760, - 31761, - 31762, - 31763, - 31764, - 31765, - 31766, - 31767, - 31768, - 31769, - 31770, - 31771, - 31772, - 31773, - 31774, - 31775, - 31776, - 31777, - 31778, - 31779, - 31780, - 31781, - 31782, - 31783, - 31784, - 31785, - 31786, - 31787, - 31788, - 31789, - 31790, - 31791, - 31792, - 31793, - 31794, - 31795, - 31796, - 31797, - 31798, - 31799, - 31800, - 31801, - 31802, - 31803, - 31804, - 31805, - 31806, - 31807, - 31808, - 31809, - 31810, - 31811, - 31812, - 31813, - 31814, - 31815, - 31816, - 31817, - 31818, - 31819, - 31820, - 31821, - 31822, - 31823, - 31824, - 31825, - 31826, - 31827, - 31828, - 31829, - 31830, - 31831, - 31832, - 31833, - 31834, - 31835, - 31836, - 31837, - 31838, - 31839, - 31840, - 31841, - 31842, - 31843, - 31844, - 31845, - 31846, - 31847, - 31848, - 31849, - 31850, - 31851, - 31852, - 31853, - 31854, - 31855, - 31856, - 31857, - 31858, - 31859, - 31860, - 31861, - 31862, - 31863, - 31864, - 31865, - 31866, - 31867, - 31868, - 31869, - 31870, - 31871, - 31872, - 31873, - 31874, - 31875, - 31876, - 31877, - 31878, - 31879, - 31880, - 31881, - 31882, - 31883, - 31884, - 31885, - 31886, - 31887, - 31888, - 31889, - 31890, - 31891, - 31892, - 31893, - 31894, - 31895, - 31896, - 31897, - 31898, - 31899, - 31900, - 31901, - 31902, - 31903, - 31904, - 31905, - 31906, - 31907, - 31908, - 31909, - 31910, - 31911, - 31912, - 31913, - 31914, - 31915, - 31916, - 31917, - 31918, - 31919, - 31920, - 31921, - 31922, - 31923, - 31924, - 31925, - 31926, - 31927, - 31928, - 31929, - 31930, - 31931, - 31932, - 31933, - 31934, - 31935, - 31936, - 31937, - 31938, - 31939, - 31940, - 31941, - 31942, - 31943, - 31944, - 31945, - 31946, - 31947, - 31948, - 31949, - 31950, - 31951, - 31952, - 31953, - 31954, - 31955, - 31956, - 31957, - 31958, - 31959, - 31960, - 31961, - 31962, - 31963, - 31964, - 31965, - 31966, - 31967, - 31968, - 31969, - 31970, - 31971, - 31972, - 31973, - 31974, - 31975, - 31976, - 31977, - 31978, - 31979, - 31980, - 31981, - 31982, - 31983, - 31984, - 31985, - 31986, - 31987, - 31988, - 31989, - 31990, - 31991, - 31992, - 31993, - 31994, - 31995, - 31996, - 31997, - 31998, - 31999, - 32000, - 32001, - 32002, - 32003, - 32004, - 32005, - 32006, - 32007, - 32008, - 32009, - 32010, - 32011, - 32012, - 32013, - 32014, - 32015, - 32016, - 32017, - 32018, - 32019, - 32020, - 32021, - 32022, - 32023, - 32024, - 32025, - 32026, - 32027, - 32028, - 32029, - 32030, - 32031, - 32032, - 32033, - 32034, - 32035, - 32036, - 32037, - 32038, - 32039, - 32040, - 32041, - 32042, - 32043, - 32044, - 32045, - 32046, - 32047, - 32048, - 32049, - 32050, - 32051, - 32052, - 32053, - 32054, - 32055, - 32056, - 32057, - 32058, - 32059, - 32060, - 32061, - 32062, - 32063, - 32064, - 32065, - 32066, - 32067, - 32068, - 32069, - 32070, - 32071, - 32072, - 32073, - 32074, - 32075, - 32076, - 32077, - 32078, - 32079, - 32080, - 32081, - 32082, - 32083, - 32084, - 32085, - 32086, - 32087, - 32088, - 32089, - 32090, - 32091, - 32092, - 32093, - 32094, - 32095, - 32096, - 32097, - 32098, - 32099, - 32100, - 32101, - 32102, - 32103, - 32104, - 32105, - 32106, - 32107, - 32108, - 32109, - 32110, - 32111, - 32112, - 32113, - 32114, - 32115, - 32116, - 32117, - 32118, - 32119, - 32120, - 32121, - 32122, - 32123, - 32124, - 32125, - 32126, - 32127, - 32128, - 32129, - 32130, - 32131, - 32132, - 32133, - 32134, - 32135, - 32136, - 32137, - 32138, - 32139, - 32140, - 32141, - 32142, - 32143, - 32144, - 32145, - 32146, - 32147, - 32148, - 32149, - 32150, - 32151, - 32152, - 32153, - 32154, - 32155, - 32156, - 32157, - 32158, - 32159, - 32160, - 32161, - 32162, - 32163, - 32164, - 32165, - 32166, - 32167, - 32168, - 32169, - 32170, - 32171, - 32172, - 32173, - 32174, - 32175, - 32176, - 32177, - 32178, - 32179, - 32180, - 32181, - 32182, - 32183, - 32184, - 32185, - 32186, - 32187, - 32188, - 32189, - 32190, - 32191, - 32192, - 32193, - 32194, - 32195, - 32196, - 32197, - 32198, - 32199, - 32200, - 32201, - 32202, - 32203, - 32204, - 32205, - 32206, - 32207, - 32208, - 32209, - 32210, - 32211, - 32212, - 32213, - 32214, - 32215, - 32216, - 32217, - 32218, - 32219, - 32220, - 32221, - 32222, - 32223, - 32224, - 32225, - 32226, - 32227, - 32228, - 32229, - 32230, - 32231, - 32232, - 32233, - 32234, - 32235, - 32236, - 32237, - 32238, - 32239, - 32240, - 32241, - 32242, - 32243, - 32244, - 32245, - 32246, - 32247, - 32248, - 32249, - 32250, - 32251, - 32252, - 32253, - 32254, - 32255, - 32256, - 32257, - 32258, - 32259, - 32260, - 32261, - 32262, - 32263, - 32264, - 32265, - 32266, - 32267, - 32268, - 32269, - 32270, - 32271, - 32272, - 32273, - 32274, - 32275, - 32276, - 32277, - 32278, - 32279, - 32280, - 32281, - 32282, - 32283, - 32284, - 32285, - 32286, - 32287, - 32288, - 32289, - 32290, - 32291, - 32292, - 32293, - 32294, - 32295, - 32296, - 32297, - 32298, - 32299, - 32300, - 32301, - 32302, - 32303, - 32304, - 32305, - 32306, - 32307, - 32308, - 32309, - 32310, - 32311, - 32312, - 32313, - 32314, - 32315, - 32316, - 32317, - 32318, - 32319, - 32320, - 32321, - 32322, - 32323, - 32324, - 32325, - 32326, - 32327, - 32328, - 32329, - 32330, - 32331, - 32332, - 32333, - 32334, - 32335, - 32336, - 32337, - 32338, - 32339, - 32340, - 32341, - 32342, - 32343, - 32344, - 32345, - 32346, - 32347, - 32348, - 32349, - 32350, - 32351, - 32352, - 32353, - 32354, - 32355, - 32356, - 32357, - 32358, - 32359, - 32360, - 32361, - 32362, - 32363, - 32364, - 32365, - 32366, - 32367, - 32368, - 32369, - 32370, - 32371, - 32372, - 32373, - 32374, - 32375, - 32376, - 32377, - 32378, - 32379, - 32380, - 32381, - 32382, - 32383, - 32384, - 32385, - 32386, - 32387, - 32388, - 32389, - 32390, - 32391, - 32392, - 32393, - 32394, - 32395, - 32396, - 32397, - 32398, - 32399, - 32400, - 32401, - 32402, - 32403, - 32404, - 32405, - 32406, - 32407, - 32408, - 32409, - 32410, - 32411, - 32412, - 32413, - 32414, - 32415, - 32416, - 32417, - 32418, - 32419, - 32420, - 32421, - 32422, - 32423, - 32424, - 32425, - 32426, - 32427, - 32428, - 32429, - 32430, - 32431, - 32432, - 32433, - 32434, - 32435, - 32436, - 32437, - 32438, - 32439, - 32440, - 32441, - 32442, - 32443, - 32444, - 32445, - 32446, - 32447, - 32448, - 32449, - 32450, - 32451, - 32452, - 32453, - 32454, - 32455, - 32456, - 32457, - 32458, - 32459, - 32460, - 32461, - 32462, - 32463, - 32464, - 32465, - 32466, - 32467, - 32468, - 32469, - 32470, - 32471, - 32472, - 32473, - 32474, - 32475, - 32476, - 32477, - 32478, - 32479, - 32480, - 32481, - 32482, - 32483, - 32484, - 32485, - 32486, - 32487, - 32488, - 32489, - 32490, - 32491, - 32492, - 32493, - 32494, - 32495, - 32496, - 32497, - 32498, - 32499, - 32500, - 32501, - 32502, - 32503, - 32504, - 32505, - 32506, - 32507, - 32508, - 32509, - 32510, - 32511, - 32512, - 32513, - 32514, - 32515, - 32516, - 32517, - 32518, - 32519, - 32520, - 32521, - 32522, - 32523, - 32524, - 32525, - 32526, - 32527, - 32528, - 32529, - 32530, - 32531, - 32532, - 32533, - 32534, - 32535, - 32536, - 32537, - 32538, - 32539, - 32540, - 32541, - 32542, - 32543, - 32544, - 32545, - 32546, - 32547, - 32548, - 32549, - 32550, - 32551, - 32552, - 32553, - 32554, - 32555, - 32556, - 32557, - 32558, - 32559, - 32560, - 32561, - 32562, - 32563, - 32564, - 32565, - 32566, - 32567, - 32568, - 32569, - 32570, - 32571, - 32572, - 32573, - 32574, - 32575, - 32576, - 32577, - 32578, - 32579, - 32580, - 32581, - 32582, - 32583, - 32584, - 32585, - 32586, - 32587, - 32588, - 32589, - 32590, - 32591, - 32592, - 32593, - 32594, - 32595, - 32596, - 32597, - 32598, - 32599, - 32600, - 32601, - 32602, - 32603, - 32604, - 32605, - 32606, - 32607, - 32608, - 32609, - 32610, - 32611, - 32612, - 32613, - 32614, - 32615, - 32616, - 32617, - 32618, - 32619, - 32620, - 32621, - 32622, - 32623, - 32624, - 32625, - 32626, - 32627, - 32628, - 32629, - 32630, - 32631, - 32632, - 32633, - 32634, - 32635, - 32636, - 32637, - 32638, - 32639, - 32640, - 32641, - 32642, - 32643, - 32644, - 32645, - 32646, - 32647, - 32648, - 32649, - 32650, - 32651, - 32652, - 32653, - 32654, - 32655, - 32656, - 32657, - 32658, - 32659, - 32660, - 32661, - 32662, - 32663, - 32664, - 32665, - 32666, - 32667, - 32668, - 32669, - 32670, - 32671, - 32672, - 32673, - 32674, - 32675, - 32676, - 32677, - 32678, - 32679, - 32680, - 32681, - 32682, - 32683, - 32684, - 32685, - 32686, - 32687, - 32688, - 32689, - 32690, - 32691, - 32692, - 32693, - 32694, - 32695, - 32696, - 32697, - 32698, - 32699, - 32700, - 32701, - 32702, - 32703, - 32704, - 32705, - 32706, - 32707, - 32708, - 32709, - 32710, - 32711, - 32712, - 32713, - 32714, - 32715, - 32716, - 32717, - 32718, - 32719, - 32720, - 32721, - 32722, - 32723, - 32724, - 32725, - 32726, - 32727, - 32728, - 32729, - 32730, - 32731, - 32732, - 32733, - 32734, - 32735, - 32736, - 32737, - 32738, - 32739, - 32740, - 32741, - 32742, - 32743, - 32744, - 32745, - 32746, - 32747, - 32748, - 32749, - 32750, - 32751, - 32752, - 32753, - 32754, - 32755, - 32756, - 32757, - 32758, - 32759, - 32760, - 32761, - 32762, - 32763, - 32764, - 32765, - 32766, - 32767, - 32768, - 32769, - 32770, - 32771, - 32772, - 32773, - 32774, - 32775, - 32776, - 32777, - 32778, - 32779, - 32780, - 32781, - 32782, - 32783, - 32784, - 32785, - 32786, - 32787, - 32788, - 32789, - 32790, - 32791, - 32792, - 32793, - 32794, - 32795, - 32796, - 32797, - 32798, - 32799, - 32800, - 32801, - 32802, - 32803, - 32804, - 32805, - 32806, - 32807, - 32808, - 32809, - 32810, - 32811, - 32812, - 32813, - 32814, - 32815, - 32816, - 32817, - 32818, - 32819, - 32820, - 32821, - 32822, - 32823, - 32824, - 32825, - 32826, - 32827, - 32828, - 32829, - 32830, - 32831, - 32832, - 32833, - 32834, - 32835, - 32836, - 32837, - 32838, - 32839, - 32840, - 32841, - 32842, - 32843, - 32844, - 32845, - 32846, - 32847, - 32848, - 32849, - 32850, - 32851, - 32852, - 32853, - 32854, - 32855, - 32856, - 32857, - 32858, - 32859, - 32860, - 32861, - 32862, - 32863, - 32864, - 32865, - 32866, - 32867, - 32868, - 32869, - 32870, - 32871, - 32872, - 32873, - 32874, - 32875, - 32876, - 32877, - 32878, - 32879, - 32880, - 32881, - 32882, - 32883, - 32884, - 32885, - 32886, - 32887, - 32888, - 32889, - 32890, - 32891, - 32892, - 32893, - 32894, - 32895, - 32896, - 32897, - 32898, - 32899, - 32900, - 32901, - 32902, - 32903, - 32904, - 32905, - 32906, - 32907, - 32908, - 32909, - 32910, - 32911, - 32912, - 32913, - 32914, - 32915, - 32916, - 32917, - 32918, - 32919, - 32920, - 32921, - 32922, - 32923, - 32924, - 32925, - 32926, - 32927, - 32928, - 32929, - 32930, - 32931, - 32932, - 32933, - 32934, - 32935, - 32936, - 32937, - 32938, - 32939, - 32940, - 32941, - 32942, - 32943, - 32944, - 32945, - 32946, - 32947, - 32948, - 32949, - 32950, - 32951, - 32952, - 32953, - 32954, - 32955, - 32956, - 32957, - 32958, - 32959, - 32960, - 32961, - 32962, - 32963, - 32964, - 32965, - 32966, - 32967, - 32968, - 32969, - 32970, - 32971, - 32972, - 32973, - 32974, - 32975, - 32976, - 32977, - 32978, - 32979, - 32980, - 32981, - 32982, - 32983, - 32984, - 32985, - 32986, - 32987, - 32988, - 32989, - 32990, - 32991, - 32992, - 32993, - 32994, - 32995, - 32996, - 32997, - 32998, - 32999, - 33000, - 33001, - 33002, - 33003, - 33004, - 33005, - 33006, - 33007, - 33008, - 33009, - 33010, - 33011, - 33012, - 33013, - 33014, - 33015, - 33016, - 33017, - 33018, - 33019, - 33020, - 33021, - 33022, - 33023, - 33024, - 33025, - 33026, - 33027, - 33028, - 33029, - 33030, - 33031, - 33032, - 33033, - 33034, - 33035, - 33036, - 33037, - 33038, - 33039, - 33040, - 33041, - 33042, - 33043, - 33044, - 33045, - 33046, - 33047, - 33048, - 33049, - 33050, - 33051, - 33052, - 33053, - 33054, - 33055, - 33056, - 33057, - 33058, - 33059, - 33060, - 33061, - 33062, - 33063, - 33064, - 33065, - 33066, - 33067, - 33068, - 33069, - 33070, - 33071, - 33072, - 33073, - 33074, - 33075, - 33076, - 33077, - 33078, - 33079, - 33080, - 33081, - 33082, - 33083, - 33084, - 33085, - 33086, - 33087, - 33088, - 33089, - 33090, - 33091, - 33092, - 33093, - 33094, - 33095, - 33096, - 33097, - 33098, - 33099, - 33100, - 33101, - 33102, - 33103, - 33104, - 33105, - 33106, - 33107, - 33108, - 33109, - 33110, - 33111, - 33112, - 33113, - 33114, - 33115, - 33116, - 33117, - 33118, - 33119, - 33120, - 33121, - 33122, - 33123, - 33124, - 33125, - 33126, - 33127, - 33128, - 33129, - 33130, - 33131, - 33132, - 33133, - 33134, - 33135, - 33136, - 33137, - 33138, - 33139, - 33140, - 33141, - 33142, - 33143, - 33144, - 33145, - 33146, - 33147, - 33148, - 33149, - 33150, - 33151, - 33152, - 33153, - 33154, - 33155, - 33156, - 33157, - 33158, - 33159, - 33160, - 33161, - 33162, - 33163, - 33164, - 33165, - 33166, - 33167, - 33168, - 33169, - 33170, - 33171, - 33172, - 33173, - 33174, - 33175, - 33176, - 33177, - 33178, - 33179, - 33180, - 33181, - 33182, - 33183, - 33184, - 33185, - 33186, - 33187, - 33188, - 33189, - 33190, - 33191, - 33192, - 33193, - 33194, - 33195, - 33196, - 33197, - 33198, - 33199, - 33200, - 33201, - 33202, - 33203, - 33204, - 33205, - 33206, - 33207, - 33208, - 33209, - 33210, - 33211, - 33212, - 33213, - 33214, - 33215, - 33216, - 33217, - 33218, - 33219, - 33220, - 33221, - 33222, - 33223, - 33224, - 33225, - 33226, - 33227, - 33228, - 33229, - 33230, - 33231, - 33232, - 33233, - 33234, - 33235, - 33236, - 33237, - 33238, - 33239, - 33240, - 33241, - 33242, - 33243, - 33244, - 33245, - 33246, - 33247, - 33248, - 33249, - 33250, - 33251, - 33252, - 33253, - 33254, - 33255, - 33256, - 33257, - 33258, - 33259, - 33260, - 33261, - 33262, - 33263, - 33264, - 33265, - 33266, - 33267, - 33268, - 33269, - 33270, - 33271, - 33272, - 33273, - 33274, - 33275, - 33276, - 33277, - 33278, - 33279, - 33280, - 33281, - 33282, - 33283, - 33284, - 33285, - 33286, - 33287, - 33288, - 33289, - 33290, - 33291, - 33292, - 33293, - 33294, - 33295, - 33296, - 33297, - 33298, - 33299, - 33300, - 33301, - 33302, - 33303, - 33304, - 33305, - 33306, - 33307, - 33308, - 33309, - 33310, - 33311, - 33312, - 33313, - 33314, - 33315, - 33316, - 33317, - 33318, - 33319, - 33320, - 33321, - 33322, - 33323, - 33324, - 33325, - 33326, - 33327, - 33328, - 33329, - 33330, - 33331, - 33332, - 33333, - 33334, - 33335, - 33336, - 33337, - 33338, - 33339, - 33340, - 33341, - 33342, - 33343, - 33344, - 33345, - 33346, - 33347, - 33348, - 33349, - 33350, - 33351, - 33352, - 33353, - 33354, - 33355, - 33356, - 33357, - 33358, - 33359, - 33360, - 33361, - 33362, - 33363, - 33364, - 33365, - 33366, - 33367, - 33368, - 33369, - 33370, - 33371, - 33372, - 33373, - 33374, - 33375, - 33376, - 33377, - 33378, - 33379, - 33380, - 33381, - 33382, - 33383, - 33384, - 33385, - 33386, - 33387, - 33388, - 33389, - 33390, - 33391, - 33392, - 33393, - 33394, - 33395, - 33396, - 33397, - 33398, - 33399, - 33400, - 33401, - 33402, - 33403, - 33404, - 33405, - 33406, - 33407, - 33408, - 33409, - 33410, - 33411, - 33412, - 33413, - 33414, - 33415, - 33416, - 33417, - 33418, - 33419, - 33420, - 33421, - 33422, - 33423, - 33424, - 33425, - 33426, - 33427, - 33428, - 33429, - 33430, - 33431, - 33432, - 33433, - 33434, - 33435, - 33436, - 33437, - 33438, - 33439, - 33440, - 33441, - 33442, - 33443, - 33444, - 33445, - 33446, - 33447, - 33448, - 33449, - 33450, - 33451, - 33452, - 33453, - 33454, - 33455, - 33456, - 33457, - 33458, - 33459, - 33460, - 33461, - 33462, - 33463, - 33464, - 33465, - 33466, - 33467, - 33468, - 33469, - 33470, - 33471, - 33472, - 33473, - 33474, - 33475, - 33476, - 33477, - 33478, - 33479, - 33480, - 33481, - 33482, - 33483, - 33484, - 33485, - 33486, - 33487, - 33488, - 33489, - 33490, - 33491, - 33492, - 33493, - 33494, - 33495, - 33496, - 33497, - 33498, - 33499, - 33500, - 33501, - 33502, - 33503, - 33504, - 33505, - 33506, - 33507, - 33508, - 33509, - 33510, - 33511, - 33512, - 33513, - 33514, - 33515, - 33516, - 33517, - 33518, - 33519, - 33520, - 33521, - 33522, - 33523, - 33524, - 33525, - 33526, - 33527, - 33528, - 33529, - 33530, - 33531, - 33532, - 33533, - 33534, - 33535, - 33536, - 33537, - 33538, - 33539, - 33540, - 33541, - 33542, - 33543, - 33544, - 33545, - 33546, - 33547, - 33548, - 33549, - 33550, - 33551, - 33552, - 33553, - 33554, - 33555, - 33556, - 33557, - 33558, - 33559, - 33560, - 33561, - 33562, - 33563, - 33564, - 33565, - 33566, - 33567, - 33568, - 33569, - 33570, - 33571, - 33572, - 33573, - 33574, - 33575, - 33576, - 33577, - 33578, - 33579, - 33580, - 33581, - 33582, - 33583, - 33584, - 33585, - 33586, - 33587, - 33588, - 33589, - 33590, - 33591, - 33592, - 33593, - 33594, - 33595, - 33596, - 33597, - 33598, - 33599, - 33600, - 33601, - 33602, - 33603, - 33604, - 33605, - 33606, - 33607, - 33608, - 33609, - 33610, - 33611, - 33612, - 33613, - 33614, - 33615, - 33616, - 33617, - 33618, - 33619, - 33620, - 33621, - 33622, - 33623, - 33624, - 33625, - 33626, - 33627, - 33628, - 33629, - 33630, - 33631, - 33632, - 33633, - 33634, - 33635, - 33636, - 33637, - 33638, - 33639, - 33640, - 33641, - 33642, - 33643, - 33644, - 33645, - 33646, - 33647, - 33648, - 33649, - 33650, - 33651, - 33652, - 33653, - 33654, - 33655, - 33656, - 33657, - 33658, - 33659, - 33660, - 33661, - 33662, - 33663, - 33664, - 33665, - 33666, - 33667, - 33668, - 33669, - 33670, - 33671, - 33672, - 33673, - 33674, - 33675, - 33676, - 33677, - 33678, - 33679, - 33680, - 33681, - 33682, - 33683, - 33684, - 33685, - 33686, - 33687, - 33688, - 33689, - 33690, - 33691, - 33692, - 33693, - 33694, - 33695, - 33696, - 33697, - 33698, - 33699, - 33700, - 33701, - 33702, - 33703, - 33704, - 33705, - 33706, - 33707, - 33708, - 33709, - 33710, - 33711, - 33712, - 33713, - 33714, - 33715, - 33716, - 33717, - 33718, - 33719, - 33720, - 33721, - 33722, - 33723, - 33724, - 33725, - 33726, - 33727, - 33728, - 33729, - 33730, - 33731, - 33732, - 33733, - 33734, - 33735, - 33736, - 33737, - 33738, - 33739, - 33740, - 33741, - 33742, - 33743, - 33744, - 33745, - 33746, - 33747, - 33748, - 33749, - 33750, - 33751, - 33752, - 33753, - 33754, - 33755, - 33756, - 33757, - 33758, - 33759, - 33760, - 33761, - 33762, - 33763, - 33764, - 33765, - 33766, - 33767, - 33768, - 33769, - 33770, - 33771, - 33772, - 33773, - 33774, - 33775, - 33776, - 33777, - 33778, - 33779, - 33780, - 33781, - 33782, - 33783, - 33784, - 33785, - 33786, - 33787, - 33788, - 33789, - 33790, - 33791, - 33792, - 33793, - 33794, - 33795, - 33796, - 33797, - 33798, - 33799, - 33800, - 33801, - 33802, - 33803, - 33804, - 33805, - 33806, - 33807, - 33808, - 33809, - 33810, - 33811, - 33812, - 33813, - 33814, - 33815, - 33816, - 33817, - 33818, - 33819, - 33820, - 33821, - 33822, - 33823, - 33824, - 33825, - 33826, - 33827, - 33828, - 33829, - 33830, - 33831, - 33832, - 33833, - 33834, - 33835, - 33836, - 33837, - 33838, - 33839, - 33840, - 33841, - 33842, - 33843, - 33844, - 33845, - 33846, - 33847, - 33848, - 33849, - 33850, - 33851, - 33852, - 33853, - 33854, - 33855, - 33856, - 33857, - 33858, - 33859, - 33860, - 33861, - 33862, - 33863, - 33864, - 33865, - 33866, - 33867, - 33868, - 33869, - 33870, - 33871, - 33872, - 33873, - 33874, - 33875, - 33876, - 33877, - 33878, - 33879, - 33880, - 33881, - 33882, - 33883, - 33884, - 33885, - 33886, - 33887, - 33888, - 33889, - 33890, - 33891, - 33892, - 33893, - 33894, - 33895, - 33896, - 33897, - 33898, - 33899, - 33900, - 33901, - 33902, - 33903, - 33904, - 33905, - 33906, - 33907, - 33908, - 33909, - 33910, - 33911, - 33912, - 33913, - 33914, - 33915, - 33916, - 33917, - 33918, - 33919, - 33920, - 33921, - 33922, - 33923, - 33924, - 33925, - 33926, - 33927, - 33928, - 33929, - 33930, - 33931, - 33932, - 33933, - 33934, - 33935, - 33936, - 33937, - 33938, - 33939, - 33940, - 33941, - 33942, - 33943, - 33944, - 33945, - 33946, - 33947, - 33948, - 33949, - 33950, - 33951, - 33952, - 33953, - 33954, - 33955, - 33956, - 33957, - 33958, - 33959, - 33960, - 33961, - 33962, - 33963, - 33964, - 33965, - 33966, - 33967, - 33968, - 33969, - 33970, - 33971, - 33972, - 33973, - 33974, - 33975, - 33976, - 33977, - 33978, - 33979, - 33980, - 33981, - 33982, - 33983, - 33984, - 33985, - 33986, - 33987, - 33988, - 33989, - 33990, - 33991, - 33992, - 33993, - 33994, - 33995, - 33996, - 33997, - 33998, - 33999, - 34000, - 34001, - 34002, - 34003, - 34004, - 34005, - 34006, - 34007, - 34008, - 34009, - 34010, - 34011, - 34012, - 34013, - 34014, - 34015, - 34016, - 34017, - 34018, - 34019, - 34020, - 34021, - 34022, - 34023, - 34024, - 34025, - 34026, - 34027, - 34028, - 34029, - 34030, - 34031, - 34032, - 34033, - 34034, - 34035, - 34036, - 34037, - 34038, - 34039, - 34040, - 34041, - 34042, - 34043, - 34044, - 34045, - 34046, - 34047, - 34048, - 34049, - 34050, - 34051, - 34052, - 34053, - 34054, - 34055, - 34056, - 34057, - 34058, - 34059, - 34060, - 34061, - 34062, - 34063, - 34064, - 34065, - 34066, - 34067, - 34068, - 34069, - 34070, - 34071, - 34072, - 34073, - 34074, - 34075, - 34076, - 34077, - 34078, - 34079, - 34080, - 34081, - 34082, - 34083, - 34084, - 34085, - 34086, - 34087, - 34088, - 34089, - 34090, - 34091, - 34092, - 34093, - 34094, - 34095, - 34096, - 34097, - 34098, - 34099, - 34100, - 34101, - 34102, - 34103, - 34104, - 34105, - 34106, - 34107, - 34108, - 34109, - 34110, - 34111, - 34112, - 34113, - 34114, - 34115, - 34116, - 34117, - 34118, - 34119, - 34120, - 34121, - 34122, - 34123, - 34124, - 34125, - 34126, - 34127, - 34128, - 34129, - 34130, - 34131, - 34132, - 34133, - 34134, - 34135, - 34136, - 34137, - 34138, - 34139, - 34140, - 34141, - 34142, - 34143, - 34144, - 34145, - 34146, - 34147, - 34148, - 34149, - 34150, - 34151, - 34152, - 34153, - 34154, - 34155, - 34156, - 34157, - 34158, - 34159, - 34160, - 34161, - 34162, - 34163, - 34164, - 34165, - 34166, - 34167, - 34168, - 34169, - 34170, - 34171, - 34172, - 34173, - 34174, - 34175, - 34176, - 34177, - 34178, - 34179, - 34180, - 34181, - 34182, - 34183, - 34184, - 34185, - 34186, - 34187, - 34188, - 34189, - 34190, - 34191, - 34192, - 34193, - 34194, - 34195, - 34196, - 34197, - 34198, - 34199, - 34200, - 34201, - 34202, - 34203, - 34204, - 34205, - 34206, - 34207, - 34208, - 34209, - 34210, - 34211, - 34212, - 34213, - 34214, - 34215, - 34216, - 34217, - 34218, - 34219, - 34220, - 34221, - 34222, - 34223, - 34224, - 34225, - 34226, - 34227, - 34228, - 34229, - 34230, - 34231, - 34232, - 34233, - 34234, - 34235, - 34236, - 34237, - 34238, - 34239, - 34240, - 34241, - 34242, - 34243, - 34244, - 34245, - 34246, - 34247, - 34248, - 34249, - 34250, - 34251, - 34252, - 34253, - 34254, - 34255, - 34256, - 34257, - 34258, - 34259, - 34260, - 34261, - 34262, - 34263, - 34264, - 34265, - 34266, - 34267, - 34268, - 34269, - 34270, - 34271, - 34272, - 34273, - 34274, - 34275, - 34276, - 34277, - 34278, - 34279, - 34280, - 34281, - 34282, - 34283, - 34284, - 34285, - 34286, - 34287, - 34288, - 34289, - 34290, - 34291, - 34292, - 34293, - 34294, - 34295, - 34296, - 34297, - 34298, - 34299, - 34300, - 34301, - 34302, - 34303, - 34304, - 34305, - 34306, - 34307, - 34308, - 34309, - 34310, - 34311, - 34312, - 34313, - 34314, - 34315, - 34316, - 34317, - 34318, - 34319, - 34320, - 34321, - 34322, - 34323, - 34324, - 34325, - 34326, - 34327, - 34328, - 34329, - 34330, - 34331, - 34332, - 34333, - 34334, - 34335, - 34336, - 34337, - 34338, - 34339, - 34340, - 34341, - 34342, - 34343, - 34344, - 34345, - 34346, - 34347, - 34348, - 34349, - 34350, - 34351, - 34352, - 34353, - 34354, - 34355, - 34356, - 34357, - 34358, - 34359, - 34360, - 34361, - 34362, - 34363, - 34364, - 34365, - 34366, - 34367, - 34368, - 34369, - 34370, - 34371, - 34372, - 34373, - 34374, - 34375, - 34376, - 34377, - 34378, - 34379, - 34380, - 34381, - 34382, - 34383, - 34384, - 34385, - 34386, - 34387, - 34388, - 34389, - 34390, - 34391, - 34392, - 34393, - 34394, - 34395, - 34396, - 34397, - 34398, - 34399, - 34400, - 34401, - 34402, - 34403, - 34404, - 34405, - 34406, - 34407, - 34408, - 34409, - 34410, - 34411, - 34412, - 34413, - 34414, - 34415, - 34416, - 34417, - 34418, - 34419, - 34420, - 34421, - 34422, - 34423, - 34424, - 34425, - 34426, - 34427, - 34428, - 34429, - 34430, - 34431, - 34432, - 34433, - 34434, - 34435, - 34436, - 34437, - 34438, - 34439, - 34440, - 34441, - 34442, - 34443, - 34444, - 34445, - 34446, - 34447, - 34448, - 34449, - 34450, - 34451, - 34452, - 34453, - 34454, - 34455, - 34456, - 34457, - 34458, - 34459, - 34460, - 34461, - 34462, - 34463, - 34464, - 34465, - 34466, - 34467, - 34468, - 34469, - 34470, - 34471, - 34472, - 34473, - 34474, - 34475, - 34476, - 34477, - 34478, - 34479, - 34480, - 34481, - 34482, - 34483, - 34484, - 34485, - 34486, - 34487, - 34488, - 34489, - 34490, - 34491, - 34492, - 34493, - 34494, - 34495, - 34496, - 34497, - 34498, - 34499, - 34500, - 34501, - 34502, - 34503, - 34504, - 34505, - 34506, - 34507, - 34508, - 34509, - 34510, - 34511, - 34512, - 34513, - 34514, - 34515, - 34516, - 34517, - 34518, - 34519, - 34520, - 34521, - 34522, - 34523, - 34524, - 34525, - 34526, - 34527, - 34528, - 34529, - 34530, - 34531, - 34532, - 34533, - 34534, - 34535, - 34536, - 34537, - 34538, - 34539, - 34540, - 34541, - 34542, - 34543, - 34544, - 34545, - 34546, - 34547, - 34548, - 34549, - 34550, - 34551, - 34552, - 34553, - 34554, - 34555, - 34556, - 34557, - 34558, - 34559, - 34560, - 34561, - 34562, - 34563, - 34564, - 34565, - 34566, - 34567, - 34568, - 34569, - 34570, - 34571, - 34572, - 34573, - 34574, - 34575, - 34576, - 34577, - 34578, - 34579, - 34580, - 34581, - 34582, - 34583, - 34584, - 34585, - 34586, - 34587, - 34588, - 34589, - 34590, - 34591, - 34592, - 34593, - 34594, - 34595, - 34596, - 34597, - 34598, - 34599, - 34600, - 34601, - 34602, - 34603, - 34604, - 34605, - 34606, - 34607, - 34608, - 34609, - 34610, - 34611, - 34612, - 34613, - 34614, - 34615, - 34616, - 34617, - 34618, - 34619, - 34620, - 34621, - 34622, - 34623, - 34624, - 34625, - 34626, - 34627, - 34628, - 34629, - 34630, - 34631, - 34632, - 34633, - 34634, - 34635, - 34636, - 34637, - 34638, - 34639, - 34640, - 34641, - 34642, - 34643, - 34644, - 34645, - 34646, - 34647, - 34648, - 34649, - 34650, - 34651, - 34652, - 34653, - 34654, - 34655, - 34656, - 34657, - 34658, - 34659, - 34660, - 34661, - 34662, - 34663, - 34664, - 34665, - 34666, - 34667, - 34668, - 34669, - 34670, - 34671, - 34672, - 34673, - 34674, - 34675, - 34676, - 34677, - 34678, - 34679, - 34680, - 34681, - 34682, - 34683, - 34684, - 34685, - 34686, - 34687, - 34688, - 34689, - 34690, - 34691, - 34692, - 34693, - 34694, - 34695, - 34696, - 34697, - 34698, - 34699, - 34700, - 34701, - 34702, - 34703, - 34704, - 34705, - 34706, - 34707, - 34708, - 34709, - 34710, - 34711, - 34712, - 34713, - 34714, - 34715, - 34716, - 34717, - 34718, - 34719, - 34720, - 34721, - 34722, - 34723, - 34724, - 34725, - 34726, - 34727, - 34728, - 34729, - 34730, - 34731, - 34732, - 34733, - 34734, - 34735, - 34736, - 34737, - 34738, - 34739, - 34740, - 34741, - 34742, - 34743, - 34744, - 34745, - 34746, - 34747, - 34748, - 34749, - 34750, - 34751, - 34752, - 34753, - 34754, - 34755, - 34756, - 34757, - 34758, - 34759, - 34760, - 34761, - 34762, - 34763, - 34764, - 34765, - 34766, - 34767, - 34768, - 34769, - 34770, - 34771, - 34772, - 34773, - 34774, - 34775, - 34776, - 34777, - 34778, - 34779, - 34780, - 34781, - 34782, - 34783, - 34784, - 34785, - 34786, - 34787, - 34788, - 34789, - 34790, - 34791, - 34792, - 34793, - 34794, - 34795, - 34796, - 34797, - 34798, - 34799, - 34800, - 34801, - 34802, - 34803, - 34804, - 34805, - 34806, - 34807, - 34808, - 34809, - 34810, - 34811, - 34812, - 34813, - 34814, - 34815, - 34816, - 34817, - 34818, - 34819, - 34820, - 34821, - 34822, - 34823, - 34824, - 34825, - 34826, - 34827, - 34828, - 34829, - 34830, - 34831, - 34832, - 34833, - 34834, - 34835, - 34836, - 34837, - 34838, - 34839, - 34840, - 34841, - 34842, - 34843, - 34844, - 34845, - 34846, - 34847, - 34848, - 34849, - 34850, - 34851, - 34852, - 34853, - 34854, - 34855, - 34856, - 34857, - 34858, - 34859, - 34860, - 34861, - 34862, - 34863, - 34864, - 34865, - 34866, - 34867, - 34868, - 34869, - 34870, - 34871, - 34872, - 34873, - 34874, - 34875, - 34876, - 34877, - 34878, - 34879, - 34880, - 34881, - 34882, - 34883, - 34884, - 34885, - 34886, - 34887, - 34888, - 34889, - 34890, - 34891, - 34892, - 34893, - 34894, - 34895, - 34896, - 34897, - 34898, - 34899, - 34900, - 34901, - 34902, - 34903, - 34904, - 34905, - 34906, - 34907, - 34908, - 34909, - 34910, - 34911, - 34912, - 34913, - 34914, - 34915, - 34916, - 34917, - 34918, - 34919, - 34920, - 34921, - 34922, - 34923, - 34924, - 34925, - 34926, - 34927, - 34928, - 34929, - 34930, - 34931, - 34932, - 34933, - 34934, - 34935, - 34936, - 34937, - 34938, - 34939, - 34940, - 34941, - 34942, - 34943, - 34944, - 34945, - 34946, - 34947, - 34948, - 34949, - 34950, - 34951, - 34952, - 34953, - 34954, - 34955, - 34956, - 34957, - 34958, - 34959, - 34960, - 34961, - 34962, - 34963, - 34964, - 34965, - 34966, - 34967, - 34968, - 34969, - 34970, - 34971, - 34972, - 34973, - 34974, - 34975, - 34976, - 34977, - 34978, - 34979, - 34980, - 34981, - 34982, - 34983, - 34984, - 34985, - 34986, - 34987, - 34988, - 34989, - 34990, - 34991, - 34992, - 34993, - 34994, - 34995, - 34996, - 34997, - 34998, - 34999, - 35000, - 35001, - 35002, - 35003, - 35004, - 35005, - 35006, - 35007, - 35008, - 35009, - 35010, - 35011, - 35012, - 35013, - 35014, - 35015, - 35016, - 35017, - 35018, - 35019, - 35020, - 35021, - 35022, - 35023, - 35024, - 35025, - 35026, - 35027, - 35028, - 35029, - 35030, - 35031, - 35032, - 35033, - 35034, - 35035, - 35036, - 35037, - 35038, - 35039, - 35040, - 35041, - 35042, - 35043, - 35044, - 35045, - 35046, - 35047, - 35048, - 35049, - 35050, - 35051, - 35052, - 35053, - 35054, - 35055, - 35056, - 35057, - 35058, - 35059, - 35060, - 35061, - 35062, - 35063, - 35064, - 35065, - 35066, - 35067, - 35068, - 35069, - 35070, - 35071, - 35072, - 35073, - 35074, - 35075, - 35076, - 35077, - 35078, - 35079, - 35080, - 35081, - 35082, - 35083, - 35084, - 35085, - 35086, - 35087, - 35088, - 35089, - 35090, - 35091, - 35092, - 35093, - 35094, - 35095, - 35096, - 35097, - 35098, - 35099, - 35100, - 35101, - 35102, - 35103, - 35104, - 35105, - 35106, - 35107, - 35108, - 35109, - 35110, - 35111, - 35112, - 35113, - 35114, - 35115, - 35116, - 35117, - 35118, - 35119, - 35120, - 35121, - 35122, - 35123, - 35124, - 35125, - 35126, - 35127, - 35128, - 35129, - 35130, - 35131, - 35132, - 35133, - 35134, - 35135, - 35136, - 35137, - 35138, - 35139, - 35140, - 35141, - 35142, - 35143, - 35144, - 35145, - 35146, - 35147, - 35148, - 35149, - 35150, - 35151, - 35152, - 35153, - 35154, - 35155, - 35156, - 35157, - 35158, - 35159, - 35160, - 35161, - 35162, - 35163, - 35164, - 35165, - 35166, - 35167, - 35168, - 35169, - 35170, - 35171, - 35172, - 35173, - 35174, - 35175, - 35176, - 35177, - 35178, - 35179, - 35180, - 35181, - 35182, - 35183, - 35184, - 35185, - 35186, - 35187, - 35188, - 35189, - 35190, - 35191, - 35192, - 35193, - 35194, - 35195, - 35196, - 35197, - 35198, - 35199, - 35200, - 35201, - 35202, - 35203, - 35204, - 35205, - 35206, - 35207, - 35208, - 35209, - 35210, - 35211, - 35212, - 35213, - 35214, - 35215, - 35216, - 35217, - 35218, - 35219, - 35220, - 35221, - 35222, - 35223, - 35224, - 35225, - 35226, - 35227, - 35228, - 35229, - 35230, - 35231, - 35232, - 35233, - 35234, - 35235, - 35236, - 35237, - 35238, - 35239, - 35240, - 35241, - 35242, - 35243, - 35244, - 35245, - 35246, - 35247, - 35248, - 35249, - 35250, - 35251, - 35252, - 35253, - 35254, - 35255, - 35256, - 35257, - 35258, - 35259, - 35260, - 35261, - 35262, - 35263, - 35264, - 35265, - 35266, - 35267, - 35268, - 35269, - 35270, - 35271, - 35272, - 35273, - 35274, - 35275, - 35276, - 35277, - 35278, - 35279, - 35280, - 35281, - 35282, - 35283, - 35284, - 35285, - 35286, - 35287, - 35288, - 35289, - 35290, - 35291, - 35292, - 35293, - 35294, - 35295, - 35296, - 35297, - 35298, - 35299, - 35300, - 35301, - 35302, - 35303, - 35304, - 35305, - 35306, - 35307, - 35308, - 35309, - 35310, - 35311, - 35312, - 35313, - 35314, - 35315, - 35316, - 35317, - 35318, - 35319, - 35320, - 35321, - 35322, - 35323, - 35324, - 35325, - 35326, - 35327, - 35328, - 35329, - 35330, - 35331, - 35332, - 35333, - 35334, - 35335, - 35336, - 35337, - 35338, - 35339, - 35340, - 35341, - 35342, - 35343, - 35344, - 35345, - 35346, - 35347, - 35348, - 35349, - 35350, - 35351, - 35352, - 35353, - 35354, - 35355, - 35356, - 35357, - 35358, - 35359, - 35360, - 35361, - 35362, - 35363, - 35364, - 35365, - 35366, - 35367, - 35368, - 35369, - 35370, - 35371, - 35372, - 35373, - 35374, - 35375, - 35376, - 35377, - 35378, - 35379, - 35380, - 35381, - 35382, - 35383, - 35384, - 35385, - 35386, - 35387, - 35388, - 35389, - 35390, - 35391, - 35392, - 35393, - 35394, - 35395, - 35396, - 35397, - 35398, - 35399, - 35400, - 35401, - 35402, - 35403, - 35404, - 35405, - 35406, - 35407, - 35408, - 35409, - 35410, - 35411, - 35412, - 35413, - 35414, - 35415, - 35416, - 35417, - 35418, - 35419, - 35420, - 35421, - 35422, - 35423, - 35424, - 35425, - 35426, - 35427, - 35428, - 35429, - 35430, - 35431, - 35432, - 35433, - 35434, - 35435, - 35436, - 35437, - 35438, - 35439, - 35440, - 35441, - 35442, - 35443, - 35444, - 35445, - 35446, - 35447, - 35448, - 35449, - 35450, - 35451, - 35452, - 35453, - 35454, - 35455, - 35456, - 35457, - 35458, - 35459, - 35460, - 35461, - 35462, - 35463, - 35464, - 35465, - 35466, - 35467, - 35468, - 35469, - 35470, - 35471, - 35472, - 35473, - 35474, - 35475, - 35476, - 35477, - 35478, - 35479, - 35480, - 35481, - 35482, - 35483, - 35484, - 35485, - 35486, - 35487, - 35488, - 35489, - 35490, - 35491, - 35492, - 35493, - 35494, - 35495, - 35496, - 35497, - 35498, - 35499, - 35500, - 35501, - 35502, - 35503, - 35504, - 35505, - 35506, - 35507, - 35508, - 35509, - 35510, - 35511, - 35512, - 35513, - 35514, - 35515, - 35516, - 35517, - 35518, - 35519, - 35520, - 35521, - 35522, - 35523, - 35524, - 35525, - 35526, - 35527, - 35528, - 35529, - 35530, - 35531, - 35532, - 35533, - 35534, - 35535, - 35536, - 35537, - 35538, - 35539, - 35540, - 35541, - 35542, - 35543, - 35544, - 35545, - 35546, - 35547, - 35548, - 35549, - 35550, - 35551, - 35552, - 35553, - 35554, - 35555, - 35556, - 35557, - 35558, - 35559, - 35560, - 35561, - 35562, - 35563, - 35564, - 35565, - 35566, - 35567, - 35568, - 35569, - 35570, - 35571, - 35572, - 35573, - 35574, - 35575, - 35576, - 35577, - 35578, - 35579, - 35580, - 35581, - 35582, - 35583, - 35584, - 35585, - 35586, - 35587, - 35588, - 35589, - 35590, - 35591, - 35592, - 35593, - 35594, - 35595, - 35596, - 35597, - 35598, - 35599, - 35600, - 35601, - 35602, - 35603, - 35604, - 35605, - 35606, - 35607, - 35608, - 35609, - 35610, - 35611, - 35612, - 35613, - 35614, - 35615, - 35616, - 35617, - 35618, - 35619, - 35620, - 35621, - 35622, - 35623, - 35624, - 35625, - 35626, - 35627, - 35628, - 35629, - 35630, - 35631, - 35632, - 35633, - 35634, - 35635, - 35636, - 35637, - 35638, - 35639, - 35640, - 35641, - 35642, - 35643, - 35644, - 35645, - 35646, - 35647, - 35648, - 35649, - 35650, - 35651, - 35652, - 35653, - 35654, - 35655, - 35656, - 35657, - 35658, - 35659, - 35660, - 35661, - 35662, - 35663, - 35664, - 35665, - 35666, - 35667, - 35668, - 35669, - 35670, - 35671, - 35672, - 35673, - 35674, - 35675, - 35676, - 35677, - 35678, - 35679, - 35680, - 35681, - 35682, - 35683, - 35684, - 35685, - 35686, - 35687, - 35688, - 35689, - 35690, - 35691, - 35692, - 35693, - 35694, - 35695, - 35696, - 35697, - 35698, - 35699, - 35700, - 35701, - 35702, - 35703, - 35704, - 35705, - 35706, - 35707, - 35708, - 35709, - 35710, - 35711, - 35712, - 35713, - 35714, - 35715, - 35716, - 35717, - 35718, - 35719, - 35720, - 35721, - 35722, - 35723, - 35724, - 35725, - 35726, - 35727, - 35728, - 35729, - 35730, - 35731, - 35732, - 35733, - 35734, - 35735, - 35736, - 35737, - 35738, - 35739, - 35740, - 35741, - 35742, - 35743, - 35744, - 35745, - 35746, - 35747, - 35748, - 35749, - 35750, - 35751, - 35752, - 35753, - 35754, - 35755, - 35756, - 35757, - 35758, - 35759, - 35760, - 35761, - 35762, - 35763, - 35764, - 35765, - 35766, - 35767, - 35768, - 35769, - 35770, - 35771, - 35772, - 35773, - 35774, - 35775, - 35776, - 35777, - 35778, - 35779, - 35780, - 35781, - 35782, - 35783, - 35784, - 35785, - 35786, - 35787, - 35788, - 35789, - 35790, - 35791, - 35792, - 35793, - 35794, - 35795, - 35796, - 35797, - 35798, - 35799, - 35800, - 35801, - 35802, - 35803, - 35804, - 35805, - 35806, - 35807, - 35808, - 35809, - 35810, - 35811, - 35812, - 35813, - 35814, - 35815, - 35816, - 35817, - 35818, - 35819, - 35820, - 35821, - 35822, - 35823, - 35824, - 35825, - 35826, - 35827, - 35828, - 35829, - 35830, - 35831, - 35832, - 35833, - 35834, - 35835, - 35836, - 35837, - 35838, - 35839, - 35840, - 35841, - 35842, - 35843, - 35844, - 35845, - 35846, - 35847, - 35848, - 35849, - 35850, - 35851, - 35852, - 35853, - 35854, - 35855, - 35856, - 35857, - 35858, - 35859, - 35860, - 35861, - 35862, - 35863, - 35864, - 35865, - 35866, - 35867, - 35868, - 35869, - 35870, - 35871, - 35872, - 35873, - 35874, - 35875, - 35876, - 35877, - 35878, - 35879, - 35880, - 35881, - 35882, - 35883, - 35884, - 35885, - 35886, - 35887, - 35888, - 35889, - 35890, - 35891, - 35892, - 35893, - 35894, - 35895, - 35896, - 35897, - 35898, - 35899, - 35900, - 35901, - 35902, - 35903, - 35904, - 35905, - 35906, - 35907, - 35908, - 35909, - 35910, - 35911, - 35912, - 35913, - 35914, - 35915, - 35916, - 35917, - 35918, - 35919, - 35920, - 35921, - 35922, - 35923, - 35924, - 35925, - 35926, - 35927, - 35928, - 35929, - 35930, - 35931, - 35932, - 35933, - 35934, - 35935, - 35936, - 35937, - 35938, - 35939, - 35940, - 35941, - 35942, - 35943, - 35944, - 35945, - 35946, - 35947, - 35948, - 35949, - 35950, - 35951, - 35952, - 35953, - 35954, - 35955, - 35956, - 35957, - 35958, - 35959, - 35960, - 35961, - 35962, - 35963, - 35964, - 35965, - 35966, - 35967, - 35968, - 35969, - 35970, - 35971, - 35972, - 35973, - 35974, - 35975, - 35976, - 35977, - 35978, - 35979, - 35980, - 35981, - 35982, - 35983, - 35984, - 35985, - 35986, - 35987, - 35988, - 35989, - 35990, - 35991, - 35992, - 35993, - 35994, - 35995, - 35996, - 35997, - 35998, - 35999, - 36000, - 36001, - 36002, - 36003, - 36004, - 36005, - 36006, - 36007, - 36008, - 36009, - 36010, - 36011, - 36012, - 36013, - 36014, - 36015, - 36016, - 36017, - 36018, - 36019, - 36020, - 36021, - 36022, - 36023, - 36024, - 36025, - 36026, - 36027, - 36028, - 36029, - 36030, - 36031, - 36032, - 36033, - 36034, - 36035, - 36036, - 36037, - 36038, - 36039, - 36040, - 36041, - 36042, - 36043, - 36044, - 36045, - 36046, - 36047, - 36048, - 36049, - 36050, - 36051, - 36052, - 36053, - 36054, - 36055, - 36056, - 36057, - 36058, - 36059, - 36060, - 36061, - 36062, - 36063, - 36064, - 36065, - 36066, - 36067, - 36068, - 36069, - 36070, - 36071, - 36072, - 36073, - 36074, - 36075, - 36076, - 36077, - 36078, - 36079, - 36080, - 36081, - 36082, - 36083, - 36084, - 36085, - 36086, - 36087, - 36088, - 36089, - 36090, - 36091, - 36092, - 36093, - 36094, - 36095, - 36096, - 36097, - 36098, - 36099, - 36100, - 36101, - 36102, - 36103, - 36104, - 36105, - 36106, - 36107, - 36108, - 36109, - 36110, - 36111, - 36112, - 36113, - 36114, - 36115, - 36116, - 36117, - 36118, - 36119, - 36120, - 36121, - 36122, - 36123, - 36124, - 36125, - 36126, - 36127, - 36128, - 36129, - 36130, - 36131, - 36132, - 36133, - 36134, - 36135, - 36136, - 36137, - 36138, - 36139, - 36140, - 36141, - 36142, - 36143, - 36144, - 36145, - 36146, - 36147, - 36148, - 36149, - 36150, - 36151, - 36152, - 36153, - 36154, - 36155, - 36156, - 36157, - 36158, - 36159, - 36160, - 36161, - 36162, - 36163, - 36164, - 36165, - 36166, - 36167, - 36168, - 36169, - 36170, - 36171, - 36172, - 36173, - 36174, - 36175, - 36176, - 36177, - 36178, - 36179, - 36180, - 36181, - 36182, - 36183, - 36184, - 36185, - 36186, - 36187, - 36188, - 36189, - 36190, - 36191, - 36192, - 36193, - 36194, - 36195, - 36196, - 36197, - 36198, - 36199, - 36200, - 36201, - 36202, - 36203, - 36204, - 36205, - 36206, - 36207, - 36208, - 36209, - 36210, - 36211, - 36212, - 36213, - 36214, - 36215, - 36216, - 36217, - 36218, - 36219, - 36220, - 36221, - 36222, - 36223, - 36224, - 36225, - 36226, - 36227, - 36228, - 36229, - 36230, - 36231, - 36232, - 36233, - 36234, - 36235, - 36236, - 36237, - 36238, - 36239, - 36240, - 36241, - 36242, - 36243, - 36244, - 36245, - 36246, - 36247, - 36248, - 36249, - 36250, - 36251, - 36252, - 36253, - 36254, - 36255, - 36256, - 36257, - 36258, - 36259, - 36260, - 36261, - 36262, - 36263, - 36264, - 36265, - 36266, - 36267, - 36268, - 36269, - 36270, - 36271, - 36272, - 36273, - 36274, - 36275, - 36276, - 36277, - 36278, - 36279, - 36280, - 36281, - 36282, - 36283, - 36284, - 36285, - 36286, - 36287, - 36288, - 36289, - 36290, - 36291, - 36292, - 36293, - 36294, - 36295, - 36296, - 36297, - 36298, - 36299, - 36300, - 36301, - 36302, - 36303, - 36304, - 36305, - 36306, - 36307, - 36308, - 36309, - 36310, - 36311, - 36312, - 36313, - 36314, - 36315, - 36316, - 36317, - 36318, - 36319, - 36320, - 36321, - 36322, - 36323, - 36324, - 36325, - 36326, - 36327, - 36328, - 36329, - 36330, - 36331, - 36332, - 36333, - 36334, - 36335, - 36336, - 36337, - 36338, - 36339, - 36340, - 36341, - 36342, - 36343, - 36344, - 36345, - 36346, - 36347, - 36348, - 36349, - 36350, - 36351, - 36352, - 36353, - 36354, - 36355, - 36356, - 36357, - 36358, - 36359, - 36360, - 36361, - 36362, - 36363, - 36364, - 36365, - 36366, - 36367, - 36368, - 36369, - 36370, - 36371, - 36372, - 36373, - 36374, - 36375, - 36376, - 36377, - 36378, - 36379, - 36380, - 36381, - 36382, - 36383, - 36384, - 36385, - 36386, - 36387, - 36388, - 36389, - 36390, - 36391, - 36392, - 36393, - 36394, - 36395, - 36396, - 36397, - 36398, - 36399, - 36400, - 36401, - 36402, - 36403, - 36404, - 36405, - 36406, - 36407, - 36408, - 36409, - 36410, - 36411, - 36412, - 36413, - 36414, - 36415, - 36416, - 36417, - 36418, - 36419, - 36420, - 36421, - 36422, - 36423, - 36424, - 36425, - 36426, - 36427, - 36428, - 36429, - 36430, - 36431, - 36432, - 36433, - 36434, - 36435, - 36436, - 36437, - 36438, - 36439, - 36440, - 36441, - 36442, - 36443, - 36444, - 36445, - 36446, - 36447, - 36448, - 36449, - 36450, - 36451, - 36452, - 36453, - 36454, - 36455, - 36456, - 36457, - 36458, - 36459, - 36460, - 36461, - 36462, - 36463, - 36464, - 36465, - 36466, - 36467, - 36468, - 36469, - 36470, - 36471, - 36472, - 36473, - 36474, - 36475, - 36476, - 36477, - 36478, - 36479, - 36480, - 36481, - 36482, - 36483, - 36484, - 36485, - 36486, - 36487, - 36488, - 36489, - 36490, - 36491, - 36492, - 36493, - 36494, - 36495, - 36496, - 36497, - 36498, - 36499, - 36500, - 36501, - 36502, - 36503, - 36504, - 36505, - 36506, - 36507, - 36508, - 36509, - 36510, - 36511, - 36512, - 36513, - 36514, - 36515, - 36516, - 36517, - 36518, - 36519, - 36520, - 36521, - 36522, - 36523, - 36524, - 36525, - 36526, - 36527, - 36528, - 36529, - 36530, - 36531, - 36532, - 36533, - 36534, - 36535, - 36536, - 36537, - 36538, - 36539, - 36540, - 36541, - 36542, - 36543, - 36544, - 36545, - 36546, - 36547, - 36548, - 36549, - 36550, - 36551, - 36552, - 36553, - 36554, - 36555, - 36556, - 36557, - 36558, - 36559, - 36560, - 36561, - 36562, - 36563, - 36564, - 36565, - 36566, - 36567, - 36568, - 36569, - 36570, - 36571, - 36572, - 36573, - 36574, - 36575, - 36576, - 36577, - 36578, - 36579, - 36580, - 36581, - 36582, - 36583, - 36584, - 36585, - 36586, - 36587, - 36588, - 36589, - 36590, - 36591, - 36592, - 36593, - 36594, - 36595, - 36596, - 36597, - 36598, - 36599, - 36600, - 36601, - 36602, - 36603, - 36604, - 36605, - 36606, - 36607, - 36608, - 36609, - 36610, - 36611, - 36612, - 36613, - 36614, - 36615, - 36616, - 36617, - 36618, - 36619, - 36620, - 36621, - 36622, - 36623, - 36624, - 36625, - 36626, - 36627, - 36628, - 36629, - 36630, - 36631, - 36632, - 36633, - 36634, - 36635, - 36636, - 36637, - 36638, - 36639, - 36640, - 36641, - 36642, - 36643, - 36644, - 36645, - 36646, - 36647, - 36648, - 36649, - 36650, - 36651, - 36652, - 36653, - 36654, - 36655, - 36656, - 36657, - 36658, - 36659, - 36660, - 36661, - 36662, - 36663, - 36664, - 36665, - 36666, - 36667, - 36668, - 36669, - 36670, - 36671, - 36672, - 36673, - 36674, - 36675, - 36676, - 36677, - 36678, - 36679, - 36680, - 36681, - 36682, - 36683, - 36684, - 36685, - 36686, - 36687, - 36688, - 36689, - 36690, - 36691, - 36692, - 36693, - 36694, - 36695, - 36696, - 36697, - 36698, - 36699, - 36700, - 36701, - 36702, - 36703, - 36704, - 36705, - 36706, - 36707, - 36708, - 36709, - 36710, - 36711, - 36712, - 36713, - 36714, - 36715, - 36716, - 36717, - 36718, - 36719, - 36720, - 36721, - 36722, - 36723, - 36724, - 36725, - 36726, - 36727, - 36728, - 36729, - 36730, - 36731, - 36732, - 36733, - 36734, - 36735, - 36736, - 36737, - 36738, - 36739, - 36740, - 36741, - 36742, - 36743, - 36744, - 36745, - 36746, - 36747, - 36748, - 36749, - 36750, - 36751, - 36752, - 36753, - 36754, - 36755, - 36756, - 36757, - 36758, - 36759, - 36760, - 36761, - 36762, - 36763, - 36764, - 36765, - 36766, - 36767, - 36768, - 36769, - 36770, - 36771, - 36772, - 36773, - 36774, - 36775, - 36776, - 36777, - 36778, - 36779, - 36780, - 36781, - 36782, - 36783, - 36784, - 36785, - 36786, - 36787, - 36788, - 36789, - 36790, - 36791, - 36792, - 36793, - 36794, - 36795, - 36796, - 36797, - 36798, - 36799, - 36800, - 36801, - 36802, - 36803, - 36804, - 36805, - 36806, - 36807, - 36808, - 36809, - 36810, - 36811, - 36812, - 36813, - 36814, - 36815, - 36816, - 36817, - 36818, - 36819, - 36820, - 36821, - 36822, - 36823, - 36824, - 36825, - 36826, - 36827, - 36828, - 36829, - 36830, - 36831, - 36832, - 36833, - 36834, - 36835, - 36836, - 36837, - 36838, - 36839, - 36840, - 36841, - 36842, - 36843, - 36844, - 36845, - 36846, - 36847, - 36848, - 36849, - 36850, - 36851, - 36852, - 36853, - 36854, - 36855, - 36856, - 36857, - 36858, - 36859, - 36860, - 36861, - 36862, - 36863, - 36864, - 36865, - 36866, - 36867, - 36868, - 36869, - 36870, - 36871, - 36872, - 36873, - 36874, - 36875, - 36876, - 36877, - 36878, - 36879, - 36880, - 36881, - 36882, - 36883, - 36884, - 36885, - 36886, - 36887, - 36888, - 36889, - 36890, - 36891, - 36892, - 36893, - 36894, - 36895, - 36896, - 36897, - 36898, - 36899, - 36900, - 36901, - 36902, - 36903, - 36904, - 36905, - 36906, - 36907, - 36908, - 36909, - 36910, - 36911, - 36912, - 36913, - 36914, - 36915, - 36916, - 36917, - 36918, - 36919, - 36920, - 36921, - 36922, - 36923, - 36924, - 36925, - 36926, - 36927, - 36928, - 36929, - 36930, - 36931, - 36932, - 36933, - 36934, - 36935, - 36936, - 36937, - 36938, - 36939, - 36940, - 36941, - 36942, - 36943, - 36944, - 36945, - 36946, - 36947, - 36948, - 36949, - 36950, - 36951, - 36952, - 36953, - 36954, - 36955, - 36956, - 36957, - 36958, - 36959, - 36960, - 36961, - 36962, - 36963, - 36964, - 36965, - 36966, - 36967, - 36968, - 36969, - 36970, - 36971, - 36972, - 36973, - 36974, - 36975, - 36976, - 36977, - 36978, - 36979, - 36980, - 36981, - 36982, - 36983, - 36984, - 36985, - 36986, - 36987, - 36988, - 36989, - 36990, - 36991, - 36992, - 36993, - 36994, - 36995, - 36996, - 36997, - 36998, - 36999, - 37000, - 37001, - 37002, - 37003, - 37004, - 37005, - 37006, - 37007, - 37008, - 37009, - 37010, - 37011, - 37012, - 37013, - 37014, - 37015, - 37016, - 37017, - 37018, - 37019, - 37020, - 37021, - 37022, - 37023, - 37024, - 37025, - 37026, - 37027, - 37028, - 37029, - 37030, - 37031, - 37032, - 37033, - 37034, - 37035, - 37036, - 37037, - 37038, - 37039, - 37040, - 37041, - 37042, - 37043, - 37044, - 37045, - 37046, - 37047, - 37048, - 37049, - 37050, - 37051, - 37052, - 37053, - 37054, - 37055, - 37056, - 37057, - 37058, - 37059, - 37060, - 37061, - 37062, - 37063, - 37064, - 37065, - 37066, - 37067, - 37068, - 37069, - 37070, - 37071, - 37072, - 37073, - 37074, - 37075, - 37076, - 37077, - 37078, - 37079, - 37080, - 37081, - 37082, - 37083, - 37084, - 37085, - 37086, - 37087, - 37088, - 37089, - 37090, - 37091, - 37092, - 37093, - 37094, - 37095, - 37096, - 37097, - 37098, - 37099, - 37100, - 37101, - 37102, - 37103, - 37104, - 37105, - 37106, - 37107, - 37108, - 37109, - 37110, - 37111, - 37112, - 37113, - 37114, - 37115, - 37116, - 37117, - 37118, - 37119, - 37120, - 37121, - 37122, - 37123, - 37124, - 37125, - 37126, - 37127, - 37128, - 37129, - 37130, - 37131, - 37132, - 37133, - 37134, - 37135, - 37136, - 37137, - 37138, - 37139, - 37140, - 37141, - 37142, - 37143, - 37144, - 37145, - 37146, - 37147, - 37148, - 37149, - 37150, - 37151, - 37152, - 37153, - 37154, - 37155, - 37156, - 37157, - 37158, - 37159, - 37160, - 37161, - 37162, - 37163, - 37164, - 37165, - 37166, - 37167, - 37168, - 37169, - 37170, - 37171, - 37172, - 37173, - 37174, - 37175, - 37176, - 37177, - 37178, - 37179, - 37180, - 37181, - 37182, - 37183, - 37184, - 37185, - 37186, - 37187, - 37188, - 37189, - 37190, - 37191, - 37192, - 37193, - 37194, - 37195, - 37196, - 37197, - 37198, - 37199, - 37200, - 37201, - 37202, - 37203, - 37204, - 37205, - 37206, - 37207, - 37208, - 37209, - 37210, - 37211, - 37212, - 37213, - 37214, - 37215, - 37216, - 37217, - 37218, - 37219, - 37220, - 37221, - 37222, - 37223, - 37224, - 37225, - 37226, - 37227, - 37228, - 37229, - 37230, - 37231, - 37232, - 37233, - 37234, - 37235, - 37236, - 37237, - 37238, - 37239, - 37240, - 37241, - 37242, - 37243, - 37244, - 37245, - 37246, - 37247, - 37248, - 37249, - 37250, - 37251, - 37252, - 37253, - 37254, - 37255, - 37256, - 37257, - 37258, - 37259, - 37260, - 37261, - 37262, - 37263, - 37264, - 37265, - 37266, - 37267, - 37268, - 37269, - 37270, - 37271, - 37272, - 37273, - 37274, - 37275, - 37276, - 37277, - 37278, - 37279, - 37280, - 37281, - 37282, - 37283, - 37284, - 37285, - 37286, - 37287, - 37288, - 37289, - 37290, - 37291, - 37292, - 37293, - 37294, - 37295, - 37296, - 37297, - 37298, - 37299, - 37300, - 37301, - 37302, - 37303, - 37304, - 37305, - 37306, - 37307, - 37308, - 37309, - 37310, - 37311, - 37312, - 37313, - 37314, - 37315, - 37316, - 37317, - 37318, - 37319, - 37320, - 37321, - 37322, - 37323, - 37324, - 37325, - 37326, - 37327, - 37328, - 37329, - 37330, - 37331, - 37332, - 37333, - 37334, - 37335, - 37336, - 37337, - 37338, - 37339, - 37340, - 37341, - 37342, - 37343, - 37344, - 37345, - 37346, - 37347, - 37348, - 37349, - 37350, - 37351, - 37352, - 37353, - 37354, - 37355, - 37356, - 37357, - 37358, - 37359, - 37360, - 37361, - 37362, - 37363, - 37364, - 37365, - 37366, - 37367, - 37368, - 37369, - 37370, - 37371, - 37372, - 37373, - 37374, - 37375, - 37376, - 37377, - 37378, - 37379, - 37380, - 37381, - 37382, - 37383, - 37384, - 37385, - 37386, - 37387, - 37388, - 37389, - 37390, - 37391, - 37392, - 37393, - 37394, - 37395, - 37396, - 37397, - 37398, - 37399, - 37400, - 37401, - 37402, - 37403, - 37404, - 37405, - 37406, - 37407, - 37408, - 37409, - 37410, - 37411, - 37412, - 37413, - 37414, - 37415, - 37416, - 37417, - 37418, - 37419, - 37420, - 37421, - 37422, - 37423, - 37424, - 37425, - 37426, - 37427, - 37428, - 37429, - 37430, - 37431, - 37432, - 37433, - 37434, - 37435, - 37436, - 37437, - 37438, - 37439, - 37440, - 37441, - 37442, - 37443, - 37444, - 37445, - 37446, - 37447, - 37448, - 37449, - 37450, - 37451, - 37452, - 37453, - 37454, - 37455, - 37456, - 37457, - 37458, - 37459, - 37460, - 37461, - 37462, - 37463, - 37464, - 37465, - 37466, - 37467, - 37468, - 37469, - 37470, - 37471, - 37472, - 37473, - 37474, - 37475, - 37476, - 37477, - 37478, - 37479, - 37480, - 37481, - 37482, - 37483, - 37484, - 37485, - 37486, - 37487, - 37488, - 37489, - 37490, - 37491, - 37492, - 37493, - 37494, - 37495, - 37496, - 37497, - 37498, - 37499, - 37500, - 37501, - 37502, - 37503, - 37504, - 37505, - 37506, - 37507, - 37508, - 37509, - 37510, - 37511, - 37512, - 37513, - 37514, - 37515, - 37516, - 37517, - 37518, - 37519, - 37520, - 37521, - 37522, - 37523, - 37524, - 37525, - 37526, - 37527, - 37528, - 37529, - 37530, - 37531, - 37532, - 37533, - 37534, - 37535, - 37536, - 37537, - 37538, - 37539, - 37540, - 37541, - 37542, - 37543, - 37544, - 37545, - 37546, - 37547, - 37548, - 37549, - 37550, - 37551, - 37552, - 37553, - 37554, - 37555, - 37556, - 37557, - 37558, - 37559, - 37560, - 37561, - 37562, - 37563, - 37564, - 37565, - 37566, - 37567, - 37568, - 37569, - 37570, - 37571, - 37572, - 37573, - 37574, - 37575, - 37576, - 37577, - 37578, - 37579, - 37580, - 37581, - 37582, - 37583, - 37584, - 37585, - 37586, - 37587, - 37588, - 37589, - 37590, - 37591, - 37592, - 37593, - 37594, - 37595, - 37596, - 37597, - 37598, - 37599, - 37600, - 37601, - 37602, - 37603, - 37604, - 37605, - 37606, - 37607, - 37608, - 37609, - 37610, - 37611, - 37612, - 37613, - 37614, - 37615, - 37616, - 37617, - 37618, - 37619, - 37620, - 37621, - 37622, - 37623, - 37624, - 37625, - 37626, - 37627, - 37628, - 37629, - 37630, - 37631, - 37632, - 37633, - 37634, - 37635, - 37636, - 37637, - 37638, - 37639, - 37640, - 37641, - 37642, - 37643, - 37644, - 37645, - 37646, - 37647, - 37648, - 37649, - 37650, - 37651, - 37652, - 37653, - 37654, - 37655, - 37656, - 37657, - 37658, - 37659, - 37660, - 37661, - 37662, - 37663, - 37664, - 37665, - 37666, - 37667, - 37668, - 37669, - 37670, - 37671, - 37672, - 37673, - 37674, - 37675, - 37676, - 37677, - 37678, - 37679, - 37680, - 37681, - 37682, - 37683, - 37684, - 37685, - 37686, - 37687, - 37688, - 37689, - 37690, - 37691, - 37692, - 37693, - 37694, - 37695, - 37696, - 37697, - 37698, - 37699, - 37700, - 37701, - 37702, - 37703, - 37704, - 37705, - 37706, - 37707, - 37708, - 37709, - 37710, - 37711, - 37712, - 37713, - 37714, - 37715, - 37716, - 37717, - 37718, - 37719, - 37720, - 37721, - 37722, - 37723, - 37724, - 37725, - 37726, - 37727, - 37728, - 37729, - 37730, - 37731, - 37732, - 37733, - 37734, - 37735, - 37736, - 37737, - 37738, - 37739, - 37740, - 37741, - 37742, - 37743, - 37744, - 37745, - 37746, - 37747, - 37748, - 37749, - 37750, - 37751, - 37752, - 37753, - 37754, - 37755, - 37756, - 37757, - 37758, - 37759, - 37760, - 37761, - 37762, - 37763, - 37764, - 37765, - 37766, - 37767, - 37768, - 37769, - 37770, - 37771, - 37772, - 37773, - 37774, - 37775, - 37776, - 37777, - 37778, - 37779, - 37780, - 37781, - 37782, - 37783, - 37784, - 37785, - 37786, - 37787, - 37788, - 37789, - 37790, - 37791, - 37792, - 37793, - 37794, - 37795, - 37796, - 37797, - 37798, - 37799, - 37800, - 37801, - 37802, - 37803, - 37804, - 37805, - 37806, - 37807, - 37808, - 37809, - 37810, - 37811, - 37812, - 37813, - 37814, - 37815, - 37816, - 37817, - 37818, - 37819, - 37820, - 37821, - 37822, - 37823, - 37824, - 37825, - 37826, - 37827, - 37828, - 37829, - 37830, - 37831, - 37832, - 37833, - 37834, - 37835, - 37836, - 37837, - 37838, - 37839, - 37840, - 37841, - 37842, - 37843, - 37844, - 37845, - 37846, - 37847, - 37848, - 37849, - 37850, - 37851, - 37852, - 37853, - 37854, - 37855, - 37856, - 37857, - 37858, - 37859, - 37860, - 37861, - 37862, - 37863, - 37864, - 37865, - 37866, - 37867, - 37868, - 37869, - 37870, - 37871, - 37872, - 37873, - 37874, - 37875, - 37876, - 37877, - 37878, - 37879, - 37880, - 37881, - 37882, - 37883, - 37884, - 37885, - 37886, - 37887, - 37888, - 37889, - 37890, - 37891, - 37892, - 37893, - 37894, - 37895, - 37896, - 37897, - 37898, - 37899, - 37900, - 37901, - 37902, - 37903, - 37904, - 37905, - 37906, - 37907, - 37908, - 37909, - 37910, - 37911, - 37912, - 37913, - 37914, - 37915, - 37916, - 37917, - 37918, - 37919, - 37920, - 37921, - 37922, - 37923, - 37924, - 37925, - 37926, - 37927, - 37928, - 37929, - 37930, - 37931, - 37932, - 37933, - 37934, - 37935, - 37936, - 37937, - 37938, - 37939, - 37940, - 37941, - 37942, - 37943, - 37944, - 37945, - 37946, - 37947, - 37948, - 37949, - 37950, - 37951, - 37952, - 37953, - 37954, - 37955, - 37956, - 37957, - 37958, - 37959, - 37960, - 37961, - 37962, - 37963, - 37964, - 37965, - 37966, - 37967, - 37968, - 37969, - 37970, - 37971, - 37972, - 37973, - 37974, - 37975, - 37976, - 37977, - 37978, - 37979, - 37980, - 37981, - 37982, - 37983, - 37984, - 37985, - 37986, - 37987, - 37988, - 37989, - 37990, - 37991, - 37992, - 37993, - 37994, - 37995, - 37996, - 37997, - 37998, - 37999, - 38000, - 38001, - 38002, - 38003, - 38004, - 38005, - 38006, - 38007, - 38008, - 38009, - 38010, - 38011, - 38012, - 38013, - 38014, - 38015, - 38016, - 38017, - 38018, - 38019, - 38020, - 38021, - 38022, - 38023, - 38024, - 38025, - 38026, - 38027, - 38028, - 38029, - 38030, - 38031, - 38032, - 38033, - 38034, - 38035, - 38036, - 38037, - 38038, - 38039, - 38040, - 38041, - 38042, - 38043, - 38044, - 38045, - 38046, - 38047, - 38048, - 38049, - 38050, - 38051, - 38052, - 38053, - 38054, - 38055, - 38056, - 38057, - 38058, - 38059, - 38060, - 38061, - 38062, - 38063, - 38064, - 38065, - 38066, - 38067, - 38068, - 38069, - 38070, - 38071, - 38072, - 38073, - 38074, - 38075, - 38076, - 38077, - 38078, - 38079, - 38080, - 38081, - 38082, - 38083, - 38084, - 38085, - 38086, - 38087, - 38088, - 38089, - 38090, - 38091, - 38092, - 38093, - 38094, - 38095, - 38096, - 38097, - 38098, - 38099, - 38100, - 38101, - 38102, - 38103, - 38104, - 38105, - 38106, - 38107, - 38108, - 38109, - 38110, - 38111, - 38112, - 38113, - 38114, - 38115, - 38116, - 38117, - 38118, - 38119, - 38120, - 38121, - 38122, - 38123, - 38124, - 38125, - 38126, - 38127, - 38128, - 38129, - 38130, - 38131, - 38132, - 38133, - 38134, - 38135, - 38136, - 38137, - 38138, - 38139, - 38140, - 38141, - 38142, - 38143, - 38144, - 38145, - 38146, - 38147, - 38148, - 38149, - 38150, - 38151, - 38152, - 38153, - 38154, - 38155, - 38156, - 38157, - 38158, - 38159, - 38160, - 38161, - 38162, - 38163, - 38164, - 38165, - 38166, - 38167, - 38168, - 38169, - 38170, - 38171, - 38172, - 38173, - 38174, - 38175, - 38176, - 38177, - 38178, - 38179, - 38180, - 38181, - 38182, - 38183, - 38184, - 38185, - 38186, - 38187, - 38188, - 38189, - 38190, - 38191, - 38192, - 38193, - 38194, - 38195, - 38196, - 38197, - 38198, - 38199, - 38200, - 38201, - 38202, - 38203, - 38204, - 38205, - 38206, - 38207, - 38208, - 38209, - 38210, - 38211, - 38212, - 38213, - 38214, - 38215, - 38216, - 38217, - 38218, - 38219, - 38220, - 38221, - 38222, - 38223, - 38224, - 38225, - 38226, - 38227, - 38228, - 38229, - 38230, - 38231, - 38232, - 38233, - 38234, - 38235, - 38236, - 38237, - 38238, - 38239, - 38240, - 38241, - 38242, - 38243, - 38244, - 38245, - 38246, - 38247, - 38248, - 38249, - 38250, - 38251, - 38252, - 38253, - 38254, - 38255, - 38256, - 38257, - 38258, - 38259, - 38260, - 38261, - 38262, - 38263, - 38264, - 38265, - 38266, - 38267, - 38268, - 38269, - 38270, - 38271, - 38272, - 38273, - 38274, - 38275, - 38276, - 38277, - 38278, - 38279, - 38280, - 38281, - 38282, - 38283, - 38284, - 38285, - 38286, - 38287, - 38288, - 38289, - 38290, - 38291, - 38292, - 38293, - 38294, - 38295, - 38296, - 38297, - 38298, - 38299, - 38300, - 38301, - 38302, - 38303, - 38304, - 38305, - 38306, - 38307, - 38308, - 38309, - 38310, - 38311, - 38312, - 38313, - 38314, - 38315, - 38316, - 38317, - 38318, - 38319, - 38320, - 38321, - 38322, - 38323, - 38324, - 38325, - 38326, - 38327, - 38328, - 38329, - 38330, - 38331, - 38332, - 38333, - 38334, - 38335, - 38336, - 38337, - 38338, - 38339, - 38340, - 38341, - 38342, - 38343, - 38344, - 38345, - 38346, - 38347, - 38348, - 38349, - 38350, - 38351, - 38352, - 38353, - 38354, - 38355, - 38356, - 38357, - 38358, - 38359, - 38360, - 38361, - 38362, - 38363, - 38364, - 38365, - 38366, - 38367, - 38368, - 38369, - 38370, - 38371, - 38372, - 38373, - 38374, - 38375, - 38376, - 38377, - 38378, - 38379, - 38380, - 38381, - 38382, - 38383, - 38384, - 38385, - 38386, - 38387, - 38388, - 38389, - 38390, - 38391, - 38392, - 38393, - 38394, - 38395, - 38396, - 38397, - 38398, - 38399, - 38400, - 38401, - 38402, - 38403, - 38404, - 38405, - 38406, - 38407, - 38408, - 38409, - 38410, - 38411, - 38412, - 38413, - 38414, - 38415, - 38416, - 38417, - 38418, - 38419, - 38420, - 38421, - 38422, - 38423, - 38424, - 38425, - 38426, - 38427, - 38428, - 38429, - 38430, - 38431, - 38432, - 38433, - 38434, - 38435, - 38436, - 38437, - 38438, - 38439, - 38440, - 38441, - 38442, - 38443, - 38444, - 38445, - 38446, - 38447, - 38448, - 38449, - 38450, - 38451, - 38452, - 38453, - 38454, - 38455, - 38456, - 38457, - 38458, - 38459, - 38460, - 38461, - 38462, - 38463, - 38464, - 38465, - 38466, - 38467, - 38468, - 38469, - 38470, - 38471, - 38472, - 38473, - 38474, - 38475, - 38476, - 38477, - 38478, - 38479, - 38480, - 38481, - 38482, - 38483, - 38484, - 38485, - 38486, - 38487, - 38488, - 38489, - 38490, - 38491, - 38492, - 38493, - 38494, - 38495, - 38496, - 38497, - 38498, - 38499, - 38500, - 38501, - 38502, - 38503, - 38504, - 38505, - 38506, - 38507, - 38508, - 38509, - 38510, - 38511, - 38512, - 38513, - 38514, - 38515, - 38516, - 38517, - 38518, - 38519, - 38520, - 38521, - 38522, - 38523, - 38524, - 38525, - 38526, - 38527, - 38528, - 38529, - 38530, - 38531, - 38532, - 38533, - 38534, - 38535, - 38536, - 38537, - 38538, - 38539, - 38540, - 38541, - 38542, - 38543, - 38544, - 38545, - 38546, - 38547, - 38548, - 38549, - 38550, - 38551, - 38552, - 38553, - 38554, - 38555, - 38556, - 38557, - 38558, - 38559, - 38560, - 38561, - 38562, - 38563, - 38564, - 38565, - 38566, - 38567, - 38568, - 38569, - 38570, - 38571, - 38572, - 38573, - 38574, - 38575, - 38576, - 38577, - 38578, - 38579, - 38580, - 38581, - 38582, - 38583, - 38584, - 38585, - 38586, - 38587, - 38588, - 38589, - 38590, - 38591, - 38592, - 38593, - 38594, - 38595, - 38596, - 38597, - 38598, - 38599, - 38600, - 38601, - 38602, - 38603, - 38604, - 38605, - 38606, - 38607, - 38608, - 38609, - 38610, - 38611, - 38612, - 38613, - 38614, - 38615, - 38616, - 38617, - 38618, - 38619, - 38620, - 38621, - 38622, - 38623, - 38624, - 38625, - 38626, - 38627, - 38628, - 38629, - 38630, - 38631, - 38632, - 38633, - 38634, - 38635, - 38636, - 38637, - 38638, - 38639, - 38640, - 38641, - 38642, - 38643, - 38644, - 38645, - 38646, - 38647, - 38648, - 38649, - 38650, - 38651, - 38652, - 38653, - 38654, - 38655, - 38656, - 38657, - 38658, - 38659, - 38660, - 38661, - 38662, - 38663, - 38664, - 38665, - 38666, - 38667, - 38668, - 38669, - 38670, - 38671, - 38672, - 38673, - 38674, - 38675, - 38676, - 38677, - 38678, - 38679, - 38680, - 38681, - 38682, - 38683, - 38684, - 38685, - 38686, - 38687, - 38688, - 38689, - 38690, - 38691, - 38692, - 38693, - 38694, - 38695, - 38696, - 38697, - 38698, - 38699, - 38700, - 38701, - 38702, - 38703, - 38704, - 38705, - 38706, - 38707, - 38708, - 38709, - 38710, - 38711, - 38712, - 38713, - 38714, - 38715, - 38716, - 38717, - 38718, - 38719, - 38720, - 38721, - 38722, - 38723, - 38724, - 38725, - 38726, - 38727, - 38728, - 38729, - 38730, - 38731, - 38732, - 38733, - 38734, - 38735, - 38736, - 38737, - 38738, - 38739, - 38740, - 38741, - 38742, - 38743, - 38744, - 38745, - 38746, - 38747, - 38748, - 38749, - 38750, - 38751, - 38752, - 38753, - 38754, - 38755, - 38756, - 38757, - 38758, - 38759, - 38760, - 38761, - 38762, - 38763, - 38764, - 38765, - 38766, - 38767, - 38768, - 38769, - 38770, - 38771, - 38772, - 38773, - 38774, - 38775, - 38776, - 38777, - 38778, - 38779, - 38780, - 38781, - 38782, - 38783, - 38784, - 38785, - 38786, - 38787, - 38788, - 38789, - 38790, - 38791, - 38792, - 38793, - 38794, - 38795, - 38796, - 38797, - 38798, - 38799, - 38800, - 38801, - 38802, - 38803, - 38804, - 38805, - 38806, - 38807, - 38808, - 38809, - 38810, - 38811, - 38812, - 38813, - 38814, - 38815, - 38816, - 38817, - 38818, - 38819, - 38820, - 38821, - 38822, - 38823, - 38824, - 38825, - 38826, - 38827, - 38828, - 38829, - 38830, - 38831, - 38832, - 38833, - 38834, - 38835, - 38836, - 38837, - 38838, - 38839, - 38840, - 38841, - 38842, - 38843, - 38844, - 38845, - 38846, - 38847, - 38848, - 38849, - 38850, - 38851, - 38852, - 38853, - 38854, - 38855, - 38856, - 38857, - 38858, - 38859, - 38860, - 38861, - 38862, - 38863, - 38864, - 38865, - 38866, - 38867, - 38868, - 38869, - 38870, - 38871, - 38872, - 38873, - 38874, - 38875, - 38876, - 38877, - 38878, - 38879, - 38880, - 38881, - 38882, - 38883, - 38884, - 38885, - 38886, - 38887, - 38888, - 38889, - 38890, - 38891, - 38892, - 38893, - 38894, - 38895, - 38896, - 38897, - 38898, - 38899, - 38900, - 38901, - 38902, - 38903, - 38904, - 38905, - 38906, - 38907, - 38908, - 38909, - 38910, - 38911, - 38912, - 38913, - 38914, - 38915, - 38916, - 38917, - 38918, - 38919, - 38920, - 38921, - 38922, - 38923, - 38924, - 38925, - 38926, - 38927, - 38928, - 38929, - 38930, - 38931, - 38932, - 38933, - 38934, - 38935, - 38936, - 38937, - 38938, - 38939, - 38940, - 38941, - 38942, - 38943, - 38944, - 38945, - 38946, - 38947, - 38948, - 38949, - 38950, - 38951, - 38952, - 38953, - 38954, - 38955, - 38956, - 38957, - 38958, - 38959, - 38960, - 38961, - 38962, - 38963, - 38964, - 38965, - 38966, - 38967, - 38968, - 38969, - 38970, - 38971, - 38972, - 38973, - 38974, - 38975, - 38976, - 38977, - 38978, - 38979, - 38980, - 38981, - 38982, - 38983, - 38984, - 38985, - 38986, - 38987, - 38988, - 38989, - 38990, - 38991, - 38992, - 38993, - 38994, - 38995, - 38996, - 38997, - 38998, - 38999, - 39000, - 39001, - 39002, - 39003, - 39004, - 39005, - 39006, - 39007, - 39008, - 39009, - 39010, - 39011, - 39012, - 39013, - 39014, - 39015, - 39016, - 39017, - 39018, - 39019, - 39020, - 39021, - 39022, - 39023, - 39024, - 39025, - 39026, - 39027, - 39028, - 39029, - 39030, - 39031, - 39032, - 39033, - 39034, - 39035, - 39036, - 39037, - 39038, - 39039, - 39040, - 39041, - 39042, - 39043, - 39044, - 39045, - 39046, - 39047, - 39048, - 39049, - 39050, - 39051, - 39052, - 39053, - 39054, - 39055, - 39056, - 39057, - 39058, - 39059, - 39060, - 39061, - 39062, - 39063, - 39064, - 39065, - 39066, - 39067, - 39068, - 39069, - 39070, - 39071, - 39072, - 39073, - 39074, - 39075, - 39076, - 39077, - 39078, - 39079, - 39080, - 39081, - 39082, - 39083, - 39084, - 39085, - 39086, - 39087, - 39088, - 39089, - 39090, - 39091, - 39092, - 39093, - 39094, - 39095, - 39096, - 39097, - 39098, - 39099, - 39100, - 39101, - 39102, - 39103, - 39104, - 39105, - 39106, - 39107, - 39108, - 39109, - 39110, - 39111, - 39112, - 39113, - 39114, - 39115, - 39116, - 39117, - 39118, - 39119, - 39120, - 39121, - 39122, - 39123, - 39124, - 39125, - 39126, - 39127, - 39128, - 39129, - 39130, - 39131, - 39132, - 39133, - 39134, - 39135, - 39136, - 39137, - 39138, - 39139, - 39140, - 39141, - 39142, - 39143, - 39144, - 39145, - 39146, - 39147, - 39148, - 39149, - 39150, - 39151, - 39152, - 39153, - 39154, - 39155, - 39156, - 39157, - 39158, - 39159, - 39160, - 39161, - 39162, - 39163, - 39164, - 39165, - 39166, - 39167, - 39168, - 39169, - 39170, - 39171, - 39172, - 39173, - 39174, - 39175, - 39176, - 39177, - 39178, - 39179, - 39180, - 39181, - 39182, - 39183, - 39184, - 39185, - 39186, - 39187, - 39188, - 39189, - 39190, - 39191, - 39192, - 39193, - 39194, - 39195, - 39196, - 39197, - 39198, - 39199, - 39200, - 39201, - 39202, - 39203, - 39204, - 39205, - 39206, - 39207, - 39208, - 39209, - 39210, - 39211, - 39212, - 39213, - 39214, - 39215, - 39216, - 39217, - 39218, - 39219, - 39220, - 39221, - 39222, - 39223, - 39224, - 39225, - 39226, - 39227, - 39228, - 39229, - 39230, - 39231, - 39232, - 39233, - 39234, - 39235, - 39236, - 39237, - 39238, - 39239, - 39240, - 39241, - 39242, - 39243, - 39244, - 39245, - 39246, - 39247, - 39248, - 39249, - 39250, - 39251, - 39252, - 39253, - 39254, - 39255, - 39256, - 39257, - 39258, - 39259, - 39260, - 39261, - 39262, - 39263, - 39264, - 39265, - 39266, - 39267, - 39268, - 39269, - 39270, - 39271, - 39272, - 39273, - 39274, - 39275, - 39276, - 39277, - 39278, - 39279, - 39280, - 39281, - 39282, - 39283, - 39284, - 39285, - 39286, - 39287, - 39288, - 39289, - 39290, - 39291, - 39292, - 39293, - 39294, - 39295, - 39296, - 39297, - 39298, - 39299, - 39300, - 39301, - 39302, - 39303, - 39304, - 39305, - 39306, - 39307, - 39308, - 39309, - 39310, - 39311, - 39312, - 39313, - 39314, - 39315, - 39316, - 39317, - 39318, - 39319, - 39320, - 39321, - 39322, - 39323, - 39324, - 39325, - 39326, - 39327, - 39328, - 39329, - 39330, - 39331, - 39332, - 39333, - 39334, - 39335, - 39336, - 39337, - 39338, - 39339, - 39340, - 39341, - 39342, - 39343, - 39344, - 39345, - 39346, - 39347, - 39348, - 39349, - 39350, - 39351, - 39352, - 39353, - 39354, - 39355, - 39356, - 39357, - 39358, - 39359, - 39360, - 39361, - 39362, - 39363, - 39364, - 39365, - 39366, - 39367, - 39368, - 39369, - 39370, - 39371, - 39372, - 39373, - 39374, - 39375, - 39376, - 39377, - 39378, - 39379, - 39380, - 39381, - 39382, - 39383, - 39384, - 39385, - 39386, - 39387, - 39388, - 39389, - 39390, - 39391, - 39392, - 39393, - 39394, - 39395, - 39396, - 39397, - 39398, - 39399, - 39400, - 39401, - 39402, - 39403, - 39404, - 39405, - 39406, - 39407, - 39408, - 39409, - 39410, - 39411, - 39412, - 39413, - 39414, - 39415, - 39416, - 39417, - 39418, - 39419, - 39420, - 39421, - 39422, - 39423, - 39424, - 39425, - 39426, - 39427, - 39428, - 39429, - 39430, - 39431, - 39432, - 39433, - 39434, - 39435, - 39436, - 39437, - 39438, - 39439, - 39440, - 39441, - 39442, - 39443, - 39444, - 39445, - 39446, - 39447, - 39448, - 39449, - 39450, - 39451, - 39452, - 39453, - 39454, - 39455, - 39456, - 39457, - 39458, - 39459, - 39460, - 39461, - 39462, - 39463, - 39464, - 39465, - 39466, - 39467, - 39468, - 39469, - 39470, - 39471, - 39472, - 39473, - 39474, - 39475, - 39476, - 39477, - 39478, - 39479, - 39480, - 39481, - 39482, - 39483, - 39484, - 39485, - 39486, - 39487, - 39488, - 39489, - 39490, - 39491, - 39492, - 39493, - 39494, - 39495, - 39496, - 39497, - 39498, - 39499, - 39500, - 39501, - 39502, - 39503, - 39504, - 39505, - 39506, - 39507, - 39508, - 39509, - 39510, - 39511, - 39512, - 39513, - 39514, - 39515, - 39516, - 39517, - 39518, - 39519, - 39520, - 39521, - 39522, - 39523, - 39524, - 39525, - 39526, - 39527, - 39528, - 39529, - 39530, - 39531, - 39532, - 39533, - 39534, - 39535, - 39536, - 39537, - 39538, - 39539, - 39540, - 39541, - 39542, - 39543, - 39544, - 39545, - 39546, - 39547, - 39548, - 39549, - 39550, - 39551, - 39552, - 39553, - 39554, - 39555, - 39556, - 39557, - 39558, - 39559, - 39560, - 39561, - 39562, - 39563, - 39564, - 39565, - 39566, - 39567, - 39568, - 39569, - 39570, - 39571, - 39572, - 39573, - 39574, - 39575, - 39576, - 39577, - 39578, - 39579, - 39580, - 39581, - 39582, - 39583, - 39584, - 39585, - 39586, - 39587, - 39588, - 39589, - 39590, - 39591, - 39592, - 39593, - 39594, - 39595, - 39596, - 39597, - 39598, - 39599, - 39600, - 39601, - 39602, - 39603, - 39604, - 39605, - 39606, - 39607, - 39608, - 39609, - 39610, - 39611, - 39612, - 39613, - 39614, - 39615, - 39616, - 39617, - 39618, - 39619, - 39620, - 39621, - 39622, - 39623, - 39624, - 39625, - 39626, - 39627, - 39628, - 39629, - 39630, - 39631, - 39632, - 39633, - 39634, - 39635, - 39636, - 39637, - 39638, - 39639, - 39640, - 39641, - 39642, - 39643, - 39644, - 39645, - 39646, - 39647, - 39648, - 39649, - 39650, - 39651, - 39652, - 39653, - 39654, - 39655, - 39656, - 39657, - 39658, - 39659, - 39660, - 39661, - 39662, - 39663, - 39664, - 39665, - 39666, - 39667, - 39668, - 39669, - 39670, - 39671, - 39672, - 39673, - 39674, - 39675, - 39676, - 39677, - 39678, - 39679, - 39680, - 39681, - 39682, - 39683, - 39684, - 39685, - 39686, - 39687, - 39688, - 39689, - 39690, - 39691, - 39692, - 39693, - 39694, - 39695, - 39696, - 39697, - 39698, - 39699, - 39700, - 39701, - 39702, - 39703, - 39704, - 39705, - 39706, - 39707, - 39708, - 39709, - 39710, - 39711, - 39712, - 39713, - 39714, - 39715, - 39716, - 39717, - 39718, - 39719, - 39720, - 39721, - 39722, - 39723, - 39724, - 39725, - 39726, - 39727, - 39728, - 39729, - 39730, - 39731, - 39732, - 39733, - 39734, - 39735, - 39736, - 39737, - 39738, - 39739, - 39740, - 39741, - 39742, - 39743, - 39744, - 39745, - 39746, - 39747, - 39748, - 39749, - 39750, - 39751, - 39752, - 39753, - 39754, - 39755, - 39756, - 39757, - 39758, - 39759, - 39760, - 39761, - 39762, - 39763, - 39764, - 39765, - 39766, - 39767, - 39768, - 39769, - 39770, - 39771, - 39772, - 39773, - 39774, - 39775, - 39776, - 39777, - 39778, - 39779, - 39780, - 39781, - 39782, - 39783, - 39784, - 39785, - 39786, - 39787, - 39788, - 39789, - 39790, - 39791, - 39792, - 39793, - 39794, - 39795, - 39796, - 39797, - 39798, - 39799, - 39800, - 39801, - 39802, - 39803, - 39804, - 39805, - 39806, - 39807, - 39808, - 39809, - 39810, - 39811, - 39812, - 39813, - 39814, - 39815, - 39816, - 39817, - 39818, - 39819, - 39820, - 39821, - 39822, - 39823, - 39824, - 39825, - 39826, - 39827, - 39828, - 39829, - 39830, - 39831, - 39832, - 39833, - 39834, - 39835, - 39836, - 39837, - 39838, - 39839, - 39840, - 39841, - 39842, - 39843, - 39844, - 39845, - 39846, - 39847, - 39848, - 39849, - 39850, - 39851, - 39852, - 39853, - 39854, - 39855, - 39856, - 39857, - 39858, - 39859, - 39860, - 39861, - 39862, - 39863, - 39864, - 39865, - 39866, - 39867, - 39868, - 39869, - 39870, - 39871, - 39872, - 39873, - 39874, - 39875, - 39876, - 39877, - 39878, - 39879, - 39880, - 39881, - 39882, - 39883, - 39884, - 39885, - 39886, - 39887, - 39888, - 39889, - 39890, - 39891, - 39892, - 39893, - 39894, - 39895, - 39896, - 39897, - 39898, - 39899, - 39900, - 39901, - 39902, - 39903, - 39904, - 39905, - 39906, - 39907, - 39908, - 39909, - 39910, - 39911, - 39912, - 39913, - 39914, - 39915, - 39916, - 39917, - 39918, - 39919, - 39920, - 39921, - 39922, - 39923, - 39924, - 39925, - 39926, - 39927, - 39928, - 39929, - 39930, - 39931, - 39932, - 39933, - 39934, - 39935, - 39936, - 39937, - 39938, - 39939, - 39940, - 39941, - 39942, - 39943, - 39944, - 39945, - 39946, - 39947, - 39948, - 39949, - 39950, - 39951, - 39952, - 39953, - 39954, - 39955, - 39956, - 39957, - 39958, - 39959, - 39960, - 39961, - 39962, - 39963, - 39964, - 39965, - 39966, - 39967, - 39968, - 39969, - 39970, - 39971, - 39972, - 39973, - 39974, - 39975, - 39976, - 39977, - 39978, - 39979, - 39980, - 39981, - 39982, - 39983, - 39984, - 39985, - 39986, - 39987, - 39988, - 39989, - 39990, - 39991, - 39992, - 39993, - 39994, - 39995, - 39996, - 39997, - 39998, - 39999, - 40000, - 40001, - 40002, - 40003, - 40004, - 40005, - 40006, - 40007, - 40008, - 40009, - 40010, - 40011, - 40012, - 40013, - 40014, - 40015, - 40016, - 40017, - 40018, - 40019, - 40020, - 40021, - 40022, - 40023, - 40024, - 40025, - 40026, - 40027, - 40028, - 40029, - 40030, - 40031, - 40032, - 40033, - 40034, - 40035, - 40036, - 40037, - 40038, - 40039, - 40040, - 40041, - 40042, - 40043, - 40044, - 40045, - 40046, - 40047, - 40048, - 40049, - 40050, - 40051, - 40052, - 40053, - 40054, - 40055, - 40056, - 40057, - 40058, - 40059, - 40060, - 40061, - 40062, - 40063, - 40064, - 40065, - 40066, - 40067, - 40068, - 40069, - 40070, - 40071, - 40072, - 40073, - 40074, - 40075, - 40076, - 40077, - 40078, - 40079, - 40080, - 40081, - 40082, - 40083, - 40084, - 40085, - 40086, - 40087, - 40088, - 40089, - 40090, - 40091, - 40092, - 40093, - 40094, - 40095, - 40096, - 40097, - 40098, - 40099, - 40100, - 40101, - 40102, - 40103, - 40104, - 40105, - 40106, - 40107, - 40108, - 40109, - 40110, - 40111, - 40112, - 40113, - 40114, - 40115, - 40116, - 40117, - 40118, - 40119, - 40120, - 40121, - 40122, - 40123, - 40124, - 40125, - 40126, - 40127, - 40128, - 40129, - 40130, - 40131, - 40132, - 40133, - 40134, - 40135, - 40136, - 40137, - 40138, - 40139, - 40140, - 40141, - 40142, - 40143, - 40144, - 40145, - 40146, - 40147, - 40148, - 40149, - 40150, - 40151, - 40152, - 40153, - 40154, - 40155, - 40156, - 40157, - 40158, - 40159, - 40160, - 40161, - 40162, - 40163, - 40164, - 40165, - 40166, - 40167, - 40168, - 40169, - 40170, - 40171, - 40172, - 40173, - 40174, - 40175, - 40176, - 40177, - 40178, - 40179, - 40180, - 40181, - 40182, - 40183, - 40184, - 40185, - 40186, - 40187, - 40188, - 40189, - 40190, - 40191, - 40192, - 40193, - 40194, - 40195, - 40196, - 40197, - 40198, - 40199, - 40200, - 40201, - 40202, - 40203, - 40204, - 40205, - 40206, - 40207, - 40208, - 40209, - 40210, - 40211, - 40212, - 40213, - 40214, - 40215, - 40216, - 40217, - 40218, - 40219, - 40220, - 40221, - 40222, - 40223, - 40224, - 40225, - 40226, - 40227, - 40228, - 40229, - 40230, - 40231, - 40232, - 40233, - 40234, - 40235, - 40236, - 40237, - 40238, - 40239, - 40240, - 40241, - 40242, - 40243, - 40244, - 40245, - 40246, - 40247, - 40248, - 40249, - 40250, - 40251, - 40252, - 40253, - 40254, - 40255, - 40256, - 40257, - 40258, - 40259, - 40260, - 40261, - 40262, - 40263, - 40264, - 40265, - 40266, - 40267, - 40268, - 40269, - 40270, - 40271, - 40272, - 40273, - 40274, - 40275, - 40276, - 40277, - 40278, - 40279, - 40280, - 40281, - 40282, - 40283, - 40284, - 40285, - 40286, - 40287, - 40288, - 40289, - 40290, - 40291, - 40292, - 40293, - 40294, - 40295, - 40296, - 40297, - 40298, - 40299, - 40300, - 40301, - 40302, - 40303, - 40304, - 40305, - 40306, - 40307, - 40308, - 40309, - 40310, - 40311, - 40312, - 40313, - 40314, - 40315, - 40316, - 40317, - 40318, - 40319, - 40320, - 40321, - 40322, - 40323, - 40324, - 40325, - 40326, - 40327, - 40328, - 40329, - 40330, - 40331, - 40332, - 40333, - 40334, - 40335, - 40336, - 40337, - 40338, - 40339, - 40340, - 40341, - 40342, - 40343, - 40344, - 40345, - 40346, - 40347, - 40348, - 40349, - 40350, - 40351, - 40352, - 40353, - 40354, - 40355, - 40356, - 40357, - 40358, - 40359, - 40360, - 40361, - 40362, - 40363, - 40364, - 40365, - 40366, - 40367, - 40368, - 40369, - 40370, - 40371, - 40372, - 40373, - 40374, - 40375, - 40376, - 40377, - 40378, - 40379, - 40380, - 40381, - 40382, - 40383, - 40384, - 40385, - 40386, - 40387, - 40388, - 40389, - 40390, - 40391, - 40392, - 40393, - 40394, - 40395, - 40396, - 40397, - 40398, - 40399, - 40400, - 40401, - 40402, - 40403, - 40404, - 40405, - 40406, - 40407, - 40408, - 40409, - 40410, - 40411, - 40412, - 40413, - 40414, - 40415, - 40416, - 40417, - 40418, - 40419, - 40420, - 40421, - 40422, - 40423, - 40424, - 40425, - 40426, - 40427, - 40428, - 40429, - 40430, - 40431, - 40432, - 40433, - 40434, - 40435, - 40436, - 40437, - 40438, - 40439, - 40440, - 40441, - 40442, - 40443, - 40444, - 40445, - 40446, - 40447, - 40448, - 40449, - 40450, - 40451, - 40452, - 40453, - 40454, - 40455, - 40456, - 40457, - 40458, - 40459, - 40460, - 40461, - 40462, - 40463, - 40464, - 40465, - 40466, - 40467, - 40468, - 40469, - 40470, - 40471, - 40472, - 40473, - 40474, - 40475, - 40476, - 40477, - 40478, - 40479, - 40480, - 40481, - 40482, - 40483, - 40484, - 40485, - 40486, - 40487, - 40488, - 40489, - 40490, - 40491, - 40492, - 40493, - 40494, - 40495, - 40496, - 40497, - 40498, - 40499, - 40500, - 40501, - 40502, - 40503, - 40504, - 40505, - 40506, - 40507, - 40508, - 40509, - 40510, - 40511, - 40512, - 40513, - 40514, - 40515, - 40516, - 40517, - 40518, - 40519, - 40520, - 40521, - 40522, - 40523, - 40524, - 40525, - 40526, - 40527, - 40528, - 40529, - 40530, - 40531, - 40532, - 40533, - 40534, - 40535, - 40536, - 40537, - 40538, - 40539, - 40540, - 40541, - 40542, - 40543, - 40544, - 40545, - 40546, - 40547, - 40548, - 40549, - 40550, - 40551, - 40552, - 40553, - 40554, - 40555, - 40556, - 40557, - 40558, - 40559, - 40560, - 40561, - 40562, - 40563, - 40564, - 40565, - 40566, - 40567, - 40568, - 40569, - 40570, - 40571, - 40572, - 40573, - 40574, - 40575, - 40576, - 40577, - 40578, - 40579, - 40580, - 40581, - 40582, - 40583, - 40584, - 40585, - 40586, - 40587, - 40588, - 40589, - 40590, - 40591, - 40592, - 40593, - 40594, - 40595, - 40596, - 40597, - 40598, - 40599, - 40600, - 40601, - 40602, - 40603, - 40604, - 40605, - 40606, - 40607, - 40608, - 40609, - 40610, - 40611, - 40612, - 40613, - 40614, - 40615, - 40616, - 40617, - 40618, - 40619, - 40620, - 40621, - 40622, - 40623, - 40624, - 40625, - 40626, - 40627, - 40628, - 40629, - 40630, - 40631, - 40632, - 40633, - 40634, - 40635, - 40636, - 40637, - 40638, - 40639, - 40640, - 40641, - 40642, - 40643, - 40644, - 40645, - 40646, - 40647, - 40648, - 40649, - 40650, - 40651, - 40652, - 40653, - 40654, - 40655, - 40656, - 40657, - 40658, - 40659, - 40660, - 40661, - 40662, - 40663, - 40664, - 40665, - 40666, - 40667, - 40668, - 40669, - 40670, - 40671, - 40672, - 40673, - 40674, - 40675, - 40676, - 40677, - 40678, - 40679, - 40680, - 40681, - 40682, - 40683, - 40684, - 40685, - 40686, - 40687, - 40688, - 40689, - 40690, - 40691, - 40692, - 40693, - 40694, - 40695, - 40696, - 40697, - 40698, - 40699, - 40700, - 40701, - 40702, - 40703, - 40704, - 40705, - 40706, - 40707, - 40708, - 40709, - 40710, - 40711, - 40712, - 40713, - 40714, - 40715, - 40716, - 40717, - 40718, - 40719, - 40720, - 40721, - 40722, - 40723, - 40724, - 40725, - 40726, - 40727, - 40728, - 40729, - 40730, - 40731, - 40732, - 40733, - 40734, - 40735, - 40736, - 40737, - 40738, - 40739, - 40740, - 40741, - 40742, - 40743, - 40744, - 40745, - 40746, - 40747, - 40748, - 40749, - 40750, - 40751, - 40752, - 40753, - 40754, - 40755, - 40756, - 40757, - 40758, - 40759, - 40760, - 40761, - 40762, - 40763, - 40764, - 40765, - 40766, - 40767, - 40768, - 40769, - 40770, - 40771, - 40772, - 40773, - 40774, - 40775, - 40776, - 40777, - 40778, - 40779, - 40780, - 40781, - 40782, - 40783, - 40784, - 40785, - 40786, - 40787, - 40788, - 40789, - 40790, - 40791, - 40792, - 40793, - 40794, - 40795, - 40796, - 40797, - 40798, - 40799, - 40800, - 40801, - 40802, - 40803, - 40804, - 40805, - 40806, - 40807, - 40808, - 40809, - 40810, - 40811, - 40812, - 40813, - 40814, - 40815, - 40816, - 40817, - 40818, - 40819, - 40820, - 40821, - 40822, - 40823, - 40824, - 40825, - 40826, - 40827, - 40828, - 40829, - 40830, - 40831, - 40832, - 40833, - 40834, - 40835, - 40836, - 40837, - 40838, - 40839, - 40840, - 40841, - 40842, - 40843, - 40844, - 40845, - 40846, - 40847, - 40848, - 40849, - 40850, - 40851, - 40852, - 40853, - 40854, - 40855, - 40856, - 40857, - 40858, - 40859, - 40860, - 40861, - 40862, - 40863, - 40864, - 40865, - 40866, - 40867, - 40868, - 40869, - 40870, - 40871, - 40872, - 40873, - 40874, - 40875, - 40876, - 40877, - 40878, - 40879, - 40880, - 40881, - 40882, - 40883, - 40884, - 40885, - 40886, - 40887, - 40888, - 40889, - 40890, - 40891, - 40892, - 40893, - 40894, - 40895, - 40896, - 40897, - 40898, - 40899, - 40900, - 40901, - 40902, - 40903, - 40904, - 40905, - 40906, - 40907, - 40908, - 40960, - 40961, - 40962, - 40963, - 40964, - 40965, - 40966, - 40967, - 40968, - 40969, - 40970, - 40971, - 40972, - 40973, - 40974, - 40975, - 40976, - 40977, - 40978, - 40979, - 40980, - 40981, - 40982, - 40983, - 40984, - 40985, - 40986, - 40987, - 40988, - 40989, - 40990, - 40991, - 40992, - 40993, - 40994, - 40995, - 40996, - 40997, - 40998, - 40999, - 41000, - 41001, - 41002, - 41003, - 41004, - 41005, - 41006, - 41007, - 41008, - 41009, - 41010, - 41011, - 41012, - 41013, - 41014, - 41015, - 41016, - 41017, - 41018, - 41019, - 41020, - 41021, - 41022, - 41023, - 41024, - 41025, - 41026, - 41027, - 41028, - 41029, - 41030, - 41031, - 41032, - 41033, - 41034, - 41035, - 41036, - 41037, - 41038, - 41039, - 41040, - 41041, - 41042, - 41043, - 41044, - 41045, - 41046, - 41047, - 41048, - 41049, - 41050, - 41051, - 41052, - 41053, - 41054, - 41055, - 41056, - 41057, - 41058, - 41059, - 41060, - 41061, - 41062, - 41063, - 41064, - 41065, - 41066, - 41067, - 41068, - 41069, - 41070, - 41071, - 41072, - 41073, - 41074, - 41075, - 41076, - 41077, - 41078, - 41079, - 41080, - 41081, - 41082, - 41083, - 41084, - 41085, - 41086, - 41087, - 41088, - 41089, - 41090, - 41091, - 41092, - 41093, - 41094, - 41095, - 41096, - 41097, - 41098, - 41099, - 41100, - 41101, - 41102, - 41103, - 41104, - 41105, - 41106, - 41107, - 41108, - 41109, - 41110, - 41111, - 41112, - 41113, - 41114, - 41115, - 41116, - 41117, - 41118, - 41119, - 41120, - 41121, - 41122, - 41123, - 41124, - 41125, - 41126, - 41127, - 41128, - 41129, - 41130, - 41131, - 41132, - 41133, - 41134, - 41135, - 41136, - 41137, - 41138, - 41139, - 41140, - 41141, - 41142, - 41143, - 41144, - 41145, - 41146, - 41147, - 41148, - 41149, - 41150, - 41151, - 41152, - 41153, - 41154, - 41155, - 41156, - 41157, - 41158, - 41159, - 41160, - 41161, - 41162, - 41163, - 41164, - 41165, - 41166, - 41167, - 41168, - 41169, - 41170, - 41171, - 41172, - 41173, - 41174, - 41175, - 41176, - 41177, - 41178, - 41179, - 41180, - 41181, - 41182, - 41183, - 41184, - 41185, - 41186, - 41187, - 41188, - 41189, - 41190, - 41191, - 41192, - 41193, - 41194, - 41195, - 41196, - 41197, - 41198, - 41199, - 41200, - 41201, - 41202, - 41203, - 41204, - 41205, - 41206, - 41207, - 41208, - 41209, - 41210, - 41211, - 41212, - 41213, - 41214, - 41215, - 41216, - 41217, - 41218, - 41219, - 41220, - 41221, - 41222, - 41223, - 41224, - 41225, - 41226, - 41227, - 41228, - 41229, - 41230, - 41231, - 41232, - 41233, - 41234, - 41235, - 41236, - 41237, - 41238, - 41239, - 41240, - 41241, - 41242, - 41243, - 41244, - 41245, - 41246, - 41247, - 41248, - 41249, - 41250, - 41251, - 41252, - 41253, - 41254, - 41255, - 41256, - 41257, - 41258, - 41259, - 41260, - 41261, - 41262, - 41263, - 41264, - 41265, - 41266, - 41267, - 41268, - 41269, - 41270, - 41271, - 41272, - 41273, - 41274, - 41275, - 41276, - 41277, - 41278, - 41279, - 41280, - 41281, - 41282, - 41283, - 41284, - 41285, - 41286, - 41287, - 41288, - 41289, - 41290, - 41291, - 41292, - 41293, - 41294, - 41295, - 41296, - 41297, - 41298, - 41299, - 41300, - 41301, - 41302, - 41303, - 41304, - 41305, - 41306, - 41307, - 41308, - 41309, - 41310, - 41311, - 41312, - 41313, - 41314, - 41315, - 41316, - 41317, - 41318, - 41319, - 41320, - 41321, - 41322, - 41323, - 41324, - 41325, - 41326, - 41327, - 41328, - 41329, - 41330, - 41331, - 41332, - 41333, - 41334, - 41335, - 41336, - 41337, - 41338, - 41339, - 41340, - 41341, - 41342, - 41343, - 41344, - 41345, - 41346, - 41347, - 41348, - 41349, - 41350, - 41351, - 41352, - 41353, - 41354, - 41355, - 41356, - 41357, - 41358, - 41359, - 41360, - 41361, - 41362, - 41363, - 41364, - 41365, - 41366, - 41367, - 41368, - 41369, - 41370, - 41371, - 41372, - 41373, - 41374, - 41375, - 41376, - 41377, - 41378, - 41379, - 41380, - 41381, - 41382, - 41383, - 41384, - 41385, - 41386, - 41387, - 41388, - 41389, - 41390, - 41391, - 41392, - 41393, - 41394, - 41395, - 41396, - 41397, - 41398, - 41399, - 41400, - 41401, - 41402, - 41403, - 41404, - 41405, - 41406, - 41407, - 41408, - 41409, - 41410, - 41411, - 41412, - 41413, - 41414, - 41415, - 41416, - 41417, - 41418, - 41419, - 41420, - 41421, - 41422, - 41423, - 41424, - 41425, - 41426, - 41427, - 41428, - 41429, - 41430, - 41431, - 41432, - 41433, - 41434, - 41435, - 41436, - 41437, - 41438, - 41439, - 41440, - 41441, - 41442, - 41443, - 41444, - 41445, - 41446, - 41447, - 41448, - 41449, - 41450, - 41451, - 41452, - 41453, - 41454, - 41455, - 41456, - 41457, - 41458, - 41459, - 41460, - 41461, - 41462, - 41463, - 41464, - 41465, - 41466, - 41467, - 41468, - 41469, - 41470, - 41471, - 41472, - 41473, - 41474, - 41475, - 41476, - 41477, - 41478, - 41479, - 41480, - 41481, - 41482, - 41483, - 41484, - 41485, - 41486, - 41487, - 41488, - 41489, - 41490, - 41491, - 41492, - 41493, - 41494, - 41495, - 41496, - 41497, - 41498, - 41499, - 41500, - 41501, - 41502, - 41503, - 41504, - 41505, - 41506, - 41507, - 41508, - 41509, - 41510, - 41511, - 41512, - 41513, - 41514, - 41515, - 41516, - 41517, - 41518, - 41519, - 41520, - 41521, - 41522, - 41523, - 41524, - 41525, - 41526, - 41527, - 41528, - 41529, - 41530, - 41531, - 41532, - 41533, - 41534, - 41535, - 41536, - 41537, - 41538, - 41539, - 41540, - 41541, - 41542, - 41543, - 41544, - 41545, - 41546, - 41547, - 41548, - 41549, - 41550, - 41551, - 41552, - 41553, - 41554, - 41555, - 41556, - 41557, - 41558, - 41559, - 41560, - 41561, - 41562, - 41563, - 41564, - 41565, - 41566, - 41567, - 41568, - 41569, - 41570, - 41571, - 41572, - 41573, - 41574, - 41575, - 41576, - 41577, - 41578, - 41579, - 41580, - 41581, - 41582, - 41583, - 41584, - 41585, - 41586, - 41587, - 41588, - 41589, - 41590, - 41591, - 41592, - 41593, - 41594, - 41595, - 41596, - 41597, - 41598, - 41599, - 41600, - 41601, - 41602, - 41603, - 41604, - 41605, - 41606, - 41607, - 41608, - 41609, - 41610, - 41611, - 41612, - 41613, - 41614, - 41615, - 41616, - 41617, - 41618, - 41619, - 41620, - 41621, - 41622, - 41623, - 41624, - 41625, - 41626, - 41627, - 41628, - 41629, - 41630, - 41631, - 41632, - 41633, - 41634, - 41635, - 41636, - 41637, - 41638, - 41639, - 41640, - 41641, - 41642, - 41643, - 41644, - 41645, - 41646, - 41647, - 41648, - 41649, - 41650, - 41651, - 41652, - 41653, - 41654, - 41655, - 41656, - 41657, - 41658, - 41659, - 41660, - 41661, - 41662, - 41663, - 41664, - 41665, - 41666, - 41667, - 41668, - 41669, - 41670, - 41671, - 41672, - 41673, - 41674, - 41675, - 41676, - 41677, - 41678, - 41679, - 41680, - 41681, - 41682, - 41683, - 41684, - 41685, - 41686, - 41687, - 41688, - 41689, - 41690, - 41691, - 41692, - 41693, - 41694, - 41695, - 41696, - 41697, - 41698, - 41699, - 41700, - 41701, - 41702, - 41703, - 41704, - 41705, - 41706, - 41707, - 41708, - 41709, - 41710, - 41711, - 41712, - 41713, - 41714, - 41715, - 41716, - 41717, - 41718, - 41719, - 41720, - 41721, - 41722, - 41723, - 41724, - 41725, - 41726, - 41727, - 41728, - 41729, - 41730, - 41731, - 41732, - 41733, - 41734, - 41735, - 41736, - 41737, - 41738, - 41739, - 41740, - 41741, - 41742, - 41743, - 41744, - 41745, - 41746, - 41747, - 41748, - 41749, - 41750, - 41751, - 41752, - 41753, - 41754, - 41755, - 41756, - 41757, - 41758, - 41759, - 41760, - 41761, - 41762, - 41763, - 41764, - 41765, - 41766, - 41767, - 41768, - 41769, - 41770, - 41771, - 41772, - 41773, - 41774, - 41775, - 41776, - 41777, - 41778, - 41779, - 41780, - 41781, - 41782, - 41783, - 41784, - 41785, - 41786, - 41787, - 41788, - 41789, - 41790, - 41791, - 41792, - 41793, - 41794, - 41795, - 41796, - 41797, - 41798, - 41799, - 41800, - 41801, - 41802, - 41803, - 41804, - 41805, - 41806, - 41807, - 41808, - 41809, - 41810, - 41811, - 41812, - 41813, - 41814, - 41815, - 41816, - 41817, - 41818, - 41819, - 41820, - 41821, - 41822, - 41823, - 41824, - 41825, - 41826, - 41827, - 41828, - 41829, - 41830, - 41831, - 41832, - 41833, - 41834, - 41835, - 41836, - 41837, - 41838, - 41839, - 41840, - 41841, - 41842, - 41843, - 41844, - 41845, - 41846, - 41847, - 41848, - 41849, - 41850, - 41851, - 41852, - 41853, - 41854, - 41855, - 41856, - 41857, - 41858, - 41859, - 41860, - 41861, - 41862, - 41863, - 41864, - 41865, - 41866, - 41867, - 41868, - 41869, - 41870, - 41871, - 41872, - 41873, - 41874, - 41875, - 41876, - 41877, - 41878, - 41879, - 41880, - 41881, - 41882, - 41883, - 41884, - 41885, - 41886, - 41887, - 41888, - 41889, - 41890, - 41891, - 41892, - 41893, - 41894, - 41895, - 41896, - 41897, - 41898, - 41899, - 41900, - 41901, - 41902, - 41903, - 41904, - 41905, - 41906, - 41907, - 41908, - 41909, - 41910, - 41911, - 41912, - 41913, - 41914, - 41915, - 41916, - 41917, - 41918, - 41919, - 41920, - 41921, - 41922, - 41923, - 41924, - 41925, - 41926, - 41927, - 41928, - 41929, - 41930, - 41931, - 41932, - 41933, - 41934, - 41935, - 41936, - 41937, - 41938, - 41939, - 41940, - 41941, - 41942, - 41943, - 41944, - 41945, - 41946, - 41947, - 41948, - 41949, - 41950, - 41951, - 41952, - 41953, - 41954, - 41955, - 41956, - 41957, - 41958, - 41959, - 41960, - 41961, - 41962, - 41963, - 41964, - 41965, - 41966, - 41967, - 41968, - 41969, - 41970, - 41971, - 41972, - 41973, - 41974, - 41975, - 41976, - 41977, - 41978, - 41979, - 41980, - 41981, - 41982, - 41983, - 41984, - 41985, - 41986, - 41987, - 41988, - 41989, - 41990, - 41991, - 41992, - 41993, - 41994, - 41995, - 41996, - 41997, - 41998, - 41999, - 42000, - 42001, - 42002, - 42003, - 42004, - 42005, - 42006, - 42007, - 42008, - 42009, - 42010, - 42011, - 42012, - 42013, - 42014, - 42015, - 42016, - 42017, - 42018, - 42019, - 42020, - 42021, - 42022, - 42023, - 42024, - 42025, - 42026, - 42027, - 42028, - 42029, - 42030, - 42031, - 42032, - 42033, - 42034, - 42035, - 42036, - 42037, - 42038, - 42039, - 42040, - 42041, - 42042, - 42043, - 42044, - 42045, - 42046, - 42047, - 42048, - 42049, - 42050, - 42051, - 42052, - 42053, - 42054, - 42055, - 42056, - 42057, - 42058, - 42059, - 42060, - 42061, - 42062, - 42063, - 42064, - 42065, - 42066, - 42067, - 42068, - 42069, - 42070, - 42071, - 42072, - 42073, - 42074, - 42075, - 42076, - 42077, - 42078, - 42079, - 42080, - 42081, - 42082, - 42083, - 42084, - 42085, - 42086, - 42087, - 42088, - 42089, - 42090, - 42091, - 42092, - 42093, - 42094, - 42095, - 42096, - 42097, - 42098, - 42099, - 42100, - 42101, - 42102, - 42103, - 42104, - 42105, - 42106, - 42107, - 42108, - 42109, - 42110, - 42111, - 42112, - 42113, - 42114, - 42115, - 42116, - 42117, - 42118, - 42119, - 42120, - 42121, - 42122, - 42123, - 42124, - 42192, - 42193, - 42194, - 42195, - 42196, - 42197, - 42198, - 42199, - 42200, - 42201, - 42202, - 42203, - 42204, - 42205, - 42206, - 42207, - 42208, - 42209, - 42210, - 42211, - 42212, - 42213, - 42214, - 42215, - 42216, - 42217, - 42218, - 42219, - 42220, - 42221, - 42222, - 42223, - 42224, - 42225, - 42226, - 42227, - 42228, - 42229, - 42230, - 42231, - 42232, - 42233, - 42234, - 42235, - 42236, - 42237, - 42240, - 42241, - 42242, - 42243, - 42244, - 42245, - 42246, - 42247, - 42248, - 42249, - 42250, - 42251, - 42252, - 42253, - 42254, - 42255, - 42256, - 42257, - 42258, - 42259, - 42260, - 42261, - 42262, - 42263, - 42264, - 42265, - 42266, - 42267, - 42268, - 42269, - 42270, - 42271, - 42272, - 42273, - 42274, - 42275, - 42276, - 42277, - 42278, - 42279, - 42280, - 42281, - 42282, - 42283, - 42284, - 42285, - 42286, - 42287, - 42288, - 42289, - 42290, - 42291, - 42292, - 42293, - 42294, - 42295, - 42296, - 42297, - 42298, - 42299, - 42300, - 42301, - 42302, - 42303, - 42304, - 42305, - 42306, - 42307, - 42308, - 42309, - 42310, - 42311, - 42312, - 42313, - 42314, - 42315, - 42316, - 42317, - 42318, - 42319, - 42320, - 42321, - 42322, - 42323, - 42324, - 42325, - 42326, - 42327, - 42328, - 42329, - 42330, - 42331, - 42332, - 42333, - 42334, - 42335, - 42336, - 42337, - 42338, - 42339, - 42340, - 42341, - 42342, - 42343, - 42344, - 42345, - 42346, - 42347, - 42348, - 42349, - 42350, - 42351, - 42352, - 42353, - 42354, - 42355, - 42356, - 42357, - 42358, - 42359, - 42360, - 42361, - 42362, - 42363, - 42364, - 42365, - 42366, - 42367, - 42368, - 42369, - 42370, - 42371, - 42372, - 42373, - 42374, - 42375, - 42376, - 42377, - 42378, - 42379, - 42380, - 42381, - 42382, - 42383, - 42384, - 42385, - 42386, - 42387, - 42388, - 42389, - 42390, - 42391, - 42392, - 42393, - 42394, - 42395, - 42396, - 42397, - 42398, - 42399, - 42400, - 42401, - 42402, - 42403, - 42404, - 42405, - 42406, - 42407, - 42408, - 42409, - 42410, - 42411, - 42412, - 42413, - 42414, - 42415, - 42416, - 42417, - 42418, - 42419, - 42420, - 42421, - 42422, - 42423, - 42424, - 42425, - 42426, - 42427, - 42428, - 42429, - 42430, - 42431, - 42432, - 42433, - 42434, - 42435, - 42436, - 42437, - 42438, - 42439, - 42440, - 42441, - 42442, - 42443, - 42444, - 42445, - 42446, - 42447, - 42448, - 42449, - 42450, - 42451, - 42452, - 42453, - 42454, - 42455, - 42456, - 42457, - 42458, - 42459, - 42460, - 42461, - 42462, - 42463, - 42464, - 42465, - 42466, - 42467, - 42468, - 42469, - 42470, - 42471, - 42472, - 42473, - 42474, - 42475, - 42476, - 42477, - 42478, - 42479, - 42480, - 42481, - 42482, - 42483, - 42484, - 42485, - 42486, - 42487, - 42488, - 42489, - 42490, - 42491, - 42492, - 42493, - 42494, - 42495, - 42496, - 42497, - 42498, - 42499, - 42500, - 42501, - 42502, - 42503, - 42504, - 42505, - 42506, - 42507, - 42508, - 42512, - 42513, - 42514, - 42515, - 42516, - 42517, - 42518, - 42519, - 42520, - 42521, - 42522, - 42523, - 42524, - 42525, - 42526, - 42527, - 42538, - 42539, - 42560, - 42561, - 42562, - 42563, - 42564, - 42565, - 42566, - 42567, - 42568, - 42569, - 42570, - 42571, - 42572, - 42573, - 42574, - 42575, - 42576, - 42577, - 42578, - 42579, - 42580, - 42581, - 42582, - 42583, - 42584, - 42585, - 42586, - 42587, - 42588, - 42589, - 42590, - 42591, - 42592, - 42593, - 42594, - 42595, - 42596, - 42597, - 42598, - 42599, - 42600, - 42601, - 42602, - 42603, - 42604, - 42605, - 42606, - 42623, - 42624, - 42625, - 42626, - 42627, - 42628, - 42629, - 42630, - 42631, - 42632, - 42633, - 42634, - 42635, - 42636, - 42637, - 42638, - 42639, - 42640, - 42641, - 42642, - 42643, - 42644, - 42645, - 42646, - 42647, - 42656, - 42657, - 42658, - 42659, - 42660, - 42661, - 42662, - 42663, - 42664, - 42665, - 42666, - 42667, - 42668, - 42669, - 42670, - 42671, - 42672, - 42673, - 42674, - 42675, - 42676, - 42677, - 42678, - 42679, - 42680, - 42681, - 42682, - 42683, - 42684, - 42685, - 42686, - 42687, - 42688, - 42689, - 42690, - 42691, - 42692, - 42693, - 42694, - 42695, - 42696, - 42697, - 42698, - 42699, - 42700, - 42701, - 42702, - 42703, - 42704, - 42705, - 42706, - 42707, - 42708, - 42709, - 42710, - 42711, - 42712, - 42713, - 42714, - 42715, - 42716, - 42717, - 42718, - 42719, - 42720, - 42721, - 42722, - 42723, - 42724, - 42725, - 42726, - 42727, - 42728, - 42729, - 42730, - 42731, - 42732, - 42733, - 42734, - 42735, - 42775, - 42776, - 42777, - 42778, - 42779, - 42780, - 42781, - 42782, - 42783, - 42786, - 42787, - 42788, - 42789, - 42790, - 42791, - 42792, - 42793, - 42794, - 42795, - 42796, - 42797, - 42798, - 42799, - 42800, - 42801, - 42802, - 42803, - 42804, - 42805, - 42806, - 42807, - 42808, - 42809, - 42810, - 42811, - 42812, - 42813, - 42814, - 42815, - 42816, - 42817, - 42818, - 42819, - 42820, - 42821, - 42822, - 42823, - 42824, - 42825, - 42826, - 42827, - 42828, - 42829, - 42830, - 42831, - 42832, - 42833, - 42834, - 42835, - 42836, - 42837, - 42838, - 42839, - 42840, - 42841, - 42842, - 42843, - 42844, - 42845, - 42846, - 42847, - 42848, - 42849, - 42850, - 42851, - 42852, - 42853, - 42854, - 42855, - 42856, - 42857, - 42858, - 42859, - 42860, - 42861, - 42862, - 42863, - 42864, - 42865, - 42866, - 42867, - 42868, - 42869, - 42870, - 42871, - 42872, - 42873, - 42874, - 42875, - 42876, - 42877, - 42878, - 42879, - 42880, - 42881, - 42882, - 42883, - 42884, - 42885, - 42886, - 42887, - 42888, - 42891, - 42892, - 42893, - 42894, - 42896, - 42897, - 42898, - 42899, - 42912, - 42913, - 42914, - 42915, - 42916, - 42917, - 42918, - 42919, - 42920, - 42921, - 42922, - 43000, - 43001, - 43002, - 43003, - 43004, - 43005, - 43006, - 43007, - 43008, - 43009, - 43011, - 43012, - 43013, - 43015, - 43016, - 43017, - 43018, - 43020, - 43021, - 43022, - 43023, - 43024, - 43025, - 43026, - 43027, - 43028, - 43029, - 43030, - 43031, - 43032, - 43033, - 43034, - 43035, - 43036, - 43037, - 43038, - 43039, - 43040, - 43041, - 43042, - 43072, - 43073, - 43074, - 43075, - 43076, - 43077, - 43078, - 43079, - 43080, - 43081, - 43082, - 43083, - 43084, - 43085, - 43086, - 43087, - 43088, - 43089, - 43090, - 43091, - 43092, - 43093, - 43094, - 43095, - 43096, - 43097, - 43098, - 43099, - 43100, - 43101, - 43102, - 43103, - 43104, - 43105, - 43106, - 43107, - 43108, - 43109, - 43110, - 43111, - 43112, - 43113, - 43114, - 43115, - 43116, - 43117, - 43118, - 43119, - 43120, - 43121, - 43122, - 43123, - 43138, - 43139, - 43140, - 43141, - 43142, - 43143, - 43144, - 43145, - 43146, - 43147, - 43148, - 43149, - 43150, - 43151, - 43152, - 43153, - 43154, - 43155, - 43156, - 43157, - 43158, - 43159, - 43160, - 43161, - 43162, - 43163, - 43164, - 43165, - 43166, - 43167, - 43168, - 43169, - 43170, - 43171, - 43172, - 43173, - 43174, - 43175, - 43176, - 43177, - 43178, - 43179, - 43180, - 43181, - 43182, - 43183, - 43184, - 43185, - 43186, - 43187, - 43250, - 43251, - 43252, - 43253, - 43254, - 43255, - 43259, - 43274, - 43275, - 43276, - 43277, - 43278, - 43279, - 43280, - 43281, - 43282, - 43283, - 43284, - 43285, - 43286, - 43287, - 43288, - 43289, - 43290, - 43291, - 43292, - 43293, - 43294, - 43295, - 43296, - 43297, - 43298, - 43299, - 43300, - 43301, - 43312, - 43313, - 43314, - 43315, - 43316, - 43317, - 43318, - 43319, - 43320, - 43321, - 43322, - 43323, - 43324, - 43325, - 43326, - 43327, - 43328, - 43329, - 43330, - 43331, - 43332, - 43333, - 43334, - 43360, - 43361, - 43362, - 43363, - 43364, - 43365, - 43366, - 43367, - 43368, - 43369, - 43370, - 43371, - 43372, - 43373, - 43374, - 43375, - 43376, - 43377, - 43378, - 43379, - 43380, - 43381, - 43382, - 43383, - 43384, - 43385, - 43386, - 43387, - 43388, - 43396, - 43397, - 43398, - 43399, - 43400, - 43401, - 43402, - 43403, - 43404, - 43405, - 43406, - 43407, - 43408, - 43409, - 43410, - 43411, - 43412, - 43413, - 43414, - 43415, - 43416, - 43417, - 43418, - 43419, - 43420, - 43421, - 43422, - 43423, - 43424, - 43425, - 43426, - 43427, - 43428, - 43429, - 43430, - 43431, - 43432, - 43433, - 43434, - 43435, - 43436, - 43437, - 43438, - 43439, - 43440, - 43441, - 43442, - 43471, - 43520, - 43521, - 43522, - 43523, - 43524, - 43525, - 43526, - 43527, - 43528, - 43529, - 43530, - 43531, - 43532, - 43533, - 43534, - 43535, - 43536, - 43537, - 43538, - 43539, - 43540, - 43541, - 43542, - 43543, - 43544, - 43545, - 43546, - 43547, - 43548, - 43549, - 43550, - 43551, - 43552, - 43553, - 43554, - 43555, - 43556, - 43557, - 43558, - 43559, - 43560, - 43584, - 43585, - 43586, - 43588, - 43589, - 43590, - 43591, - 43592, - 43593, - 43594, - 43595, - 43616, - 43617, - 43618, - 43619, - 43620, - 43621, - 43622, - 43623, - 43624, - 43625, - 43626, - 43627, - 43628, - 43629, - 43630, - 43631, - 43632, - 43633, - 43634, - 43635, - 43636, - 43637, - 43638, - 43642, - 43648, - 43649, - 43650, - 43651, - 43652, - 43653, - 43654, - 43655, - 43656, - 43657, - 43658, - 43659, - 43660, - 43661, - 43662, - 43663, - 43664, - 43665, - 43666, - 43667, - 43668, - 43669, - 43670, - 43671, - 43672, - 43673, - 43674, - 43675, - 43676, - 43677, - 43678, - 43679, - 43680, - 43681, - 43682, - 43683, - 43684, - 43685, - 43686, - 43687, - 43688, - 43689, - 43690, - 43691, - 43692, - 43693, - 43694, - 43695, - 43697, - 43701, - 43702, - 43705, - 43706, - 43707, - 43708, - 43709, - 43712, - 43714, - 43739, - 43740, - 43741, - 43744, - 43745, - 43746, - 43747, - 43748, - 43749, - 43750, - 43751, - 43752, - 43753, - 43754, - 43762, - 43763, - 43764, - 43777, - 43778, - 43779, - 43780, - 43781, - 43782, - 43785, - 43786, - 43787, - 43788, - 43789, - 43790, - 43793, - 43794, - 43795, - 43796, - 43797, - 43798, - 43808, - 43809, - 43810, - 43811, - 43812, - 43813, - 43814, - 43816, - 43817, - 43818, - 43819, - 43820, - 43821, - 43822, - 43968, - 43969, - 43970, - 43971, - 43972, - 43973, - 43974, - 43975, - 43976, - 43977, - 43978, - 43979, - 43980, - 43981, - 43982, - 43983, - 43984, - 43985, - 43986, - 43987, - 43988, - 43989, - 43990, - 43991, - 43992, - 43993, - 43994, - 43995, - 43996, - 43997, - 43998, - 43999, - 44000, - 44001, - 44002, - 44032, - 44033, - 44034, - 44035, - 44036, - 44037, - 44038, - 44039, - 44040, - 44041, - 44042, - 44043, - 44044, - 44045, - 44046, - 44047, - 44048, - 44049, - 44050, - 44051, - 44052, - 44053, - 44054, - 44055, - 44056, - 44057, - 44058, - 44059, - 44060, - 44061, - 44062, - 44063, - 44064, - 44065, - 44066, - 44067, - 44068, - 44069, - 44070, - 44071, - 44072, - 44073, - 44074, - 44075, - 44076, - 44077, - 44078, - 44079, - 44080, - 44081, - 44082, - 44083, - 44084, - 44085, - 44086, - 44087, - 44088, - 44089, - 44090, - 44091, - 44092, - 44093, - 44094, - 44095, - 44096, - 44097, - 44098, - 44099, - 44100, - 44101, - 44102, - 44103, - 44104, - 44105, - 44106, - 44107, - 44108, - 44109, - 44110, - 44111, - 44112, - 44113, - 44114, - 44115, - 44116, - 44117, - 44118, - 44119, - 44120, - 44121, - 44122, - 44123, - 44124, - 44125, - 44126, - 44127, - 44128, - 44129, - 44130, - 44131, - 44132, - 44133, - 44134, - 44135, - 44136, - 44137, - 44138, - 44139, - 44140, - 44141, - 44142, - 44143, - 44144, - 44145, - 44146, - 44147, - 44148, - 44149, - 44150, - 44151, - 44152, - 44153, - 44154, - 44155, - 44156, - 44157, - 44158, - 44159, - 44160, - 44161, - 44162, - 44163, - 44164, - 44165, - 44166, - 44167, - 44168, - 44169, - 44170, - 44171, - 44172, - 44173, - 44174, - 44175, - 44176, - 44177, - 44178, - 44179, - 44180, - 44181, - 44182, - 44183, - 44184, - 44185, - 44186, - 44187, - 44188, - 44189, - 44190, - 44191, - 44192, - 44193, - 44194, - 44195, - 44196, - 44197, - 44198, - 44199, - 44200, - 44201, - 44202, - 44203, - 44204, - 44205, - 44206, - 44207, - 44208, - 44209, - 44210, - 44211, - 44212, - 44213, - 44214, - 44215, - 44216, - 44217, - 44218, - 44219, - 44220, - 44221, - 44222, - 44223, - 44224, - 44225, - 44226, - 44227, - 44228, - 44229, - 44230, - 44231, - 44232, - 44233, - 44234, - 44235, - 44236, - 44237, - 44238, - 44239, - 44240, - 44241, - 44242, - 44243, - 44244, - 44245, - 44246, - 44247, - 44248, - 44249, - 44250, - 44251, - 44252, - 44253, - 44254, - 44255, - 44256, - 44257, - 44258, - 44259, - 44260, - 44261, - 44262, - 44263, - 44264, - 44265, - 44266, - 44267, - 44268, - 44269, - 44270, - 44271, - 44272, - 44273, - 44274, - 44275, - 44276, - 44277, - 44278, - 44279, - 44280, - 44281, - 44282, - 44283, - 44284, - 44285, - 44286, - 44287, - 44288, - 44289, - 44290, - 44291, - 44292, - 44293, - 44294, - 44295, - 44296, - 44297, - 44298, - 44299, - 44300, - 44301, - 44302, - 44303, - 44304, - 44305, - 44306, - 44307, - 44308, - 44309, - 44310, - 44311, - 44312, - 44313, - 44314, - 44315, - 44316, - 44317, - 44318, - 44319, - 44320, - 44321, - 44322, - 44323, - 44324, - 44325, - 44326, - 44327, - 44328, - 44329, - 44330, - 44331, - 44332, - 44333, - 44334, - 44335, - 44336, - 44337, - 44338, - 44339, - 44340, - 44341, - 44342, - 44343, - 44344, - 44345, - 44346, - 44347, - 44348, - 44349, - 44350, - 44351, - 44352, - 44353, - 44354, - 44355, - 44356, - 44357, - 44358, - 44359, - 44360, - 44361, - 44362, - 44363, - 44364, - 44365, - 44366, - 44367, - 44368, - 44369, - 44370, - 44371, - 44372, - 44373, - 44374, - 44375, - 44376, - 44377, - 44378, - 44379, - 44380, - 44381, - 44382, - 44383, - 44384, - 44385, - 44386, - 44387, - 44388, - 44389, - 44390, - 44391, - 44392, - 44393, - 44394, - 44395, - 44396, - 44397, - 44398, - 44399, - 44400, - 44401, - 44402, - 44403, - 44404, - 44405, - 44406, - 44407, - 44408, - 44409, - 44410, - 44411, - 44412, - 44413, - 44414, - 44415, - 44416, - 44417, - 44418, - 44419, - 44420, - 44421, - 44422, - 44423, - 44424, - 44425, - 44426, - 44427, - 44428, - 44429, - 44430, - 44431, - 44432, - 44433, - 44434, - 44435, - 44436, - 44437, - 44438, - 44439, - 44440, - 44441, - 44442, - 44443, - 44444, - 44445, - 44446, - 44447, - 44448, - 44449, - 44450, - 44451, - 44452, - 44453, - 44454, - 44455, - 44456, - 44457, - 44458, - 44459, - 44460, - 44461, - 44462, - 44463, - 44464, - 44465, - 44466, - 44467, - 44468, - 44469, - 44470, - 44471, - 44472, - 44473, - 44474, - 44475, - 44476, - 44477, - 44478, - 44479, - 44480, - 44481, - 44482, - 44483, - 44484, - 44485, - 44486, - 44487, - 44488, - 44489, - 44490, - 44491, - 44492, - 44493, - 44494, - 44495, - 44496, - 44497, - 44498, - 44499, - 44500, - 44501, - 44502, - 44503, - 44504, - 44505, - 44506, - 44507, - 44508, - 44509, - 44510, - 44511, - 44512, - 44513, - 44514, - 44515, - 44516, - 44517, - 44518, - 44519, - 44520, - 44521, - 44522, - 44523, - 44524, - 44525, - 44526, - 44527, - 44528, - 44529, - 44530, - 44531, - 44532, - 44533, - 44534, - 44535, - 44536, - 44537, - 44538, - 44539, - 44540, - 44541, - 44542, - 44543, - 44544, - 44545, - 44546, - 44547, - 44548, - 44549, - 44550, - 44551, - 44552, - 44553, - 44554, - 44555, - 44556, - 44557, - 44558, - 44559, - 44560, - 44561, - 44562, - 44563, - 44564, - 44565, - 44566, - 44567, - 44568, - 44569, - 44570, - 44571, - 44572, - 44573, - 44574, - 44575, - 44576, - 44577, - 44578, - 44579, - 44580, - 44581, - 44582, - 44583, - 44584, - 44585, - 44586, - 44587, - 44588, - 44589, - 44590, - 44591, - 44592, - 44593, - 44594, - 44595, - 44596, - 44597, - 44598, - 44599, - 44600, - 44601, - 44602, - 44603, - 44604, - 44605, - 44606, - 44607, - 44608, - 44609, - 44610, - 44611, - 44612, - 44613, - 44614, - 44615, - 44616, - 44617, - 44618, - 44619, - 44620, - 44621, - 44622, - 44623, - 44624, - 44625, - 44626, - 44627, - 44628, - 44629, - 44630, - 44631, - 44632, - 44633, - 44634, - 44635, - 44636, - 44637, - 44638, - 44639, - 44640, - 44641, - 44642, - 44643, - 44644, - 44645, - 44646, - 44647, - 44648, - 44649, - 44650, - 44651, - 44652, - 44653, - 44654, - 44655, - 44656, - 44657, - 44658, - 44659, - 44660, - 44661, - 44662, - 44663, - 44664, - 44665, - 44666, - 44667, - 44668, - 44669, - 44670, - 44671, - 44672, - 44673, - 44674, - 44675, - 44676, - 44677, - 44678, - 44679, - 44680, - 44681, - 44682, - 44683, - 44684, - 44685, - 44686, - 44687, - 44688, - 44689, - 44690, - 44691, - 44692, - 44693, - 44694, - 44695, - 44696, - 44697, - 44698, - 44699, - 44700, - 44701, - 44702, - 44703, - 44704, - 44705, - 44706, - 44707, - 44708, - 44709, - 44710, - 44711, - 44712, - 44713, - 44714, - 44715, - 44716, - 44717, - 44718, - 44719, - 44720, - 44721, - 44722, - 44723, - 44724, - 44725, - 44726, - 44727, - 44728, - 44729, - 44730, - 44731, - 44732, - 44733, - 44734, - 44735, - 44736, - 44737, - 44738, - 44739, - 44740, - 44741, - 44742, - 44743, - 44744, - 44745, - 44746, - 44747, - 44748, - 44749, - 44750, - 44751, - 44752, - 44753, - 44754, - 44755, - 44756, - 44757, - 44758, - 44759, - 44760, - 44761, - 44762, - 44763, - 44764, - 44765, - 44766, - 44767, - 44768, - 44769, - 44770, - 44771, - 44772, - 44773, - 44774, - 44775, - 44776, - 44777, - 44778, - 44779, - 44780, - 44781, - 44782, - 44783, - 44784, - 44785, - 44786, - 44787, - 44788, - 44789, - 44790, - 44791, - 44792, - 44793, - 44794, - 44795, - 44796, - 44797, - 44798, - 44799, - 44800, - 44801, - 44802, - 44803, - 44804, - 44805, - 44806, - 44807, - 44808, - 44809, - 44810, - 44811, - 44812, - 44813, - 44814, - 44815, - 44816, - 44817, - 44818, - 44819, - 44820, - 44821, - 44822, - 44823, - 44824, - 44825, - 44826, - 44827, - 44828, - 44829, - 44830, - 44831, - 44832, - 44833, - 44834, - 44835, - 44836, - 44837, - 44838, - 44839, - 44840, - 44841, - 44842, - 44843, - 44844, - 44845, - 44846, - 44847, - 44848, - 44849, - 44850, - 44851, - 44852, - 44853, - 44854, - 44855, - 44856, - 44857, - 44858, - 44859, - 44860, - 44861, - 44862, - 44863, - 44864, - 44865, - 44866, - 44867, - 44868, - 44869, - 44870, - 44871, - 44872, - 44873, - 44874, - 44875, - 44876, - 44877, - 44878, - 44879, - 44880, - 44881, - 44882, - 44883, - 44884, - 44885, - 44886, - 44887, - 44888, - 44889, - 44890, - 44891, - 44892, - 44893, - 44894, - 44895, - 44896, - 44897, - 44898, - 44899, - 44900, - 44901, - 44902, - 44903, - 44904, - 44905, - 44906, - 44907, - 44908, - 44909, - 44910, - 44911, - 44912, - 44913, - 44914, - 44915, - 44916, - 44917, - 44918, - 44919, - 44920, - 44921, - 44922, - 44923, - 44924, - 44925, - 44926, - 44927, - 44928, - 44929, - 44930, - 44931, - 44932, - 44933, - 44934, - 44935, - 44936, - 44937, - 44938, - 44939, - 44940, - 44941, - 44942, - 44943, - 44944, - 44945, - 44946, - 44947, - 44948, - 44949, - 44950, - 44951, - 44952, - 44953, - 44954, - 44955, - 44956, - 44957, - 44958, - 44959, - 44960, - 44961, - 44962, - 44963, - 44964, - 44965, - 44966, - 44967, - 44968, - 44969, - 44970, - 44971, - 44972, - 44973, - 44974, - 44975, - 44976, - 44977, - 44978, - 44979, - 44980, - 44981, - 44982, - 44983, - 44984, - 44985, - 44986, - 44987, - 44988, - 44989, - 44990, - 44991, - 44992, - 44993, - 44994, - 44995, - 44996, - 44997, - 44998, - 44999, - 45000, - 45001, - 45002, - 45003, - 45004, - 45005, - 45006, - 45007, - 45008, - 45009, - 45010, - 45011, - 45012, - 45013, - 45014, - 45015, - 45016, - 45017, - 45018, - 45019, - 45020, - 45021, - 45022, - 45023, - 45024, - 45025, - 45026, - 45027, - 45028, - 45029, - 45030, - 45031, - 45032, - 45033, - 45034, - 45035, - 45036, - 45037, - 45038, - 45039, - 45040, - 45041, - 45042, - 45043, - 45044, - 45045, - 45046, - 45047, - 45048, - 45049, - 45050, - 45051, - 45052, - 45053, - 45054, - 45055, - 45056, - 45057, - 45058, - 45059, - 45060, - 45061, - 45062, - 45063, - 45064, - 45065, - 45066, - 45067, - 45068, - 45069, - 45070, - 45071, - 45072, - 45073, - 45074, - 45075, - 45076, - 45077, - 45078, - 45079, - 45080, - 45081, - 45082, - 45083, - 45084, - 45085, - 45086, - 45087, - 45088, - 45089, - 45090, - 45091, - 45092, - 45093, - 45094, - 45095, - 45096, - 45097, - 45098, - 45099, - 45100, - 45101, - 45102, - 45103, - 45104, - 45105, - 45106, - 45107, - 45108, - 45109, - 45110, - 45111, - 45112, - 45113, - 45114, - 45115, - 45116, - 45117, - 45118, - 45119, - 45120, - 45121, - 45122, - 45123, - 45124, - 45125, - 45126, - 45127, - 45128, - 45129, - 45130, - 45131, - 45132, - 45133, - 45134, - 45135, - 45136, - 45137, - 45138, - 45139, - 45140, - 45141, - 45142, - 45143, - 45144, - 45145, - 45146, - 45147, - 45148, - 45149, - 45150, - 45151, - 45152, - 45153, - 45154, - 45155, - 45156, - 45157, - 45158, - 45159, - 45160, - 45161, - 45162, - 45163, - 45164, - 45165, - 45166, - 45167, - 45168, - 45169, - 45170, - 45171, - 45172, - 45173, - 45174, - 45175, - 45176, - 45177, - 45178, - 45179, - 45180, - 45181, - 45182, - 45183, - 45184, - 45185, - 45186, - 45187, - 45188, - 45189, - 45190, - 45191, - 45192, - 45193, - 45194, - 45195, - 45196, - 45197, - 45198, - 45199, - 45200, - 45201, - 45202, - 45203, - 45204, - 45205, - 45206, - 45207, - 45208, - 45209, - 45210, - 45211, - 45212, - 45213, - 45214, - 45215, - 45216, - 45217, - 45218, - 45219, - 45220, - 45221, - 45222, - 45223, - 45224, - 45225, - 45226, - 45227, - 45228, - 45229, - 45230, - 45231, - 45232, - 45233, - 45234, - 45235, - 45236, - 45237, - 45238, - 45239, - 45240, - 45241, - 45242, - 45243, - 45244, - 45245, - 45246, - 45247, - 45248, - 45249, - 45250, - 45251, - 45252, - 45253, - 45254, - 45255, - 45256, - 45257, - 45258, - 45259, - 45260, - 45261, - 45262, - 45263, - 45264, - 45265, - 45266, - 45267, - 45268, - 45269, - 45270, - 45271, - 45272, - 45273, - 45274, - 45275, - 45276, - 45277, - 45278, - 45279, - 45280, - 45281, - 45282, - 45283, - 45284, - 45285, - 45286, - 45287, - 45288, - 45289, - 45290, - 45291, - 45292, - 45293, - 45294, - 45295, - 45296, - 45297, - 45298, - 45299, - 45300, - 45301, - 45302, - 45303, - 45304, - 45305, - 45306, - 45307, - 45308, - 45309, - 45310, - 45311, - 45312, - 45313, - 45314, - 45315, - 45316, - 45317, - 45318, - 45319, - 45320, - 45321, - 45322, - 45323, - 45324, - 45325, - 45326, - 45327, - 45328, - 45329, - 45330, - 45331, - 45332, - 45333, - 45334, - 45335, - 45336, - 45337, - 45338, - 45339, - 45340, - 45341, - 45342, - 45343, - 45344, - 45345, - 45346, - 45347, - 45348, - 45349, - 45350, - 45351, - 45352, - 45353, - 45354, - 45355, - 45356, - 45357, - 45358, - 45359, - 45360, - 45361, - 45362, - 45363, - 45364, - 45365, - 45366, - 45367, - 45368, - 45369, - 45370, - 45371, - 45372, - 45373, - 45374, - 45375, - 45376, - 45377, - 45378, - 45379, - 45380, - 45381, - 45382, - 45383, - 45384, - 45385, - 45386, - 45387, - 45388, - 45389, - 45390, - 45391, - 45392, - 45393, - 45394, - 45395, - 45396, - 45397, - 45398, - 45399, - 45400, - 45401, - 45402, - 45403, - 45404, - 45405, - 45406, - 45407, - 45408, - 45409, - 45410, - 45411, - 45412, - 45413, - 45414, - 45415, - 45416, - 45417, - 45418, - 45419, - 45420, - 45421, - 45422, - 45423, - 45424, - 45425, - 45426, - 45427, - 45428, - 45429, - 45430, - 45431, - 45432, - 45433, - 45434, - 45435, - 45436, - 45437, - 45438, - 45439, - 45440, - 45441, - 45442, - 45443, - 45444, - 45445, - 45446, - 45447, - 45448, - 45449, - 45450, - 45451, - 45452, - 45453, - 45454, - 45455, - 45456, - 45457, - 45458, - 45459, - 45460, - 45461, - 45462, - 45463, - 45464, - 45465, - 45466, - 45467, - 45468, - 45469, - 45470, - 45471, - 45472, - 45473, - 45474, - 45475, - 45476, - 45477, - 45478, - 45479, - 45480, - 45481, - 45482, - 45483, - 45484, - 45485, - 45486, - 45487, - 45488, - 45489, - 45490, - 45491, - 45492, - 45493, - 45494, - 45495, - 45496, - 45497, - 45498, - 45499, - 45500, - 45501, - 45502, - 45503, - 45504, - 45505, - 45506, - 45507, - 45508, - 45509, - 45510, - 45511, - 45512, - 45513, - 45514, - 45515, - 45516, - 45517, - 45518, - 45519, - 45520, - 45521, - 45522, - 45523, - 45524, - 45525, - 45526, - 45527, - 45528, - 45529, - 45530, - 45531, - 45532, - 45533, - 45534, - 45535, - 45536, - 45537, - 45538, - 45539, - 45540, - 45541, - 45542, - 45543, - 45544, - 45545, - 45546, - 45547, - 45548, - 45549, - 45550, - 45551, - 45552, - 45553, - 45554, - 45555, - 45556, - 45557, - 45558, - 45559, - 45560, - 45561, - 45562, - 45563, - 45564, - 45565, - 45566, - 45567, - 45568, - 45569, - 45570, - 45571, - 45572, - 45573, - 45574, - 45575, - 45576, - 45577, - 45578, - 45579, - 45580, - 45581, - 45582, - 45583, - 45584, - 45585, - 45586, - 45587, - 45588, - 45589, - 45590, - 45591, - 45592, - 45593, - 45594, - 45595, - 45596, - 45597, - 45598, - 45599, - 45600, - 45601, - 45602, - 45603, - 45604, - 45605, - 45606, - 45607, - 45608, - 45609, - 45610, - 45611, - 45612, - 45613, - 45614, - 45615, - 45616, - 45617, - 45618, - 45619, - 45620, - 45621, - 45622, - 45623, - 45624, - 45625, - 45626, - 45627, - 45628, - 45629, - 45630, - 45631, - 45632, - 45633, - 45634, - 45635, - 45636, - 45637, - 45638, - 45639, - 45640, - 45641, - 45642, - 45643, - 45644, - 45645, - 45646, - 45647, - 45648, - 45649, - 45650, - 45651, - 45652, - 45653, - 45654, - 45655, - 45656, - 45657, - 45658, - 45659, - 45660, - 45661, - 45662, - 45663, - 45664, - 45665, - 45666, - 45667, - 45668, - 45669, - 45670, - 45671, - 45672, - 45673, - 45674, - 45675, - 45676, - 45677, - 45678, - 45679, - 45680, - 45681, - 45682, - 45683, - 45684, - 45685, - 45686, - 45687, - 45688, - 45689, - 45690, - 45691, - 45692, - 45693, - 45694, - 45695, - 45696, - 45697, - 45698, - 45699, - 45700, - 45701, - 45702, - 45703, - 45704, - 45705, - 45706, - 45707, - 45708, - 45709, - 45710, - 45711, - 45712, - 45713, - 45714, - 45715, - 45716, - 45717, - 45718, - 45719, - 45720, - 45721, - 45722, - 45723, - 45724, - 45725, - 45726, - 45727, - 45728, - 45729, - 45730, - 45731, - 45732, - 45733, - 45734, - 45735, - 45736, - 45737, - 45738, - 45739, - 45740, - 45741, - 45742, - 45743, - 45744, - 45745, - 45746, - 45747, - 45748, - 45749, - 45750, - 45751, - 45752, - 45753, - 45754, - 45755, - 45756, - 45757, - 45758, - 45759, - 45760, - 45761, - 45762, - 45763, - 45764, - 45765, - 45766, - 45767, - 45768, - 45769, - 45770, - 45771, - 45772, - 45773, - 45774, - 45775, - 45776, - 45777, - 45778, - 45779, - 45780, - 45781, - 45782, - 45783, - 45784, - 45785, - 45786, - 45787, - 45788, - 45789, - 45790, - 45791, - 45792, - 45793, - 45794, - 45795, - 45796, - 45797, - 45798, - 45799, - 45800, - 45801, - 45802, - 45803, - 45804, - 45805, - 45806, - 45807, - 45808, - 45809, - 45810, - 45811, - 45812, - 45813, - 45814, - 45815, - 45816, - 45817, - 45818, - 45819, - 45820, - 45821, - 45822, - 45823, - 45824, - 45825, - 45826, - 45827, - 45828, - 45829, - 45830, - 45831, - 45832, - 45833, - 45834, - 45835, - 45836, - 45837, - 45838, - 45839, - 45840, - 45841, - 45842, - 45843, - 45844, - 45845, - 45846, - 45847, - 45848, - 45849, - 45850, - 45851, - 45852, - 45853, - 45854, - 45855, - 45856, - 45857, - 45858, - 45859, - 45860, - 45861, - 45862, - 45863, - 45864, - 45865, - 45866, - 45867, - 45868, - 45869, - 45870, - 45871, - 45872, - 45873, - 45874, - 45875, - 45876, - 45877, - 45878, - 45879, - 45880, - 45881, - 45882, - 45883, - 45884, - 45885, - 45886, - 45887, - 45888, - 45889, - 45890, - 45891, - 45892, - 45893, - 45894, - 45895, - 45896, - 45897, - 45898, - 45899, - 45900, - 45901, - 45902, - 45903, - 45904, - 45905, - 45906, - 45907, - 45908, - 45909, - 45910, - 45911, - 45912, - 45913, - 45914, - 45915, - 45916, - 45917, - 45918, - 45919, - 45920, - 45921, - 45922, - 45923, - 45924, - 45925, - 45926, - 45927, - 45928, - 45929, - 45930, - 45931, - 45932, - 45933, - 45934, - 45935, - 45936, - 45937, - 45938, - 45939, - 45940, - 45941, - 45942, - 45943, - 45944, - 45945, - 45946, - 45947, - 45948, - 45949, - 45950, - 45951, - 45952, - 45953, - 45954, - 45955, - 45956, - 45957, - 45958, - 45959, - 45960, - 45961, - 45962, - 45963, - 45964, - 45965, - 45966, - 45967, - 45968, - 45969, - 45970, - 45971, - 45972, - 45973, - 45974, - 45975, - 45976, - 45977, - 45978, - 45979, - 45980, - 45981, - 45982, - 45983, - 45984, - 45985, - 45986, - 45987, - 45988, - 45989, - 45990, - 45991, - 45992, - 45993, - 45994, - 45995, - 45996, - 45997, - 45998, - 45999, - 46000, - 46001, - 46002, - 46003, - 46004, - 46005, - 46006, - 46007, - 46008, - 46009, - 46010, - 46011, - 46012, - 46013, - 46014, - 46015, - 46016, - 46017, - 46018, - 46019, - 46020, - 46021, - 46022, - 46023, - 46024, - 46025, - 46026, - 46027, - 46028, - 46029, - 46030, - 46031, - 46032, - 46033, - 46034, - 46035, - 46036, - 46037, - 46038, - 46039, - 46040, - 46041, - 46042, - 46043, - 46044, - 46045, - 46046, - 46047, - 46048, - 46049, - 46050, - 46051, - 46052, - 46053, - 46054, - 46055, - 46056, - 46057, - 46058, - 46059, - 46060, - 46061, - 46062, - 46063, - 46064, - 46065, - 46066, - 46067, - 46068, - 46069, - 46070, - 46071, - 46072, - 46073, - 46074, - 46075, - 46076, - 46077, - 46078, - 46079, - 46080, - 46081, - 46082, - 46083, - 46084, - 46085, - 46086, - 46087, - 46088, - 46089, - 46090, - 46091, - 46092, - 46093, - 46094, - 46095, - 46096, - 46097, - 46098, - 46099, - 46100, - 46101, - 46102, - 46103, - 46104, - 46105, - 46106, - 46107, - 46108, - 46109, - 46110, - 46111, - 46112, - 46113, - 46114, - 46115, - 46116, - 46117, - 46118, - 46119, - 46120, - 46121, - 46122, - 46123, - 46124, - 46125, - 46126, - 46127, - 46128, - 46129, - 46130, - 46131, - 46132, - 46133, - 46134, - 46135, - 46136, - 46137, - 46138, - 46139, - 46140, - 46141, - 46142, - 46143, - 46144, - 46145, - 46146, - 46147, - 46148, - 46149, - 46150, - 46151, - 46152, - 46153, - 46154, - 46155, - 46156, - 46157, - 46158, - 46159, - 46160, - 46161, - 46162, - 46163, - 46164, - 46165, - 46166, - 46167, - 46168, - 46169, - 46170, - 46171, - 46172, - 46173, - 46174, - 46175, - 46176, - 46177, - 46178, - 46179, - 46180, - 46181, - 46182, - 46183, - 46184, - 46185, - 46186, - 46187, - 46188, - 46189, - 46190, - 46191, - 46192, - 46193, - 46194, - 46195, - 46196, - 46197, - 46198, - 46199, - 46200, - 46201, - 46202, - 46203, - 46204, - 46205, - 46206, - 46207, - 46208, - 46209, - 46210, - 46211, - 46212, - 46213, - 46214, - 46215, - 46216, - 46217, - 46218, - 46219, - 46220, - 46221, - 46222, - 46223, - 46224, - 46225, - 46226, - 46227, - 46228, - 46229, - 46230, - 46231, - 46232, - 46233, - 46234, - 46235, - 46236, - 46237, - 46238, - 46239, - 46240, - 46241, - 46242, - 46243, - 46244, - 46245, - 46246, - 46247, - 46248, - 46249, - 46250, - 46251, - 46252, - 46253, - 46254, - 46255, - 46256, - 46257, - 46258, - 46259, - 46260, - 46261, - 46262, - 46263, - 46264, - 46265, - 46266, - 46267, - 46268, - 46269, - 46270, - 46271, - 46272, - 46273, - 46274, - 46275, - 46276, - 46277, - 46278, - 46279, - 46280, - 46281, - 46282, - 46283, - 46284, - 46285, - 46286, - 46287, - 46288, - 46289, - 46290, - 46291, - 46292, - 46293, - 46294, - 46295, - 46296, - 46297, - 46298, - 46299, - 46300, - 46301, - 46302, - 46303, - 46304, - 46305, - 46306, - 46307, - 46308, - 46309, - 46310, - 46311, - 46312, - 46313, - 46314, - 46315, - 46316, - 46317, - 46318, - 46319, - 46320, - 46321, - 46322, - 46323, - 46324, - 46325, - 46326, - 46327, - 46328, - 46329, - 46330, - 46331, - 46332, - 46333, - 46334, - 46335, - 46336, - 46337, - 46338, - 46339, - 46340, - 46341, - 46342, - 46343, - 46344, - 46345, - 46346, - 46347, - 46348, - 46349, - 46350, - 46351, - 46352, - 46353, - 46354, - 46355, - 46356, - 46357, - 46358, - 46359, - 46360, - 46361, - 46362, - 46363, - 46364, - 46365, - 46366, - 46367, - 46368, - 46369, - 46370, - 46371, - 46372, - 46373, - 46374, - 46375, - 46376, - 46377, - 46378, - 46379, - 46380, - 46381, - 46382, - 46383, - 46384, - 46385, - 46386, - 46387, - 46388, - 46389, - 46390, - 46391, - 46392, - 46393, - 46394, - 46395, - 46396, - 46397, - 46398, - 46399, - 46400, - 46401, - 46402, - 46403, - 46404, - 46405, - 46406, - 46407, - 46408, - 46409, - 46410, - 46411, - 46412, - 46413, - 46414, - 46415, - 46416, - 46417, - 46418, - 46419, - 46420, - 46421, - 46422, - 46423, - 46424, - 46425, - 46426, - 46427, - 46428, - 46429, - 46430, - 46431, - 46432, - 46433, - 46434, - 46435, - 46436, - 46437, - 46438, - 46439, - 46440, - 46441, - 46442, - 46443, - 46444, - 46445, - 46446, - 46447, - 46448, - 46449, - 46450, - 46451, - 46452, - 46453, - 46454, - 46455, - 46456, - 46457, - 46458, - 46459, - 46460, - 46461, - 46462, - 46463, - 46464, - 46465, - 46466, - 46467, - 46468, - 46469, - 46470, - 46471, - 46472, - 46473, - 46474, - 46475, - 46476, - 46477, - 46478, - 46479, - 46480, - 46481, - 46482, - 46483, - 46484, - 46485, - 46486, - 46487, - 46488, - 46489, - 46490, - 46491, - 46492, - 46493, - 46494, - 46495, - 46496, - 46497, - 46498, - 46499, - 46500, - 46501, - 46502, - 46503, - 46504, - 46505, - 46506, - 46507, - 46508, - 46509, - 46510, - 46511, - 46512, - 46513, - 46514, - 46515, - 46516, - 46517, - 46518, - 46519, - 46520, - 46521, - 46522, - 46523, - 46524, - 46525, - 46526, - 46527, - 46528, - 46529, - 46530, - 46531, - 46532, - 46533, - 46534, - 46535, - 46536, - 46537, - 46538, - 46539, - 46540, - 46541, - 46542, - 46543, - 46544, - 46545, - 46546, - 46547, - 46548, - 46549, - 46550, - 46551, - 46552, - 46553, - 46554, - 46555, - 46556, - 46557, - 46558, - 46559, - 46560, - 46561, - 46562, - 46563, - 46564, - 46565, - 46566, - 46567, - 46568, - 46569, - 46570, - 46571, - 46572, - 46573, - 46574, - 46575, - 46576, - 46577, - 46578, - 46579, - 46580, - 46581, - 46582, - 46583, - 46584, - 46585, - 46586, - 46587, - 46588, - 46589, - 46590, - 46591, - 46592, - 46593, - 46594, - 46595, - 46596, - 46597, - 46598, - 46599, - 46600, - 46601, - 46602, - 46603, - 46604, - 46605, - 46606, - 46607, - 46608, - 46609, - 46610, - 46611, - 46612, - 46613, - 46614, - 46615, - 46616, - 46617, - 46618, - 46619, - 46620, - 46621, - 46622, - 46623, - 46624, - 46625, - 46626, - 46627, - 46628, - 46629, - 46630, - 46631, - 46632, - 46633, - 46634, - 46635, - 46636, - 46637, - 46638, - 46639, - 46640, - 46641, - 46642, - 46643, - 46644, - 46645, - 46646, - 46647, - 46648, - 46649, - 46650, - 46651, - 46652, - 46653, - 46654, - 46655, - 46656, - 46657, - 46658, - 46659, - 46660, - 46661, - 46662, - 46663, - 46664, - 46665, - 46666, - 46667, - 46668, - 46669, - 46670, - 46671, - 46672, - 46673, - 46674, - 46675, - 46676, - 46677, - 46678, - 46679, - 46680, - 46681, - 46682, - 46683, - 46684, - 46685, - 46686, - 46687, - 46688, - 46689, - 46690, - 46691, - 46692, - 46693, - 46694, - 46695, - 46696, - 46697, - 46698, - 46699, - 46700, - 46701, - 46702, - 46703, - 46704, - 46705, - 46706, - 46707, - 46708, - 46709, - 46710, - 46711, - 46712, - 46713, - 46714, - 46715, - 46716, - 46717, - 46718, - 46719, - 46720, - 46721, - 46722, - 46723, - 46724, - 46725, - 46726, - 46727, - 46728, - 46729, - 46730, - 46731, - 46732, - 46733, - 46734, - 46735, - 46736, - 46737, - 46738, - 46739, - 46740, - 46741, - 46742, - 46743, - 46744, - 46745, - 46746, - 46747, - 46748, - 46749, - 46750, - 46751, - 46752, - 46753, - 46754, - 46755, - 46756, - 46757, - 46758, - 46759, - 46760, - 46761, - 46762, - 46763, - 46764, - 46765, - 46766, - 46767, - 46768, - 46769, - 46770, - 46771, - 46772, - 46773, - 46774, - 46775, - 46776, - 46777, - 46778, - 46779, - 46780, - 46781, - 46782, - 46783, - 46784, - 46785, - 46786, - 46787, - 46788, - 46789, - 46790, - 46791, - 46792, - 46793, - 46794, - 46795, - 46796, - 46797, - 46798, - 46799, - 46800, - 46801, - 46802, - 46803, - 46804, - 46805, - 46806, - 46807, - 46808, - 46809, - 46810, - 46811, - 46812, - 46813, - 46814, - 46815, - 46816, - 46817, - 46818, - 46819, - 46820, - 46821, - 46822, - 46823, - 46824, - 46825, - 46826, - 46827, - 46828, - 46829, - 46830, - 46831, - 46832, - 46833, - 46834, - 46835, - 46836, - 46837, - 46838, - 46839, - 46840, - 46841, - 46842, - 46843, - 46844, - 46845, - 46846, - 46847, - 46848, - 46849, - 46850, - 46851, - 46852, - 46853, - 46854, - 46855, - 46856, - 46857, - 46858, - 46859, - 46860, - 46861, - 46862, - 46863, - 46864, - 46865, - 46866, - 46867, - 46868, - 46869, - 46870, - 46871, - 46872, - 46873, - 46874, - 46875, - 46876, - 46877, - 46878, - 46879, - 46880, - 46881, - 46882, - 46883, - 46884, - 46885, - 46886, - 46887, - 46888, - 46889, - 46890, - 46891, - 46892, - 46893, - 46894, - 46895, - 46896, - 46897, - 46898, - 46899, - 46900, - 46901, - 46902, - 46903, - 46904, - 46905, - 46906, - 46907, - 46908, - 46909, - 46910, - 46911, - 46912, - 46913, - 46914, - 46915, - 46916, - 46917, - 46918, - 46919, - 46920, - 46921, - 46922, - 46923, - 46924, - 46925, - 46926, - 46927, - 46928, - 46929, - 46930, - 46931, - 46932, - 46933, - 46934, - 46935, - 46936, - 46937, - 46938, - 46939, - 46940, - 46941, - 46942, - 46943, - 46944, - 46945, - 46946, - 46947, - 46948, - 46949, - 46950, - 46951, - 46952, - 46953, - 46954, - 46955, - 46956, - 46957, - 46958, - 46959, - 46960, - 46961, - 46962, - 46963, - 46964, - 46965, - 46966, - 46967, - 46968, - 46969, - 46970, - 46971, - 46972, - 46973, - 46974, - 46975, - 46976, - 46977, - 46978, - 46979, - 46980, - 46981, - 46982, - 46983, - 46984, - 46985, - 46986, - 46987, - 46988, - 46989, - 46990, - 46991, - 46992, - 46993, - 46994, - 46995, - 46996, - 46997, - 46998, - 46999, - 47000, - 47001, - 47002, - 47003, - 47004, - 47005, - 47006, - 47007, - 47008, - 47009, - 47010, - 47011, - 47012, - 47013, - 47014, - 47015, - 47016, - 47017, - 47018, - 47019, - 47020, - 47021, - 47022, - 47023, - 47024, - 47025, - 47026, - 47027, - 47028, - 47029, - 47030, - 47031, - 47032, - 47033, - 47034, - 47035, - 47036, - 47037, - 47038, - 47039, - 47040, - 47041, - 47042, - 47043, - 47044, - 47045, - 47046, - 47047, - 47048, - 47049, - 47050, - 47051, - 47052, - 47053, - 47054, - 47055, - 47056, - 47057, - 47058, - 47059, - 47060, - 47061, - 47062, - 47063, - 47064, - 47065, - 47066, - 47067, - 47068, - 47069, - 47070, - 47071, - 47072, - 47073, - 47074, - 47075, - 47076, - 47077, - 47078, - 47079, - 47080, - 47081, - 47082, - 47083, - 47084, - 47085, - 47086, - 47087, - 47088, - 47089, - 47090, - 47091, - 47092, - 47093, - 47094, - 47095, - 47096, - 47097, - 47098, - 47099, - 47100, - 47101, - 47102, - 47103, - 47104, - 47105, - 47106, - 47107, - 47108, - 47109, - 47110, - 47111, - 47112, - 47113, - 47114, - 47115, - 47116, - 47117, - 47118, - 47119, - 47120, - 47121, - 47122, - 47123, - 47124, - 47125, - 47126, - 47127, - 47128, - 47129, - 47130, - 47131, - 47132, - 47133, - 47134, - 47135, - 47136, - 47137, - 47138, - 47139, - 47140, - 47141, - 47142, - 47143, - 47144, - 47145, - 47146, - 47147, - 47148, - 47149, - 47150, - 47151, - 47152, - 47153, - 47154, - 47155, - 47156, - 47157, - 47158, - 47159, - 47160, - 47161, - 47162, - 47163, - 47164, - 47165, - 47166, - 47167, - 47168, - 47169, - 47170, - 47171, - 47172, - 47173, - 47174, - 47175, - 47176, - 47177, - 47178, - 47179, - 47180, - 47181, - 47182, - 47183, - 47184, - 47185, - 47186, - 47187, - 47188, - 47189, - 47190, - 47191, - 47192, - 47193, - 47194, - 47195, - 47196, - 47197, - 47198, - 47199, - 47200, - 47201, - 47202, - 47203, - 47204, - 47205, - 47206, - 47207, - 47208, - 47209, - 47210, - 47211, - 47212, - 47213, - 47214, - 47215, - 47216, - 47217, - 47218, - 47219, - 47220, - 47221, - 47222, - 47223, - 47224, - 47225, - 47226, - 47227, - 47228, - 47229, - 47230, - 47231, - 47232, - 47233, - 47234, - 47235, - 47236, - 47237, - 47238, - 47239, - 47240, - 47241, - 47242, - 47243, - 47244, - 47245, - 47246, - 47247, - 47248, - 47249, - 47250, - 47251, - 47252, - 47253, - 47254, - 47255, - 47256, - 47257, - 47258, - 47259, - 47260, - 47261, - 47262, - 47263, - 47264, - 47265, - 47266, - 47267, - 47268, - 47269, - 47270, - 47271, - 47272, - 47273, - 47274, - 47275, - 47276, - 47277, - 47278, - 47279, - 47280, - 47281, - 47282, - 47283, - 47284, - 47285, - 47286, - 47287, - 47288, - 47289, - 47290, - 47291, - 47292, - 47293, - 47294, - 47295, - 47296, - 47297, - 47298, - 47299, - 47300, - 47301, - 47302, - 47303, - 47304, - 47305, - 47306, - 47307, - 47308, - 47309, - 47310, - 47311, - 47312, - 47313, - 47314, - 47315, - 47316, - 47317, - 47318, - 47319, - 47320, - 47321, - 47322, - 47323, - 47324, - 47325, - 47326, - 47327, - 47328, - 47329, - 47330, - 47331, - 47332, - 47333, - 47334, - 47335, - 47336, - 47337, - 47338, - 47339, - 47340, - 47341, - 47342, - 47343, - 47344, - 47345, - 47346, - 47347, - 47348, - 47349, - 47350, - 47351, - 47352, - 47353, - 47354, - 47355, - 47356, - 47357, - 47358, - 47359, - 47360, - 47361, - 47362, - 47363, - 47364, - 47365, - 47366, - 47367, - 47368, - 47369, - 47370, - 47371, - 47372, - 47373, - 47374, - 47375, - 47376, - 47377, - 47378, - 47379, - 47380, - 47381, - 47382, - 47383, - 47384, - 47385, - 47386, - 47387, - 47388, - 47389, - 47390, - 47391, - 47392, - 47393, - 47394, - 47395, - 47396, - 47397, - 47398, - 47399, - 47400, - 47401, - 47402, - 47403, - 47404, - 47405, - 47406, - 47407, - 47408, - 47409, - 47410, - 47411, - 47412, - 47413, - 47414, - 47415, - 47416, - 47417, - 47418, - 47419, - 47420, - 47421, - 47422, - 47423, - 47424, - 47425, - 47426, - 47427, - 47428, - 47429, - 47430, - 47431, - 47432, - 47433, - 47434, - 47435, - 47436, - 47437, - 47438, - 47439, - 47440, - 47441, - 47442, - 47443, - 47444, - 47445, - 47446, - 47447, - 47448, - 47449, - 47450, - 47451, - 47452, - 47453, - 47454, - 47455, - 47456, - 47457, - 47458, - 47459, - 47460, - 47461, - 47462, - 47463, - 47464, - 47465, - 47466, - 47467, - 47468, - 47469, - 47470, - 47471, - 47472, - 47473, - 47474, - 47475, - 47476, - 47477, - 47478, - 47479, - 47480, - 47481, - 47482, - 47483, - 47484, - 47485, - 47486, - 47487, - 47488, - 47489, - 47490, - 47491, - 47492, - 47493, - 47494, - 47495, - 47496, - 47497, - 47498, - 47499, - 47500, - 47501, - 47502, - 47503, - 47504, - 47505, - 47506, - 47507, - 47508, - 47509, - 47510, - 47511, - 47512, - 47513, - 47514, - 47515, - 47516, - 47517, - 47518, - 47519, - 47520, - 47521, - 47522, - 47523, - 47524, - 47525, - 47526, - 47527, - 47528, - 47529, - 47530, - 47531, - 47532, - 47533, - 47534, - 47535, - 47536, - 47537, - 47538, - 47539, - 47540, - 47541, - 47542, - 47543, - 47544, - 47545, - 47546, - 47547, - 47548, - 47549, - 47550, - 47551, - 47552, - 47553, - 47554, - 47555, - 47556, - 47557, - 47558, - 47559, - 47560, - 47561, - 47562, - 47563, - 47564, - 47565, - 47566, - 47567, - 47568, - 47569, - 47570, - 47571, - 47572, - 47573, - 47574, - 47575, - 47576, - 47577, - 47578, - 47579, - 47580, - 47581, - 47582, - 47583, - 47584, - 47585, - 47586, - 47587, - 47588, - 47589, - 47590, - 47591, - 47592, - 47593, - 47594, - 47595, - 47596, - 47597, - 47598, - 47599, - 47600, - 47601, - 47602, - 47603, - 47604, - 47605, - 47606, - 47607, - 47608, - 47609, - 47610, - 47611, - 47612, - 47613, - 47614, - 47615, - 47616, - 47617, - 47618, - 47619, - 47620, - 47621, - 47622, - 47623, - 47624, - 47625, - 47626, - 47627, - 47628, - 47629, - 47630, - 47631, - 47632, - 47633, - 47634, - 47635, - 47636, - 47637, - 47638, - 47639, - 47640, - 47641, - 47642, - 47643, - 47644, - 47645, - 47646, - 47647, - 47648, - 47649, - 47650, - 47651, - 47652, - 47653, - 47654, - 47655, - 47656, - 47657, - 47658, - 47659, - 47660, - 47661, - 47662, - 47663, - 47664, - 47665, - 47666, - 47667, - 47668, - 47669, - 47670, - 47671, - 47672, - 47673, - 47674, - 47675, - 47676, - 47677, - 47678, - 47679, - 47680, - 47681, - 47682, - 47683, - 47684, - 47685, - 47686, - 47687, - 47688, - 47689, - 47690, - 47691, - 47692, - 47693, - 47694, - 47695, - 47696, - 47697, - 47698, - 47699, - 47700, - 47701, - 47702, - 47703, - 47704, - 47705, - 47706, - 47707, - 47708, - 47709, - 47710, - 47711, - 47712, - 47713, - 47714, - 47715, - 47716, - 47717, - 47718, - 47719, - 47720, - 47721, - 47722, - 47723, - 47724, - 47725, - 47726, - 47727, - 47728, - 47729, - 47730, - 47731, - 47732, - 47733, - 47734, - 47735, - 47736, - 47737, - 47738, - 47739, - 47740, - 47741, - 47742, - 47743, - 47744, - 47745, - 47746, - 47747, - 47748, - 47749, - 47750, - 47751, - 47752, - 47753, - 47754, - 47755, - 47756, - 47757, - 47758, - 47759, - 47760, - 47761, - 47762, - 47763, - 47764, - 47765, - 47766, - 47767, - 47768, - 47769, - 47770, - 47771, - 47772, - 47773, - 47774, - 47775, - 47776, - 47777, - 47778, - 47779, - 47780, - 47781, - 47782, - 47783, - 47784, - 47785, - 47786, - 47787, - 47788, - 47789, - 47790, - 47791, - 47792, - 47793, - 47794, - 47795, - 47796, - 47797, - 47798, - 47799, - 47800, - 47801, - 47802, - 47803, - 47804, - 47805, - 47806, - 47807, - 47808, - 47809, - 47810, - 47811, - 47812, - 47813, - 47814, - 47815, - 47816, - 47817, - 47818, - 47819, - 47820, - 47821, - 47822, - 47823, - 47824, - 47825, - 47826, - 47827, - 47828, - 47829, - 47830, - 47831, - 47832, - 47833, - 47834, - 47835, - 47836, - 47837, - 47838, - 47839, - 47840, - 47841, - 47842, - 47843, - 47844, - 47845, - 47846, - 47847, - 47848, - 47849, - 47850, - 47851, - 47852, - 47853, - 47854, - 47855, - 47856, - 47857, - 47858, - 47859, - 47860, - 47861, - 47862, - 47863, - 47864, - 47865, - 47866, - 47867, - 47868, - 47869, - 47870, - 47871, - 47872, - 47873, - 47874, - 47875, - 47876, - 47877, - 47878, - 47879, - 47880, - 47881, - 47882, - 47883, - 47884, - 47885, - 47886, - 47887, - 47888, - 47889, - 47890, - 47891, - 47892, - 47893, - 47894, - 47895, - 47896, - 47897, - 47898, - 47899, - 47900, - 47901, - 47902, - 47903, - 47904, - 47905, - 47906, - 47907, - 47908, - 47909, - 47910, - 47911, - 47912, - 47913, - 47914, - 47915, - 47916, - 47917, - 47918, - 47919, - 47920, - 47921, - 47922, - 47923, - 47924, - 47925, - 47926, - 47927, - 47928, - 47929, - 47930, - 47931, - 47932, - 47933, - 47934, - 47935, - 47936, - 47937, - 47938, - 47939, - 47940, - 47941, - 47942, - 47943, - 47944, - 47945, - 47946, - 47947, - 47948, - 47949, - 47950, - 47951, - 47952, - 47953, - 47954, - 47955, - 47956, - 47957, - 47958, - 47959, - 47960, - 47961, - 47962, - 47963, - 47964, - 47965, - 47966, - 47967, - 47968, - 47969, - 47970, - 47971, - 47972, - 47973, - 47974, - 47975, - 47976, - 47977, - 47978, - 47979, - 47980, - 47981, - 47982, - 47983, - 47984, - 47985, - 47986, - 47987, - 47988, - 47989, - 47990, - 47991, - 47992, - 47993, - 47994, - 47995, - 47996, - 47997, - 47998, - 47999, - 48000, - 48001, - 48002, - 48003, - 48004, - 48005, - 48006, - 48007, - 48008, - 48009, - 48010, - 48011, - 48012, - 48013, - 48014, - 48015, - 48016, - 48017, - 48018, - 48019, - 48020, - 48021, - 48022, - 48023, - 48024, - 48025, - 48026, - 48027, - 48028, - 48029, - 48030, - 48031, - 48032, - 48033, - 48034, - 48035, - 48036, - 48037, - 48038, - 48039, - 48040, - 48041, - 48042, - 48043, - 48044, - 48045, - 48046, - 48047, - 48048, - 48049, - 48050, - 48051, - 48052, - 48053, - 48054, - 48055, - 48056, - 48057, - 48058, - 48059, - 48060, - 48061, - 48062, - 48063, - 48064, - 48065, - 48066, - 48067, - 48068, - 48069, - 48070, - 48071, - 48072, - 48073, - 48074, - 48075, - 48076, - 48077, - 48078, - 48079, - 48080, - 48081, - 48082, - 48083, - 48084, - 48085, - 48086, - 48087, - 48088, - 48089, - 48090, - 48091, - 48092, - 48093, - 48094, - 48095, - 48096, - 48097, - 48098, - 48099, - 48100, - 48101, - 48102, - 48103, - 48104, - 48105, - 48106, - 48107, - 48108, - 48109, - 48110, - 48111, - 48112, - 48113, - 48114, - 48115, - 48116, - 48117, - 48118, - 48119, - 48120, - 48121, - 48122, - 48123, - 48124, - 48125, - 48126, - 48127, - 48128, - 48129, - 48130, - 48131, - 48132, - 48133, - 48134, - 48135, - 48136, - 48137, - 48138, - 48139, - 48140, - 48141, - 48142, - 48143, - 48144, - 48145, - 48146, - 48147, - 48148, - 48149, - 48150, - 48151, - 48152, - 48153, - 48154, - 48155, - 48156, - 48157, - 48158, - 48159, - 48160, - 48161, - 48162, - 48163, - 48164, - 48165, - 48166, - 48167, - 48168, - 48169, - 48170, - 48171, - 48172, - 48173, - 48174, - 48175, - 48176, - 48177, - 48178, - 48179, - 48180, - 48181, - 48182, - 48183, - 48184, - 48185, - 48186, - 48187, - 48188, - 48189, - 48190, - 48191, - 48192, - 48193, - 48194, - 48195, - 48196, - 48197, - 48198, - 48199, - 48200, - 48201, - 48202, - 48203, - 48204, - 48205, - 48206, - 48207, - 48208, - 48209, - 48210, - 48211, - 48212, - 48213, - 48214, - 48215, - 48216, - 48217, - 48218, - 48219, - 48220, - 48221, - 48222, - 48223, - 48224, - 48225, - 48226, - 48227, - 48228, - 48229, - 48230, - 48231, - 48232, - 48233, - 48234, - 48235, - 48236, - 48237, - 48238, - 48239, - 48240, - 48241, - 48242, - 48243, - 48244, - 48245, - 48246, - 48247, - 48248, - 48249, - 48250, - 48251, - 48252, - 48253, - 48254, - 48255, - 48256, - 48257, - 48258, - 48259, - 48260, - 48261, - 48262, - 48263, - 48264, - 48265, - 48266, - 48267, - 48268, - 48269, - 48270, - 48271, - 48272, - 48273, - 48274, - 48275, - 48276, - 48277, - 48278, - 48279, - 48280, - 48281, - 48282, - 48283, - 48284, - 48285, - 48286, - 48287, - 48288, - 48289, - 48290, - 48291, - 48292, - 48293, - 48294, - 48295, - 48296, - 48297, - 48298, - 48299, - 48300, - 48301, - 48302, - 48303, - 48304, - 48305, - 48306, - 48307, - 48308, - 48309, - 48310, - 48311, - 48312, - 48313, - 48314, - 48315, - 48316, - 48317, - 48318, - 48319, - 48320, - 48321, - 48322, - 48323, - 48324, - 48325, - 48326, - 48327, - 48328, - 48329, - 48330, - 48331, - 48332, - 48333, - 48334, - 48335, - 48336, - 48337, - 48338, - 48339, - 48340, - 48341, - 48342, - 48343, - 48344, - 48345, - 48346, - 48347, - 48348, - 48349, - 48350, - 48351, - 48352, - 48353, - 48354, - 48355, - 48356, - 48357, - 48358, - 48359, - 48360, - 48361, - 48362, - 48363, - 48364, - 48365, - 48366, - 48367, - 48368, - 48369, - 48370, - 48371, - 48372, - 48373, - 48374, - 48375, - 48376, - 48377, - 48378, - 48379, - 48380, - 48381, - 48382, - 48383, - 48384, - 48385, - 48386, - 48387, - 48388, - 48389, - 48390, - 48391, - 48392, - 48393, - 48394, - 48395, - 48396, - 48397, - 48398, - 48399, - 48400, - 48401, - 48402, - 48403, - 48404, - 48405, - 48406, - 48407, - 48408, - 48409, - 48410, - 48411, - 48412, - 48413, - 48414, - 48415, - 48416, - 48417, - 48418, - 48419, - 48420, - 48421, - 48422, - 48423, - 48424, - 48425, - 48426, - 48427, - 48428, - 48429, - 48430, - 48431, - 48432, - 48433, - 48434, - 48435, - 48436, - 48437, - 48438, - 48439, - 48440, - 48441, - 48442, - 48443, - 48444, - 48445, - 48446, - 48447, - 48448, - 48449, - 48450, - 48451, - 48452, - 48453, - 48454, - 48455, - 48456, - 48457, - 48458, - 48459, - 48460, - 48461, - 48462, - 48463, - 48464, - 48465, - 48466, - 48467, - 48468, - 48469, - 48470, - 48471, - 48472, - 48473, - 48474, - 48475, - 48476, - 48477, - 48478, - 48479, - 48480, - 48481, - 48482, - 48483, - 48484, - 48485, - 48486, - 48487, - 48488, - 48489, - 48490, - 48491, - 48492, - 48493, - 48494, - 48495, - 48496, - 48497, - 48498, - 48499, - 48500, - 48501, - 48502, - 48503, - 48504, - 48505, - 48506, - 48507, - 48508, - 48509, - 48510, - 48511, - 48512, - 48513, - 48514, - 48515, - 48516, - 48517, - 48518, - 48519, - 48520, - 48521, - 48522, - 48523, - 48524, - 48525, - 48526, - 48527, - 48528, - 48529, - 48530, - 48531, - 48532, - 48533, - 48534, - 48535, - 48536, - 48537, - 48538, - 48539, - 48540, - 48541, - 48542, - 48543, - 48544, - 48545, - 48546, - 48547, - 48548, - 48549, - 48550, - 48551, - 48552, - 48553, - 48554, - 48555, - 48556, - 48557, - 48558, - 48559, - 48560, - 48561, - 48562, - 48563, - 48564, - 48565, - 48566, - 48567, - 48568, - 48569, - 48570, - 48571, - 48572, - 48573, - 48574, - 48575, - 48576, - 48577, - 48578, - 48579, - 48580, - 48581, - 48582, - 48583, - 48584, - 48585, - 48586, - 48587, - 48588, - 48589, - 48590, - 48591, - 48592, - 48593, - 48594, - 48595, - 48596, - 48597, - 48598, - 48599, - 48600, - 48601, - 48602, - 48603, - 48604, - 48605, - 48606, - 48607, - 48608, - 48609, - 48610, - 48611, - 48612, - 48613, - 48614, - 48615, - 48616, - 48617, - 48618, - 48619, - 48620, - 48621, - 48622, - 48623, - 48624, - 48625, - 48626, - 48627, - 48628, - 48629, - 48630, - 48631, - 48632, - 48633, - 48634, - 48635, - 48636, - 48637, - 48638, - 48639, - 48640, - 48641, - 48642, - 48643, - 48644, - 48645, - 48646, - 48647, - 48648, - 48649, - 48650, - 48651, - 48652, - 48653, - 48654, - 48655, - 48656, - 48657, - 48658, - 48659, - 48660, - 48661, - 48662, - 48663, - 48664, - 48665, - 48666, - 48667, - 48668, - 48669, - 48670, - 48671, - 48672, - 48673, - 48674, - 48675, - 48676, - 48677, - 48678, - 48679, - 48680, - 48681, - 48682, - 48683, - 48684, - 48685, - 48686, - 48687, - 48688, - 48689, - 48690, - 48691, - 48692, - 48693, - 48694, - 48695, - 48696, - 48697, - 48698, - 48699, - 48700, - 48701, - 48702, - 48703, - 48704, - 48705, - 48706, - 48707, - 48708, - 48709, - 48710, - 48711, - 48712, - 48713, - 48714, - 48715, - 48716, - 48717, - 48718, - 48719, - 48720, - 48721, - 48722, - 48723, - 48724, - 48725, - 48726, - 48727, - 48728, - 48729, - 48730, - 48731, - 48732, - 48733, - 48734, - 48735, - 48736, - 48737, - 48738, - 48739, - 48740, - 48741, - 48742, - 48743, - 48744, - 48745, - 48746, - 48747, - 48748, - 48749, - 48750, - 48751, - 48752, - 48753, - 48754, - 48755, - 48756, - 48757, - 48758, - 48759, - 48760, - 48761, - 48762, - 48763, - 48764, - 48765, - 48766, - 48767, - 48768, - 48769, - 48770, - 48771, - 48772, - 48773, - 48774, - 48775, - 48776, - 48777, - 48778, - 48779, - 48780, - 48781, - 48782, - 48783, - 48784, - 48785, - 48786, - 48787, - 48788, - 48789, - 48790, - 48791, - 48792, - 48793, - 48794, - 48795, - 48796, - 48797, - 48798, - 48799, - 48800, - 48801, - 48802, - 48803, - 48804, - 48805, - 48806, - 48807, - 48808, - 48809, - 48810, - 48811, - 48812, - 48813, - 48814, - 48815, - 48816, - 48817, - 48818, - 48819, - 48820, - 48821, - 48822, - 48823, - 48824, - 48825, - 48826, - 48827, - 48828, - 48829, - 48830, - 48831, - 48832, - 48833, - 48834, - 48835, - 48836, - 48837, - 48838, - 48839, - 48840, - 48841, - 48842, - 48843, - 48844, - 48845, - 48846, - 48847, - 48848, - 48849, - 48850, - 48851, - 48852, - 48853, - 48854, - 48855, - 48856, - 48857, - 48858, - 48859, - 48860, - 48861, - 48862, - 48863, - 48864, - 48865, - 48866, - 48867, - 48868, - 48869, - 48870, - 48871, - 48872, - 48873, - 48874, - 48875, - 48876, - 48877, - 48878, - 48879, - 48880, - 48881, - 48882, - 48883, - 48884, - 48885, - 48886, - 48887, - 48888, - 48889, - 48890, - 48891, - 48892, - 48893, - 48894, - 48895, - 48896, - 48897, - 48898, - 48899, - 48900, - 48901, - 48902, - 48903, - 48904, - 48905, - 48906, - 48907, - 48908, - 48909, - 48910, - 48911, - 48912, - 48913, - 48914, - 48915, - 48916, - 48917, - 48918, - 48919, - 48920, - 48921, - 48922, - 48923, - 48924, - 48925, - 48926, - 48927, - 48928, - 48929, - 48930, - 48931, - 48932, - 48933, - 48934, - 48935, - 48936, - 48937, - 48938, - 48939, - 48940, - 48941, - 48942, - 48943, - 48944, - 48945, - 48946, - 48947, - 48948, - 48949, - 48950, - 48951, - 48952, - 48953, - 48954, - 48955, - 48956, - 48957, - 48958, - 48959, - 48960, - 48961, - 48962, - 48963, - 48964, - 48965, - 48966, - 48967, - 48968, - 48969, - 48970, - 48971, - 48972, - 48973, - 48974, - 48975, - 48976, - 48977, - 48978, - 48979, - 48980, - 48981, - 48982, - 48983, - 48984, - 48985, - 48986, - 48987, - 48988, - 48989, - 48990, - 48991, - 48992, - 48993, - 48994, - 48995, - 48996, - 48997, - 48998, - 48999, - 49000, - 49001, - 49002, - 49003, - 49004, - 49005, - 49006, - 49007, - 49008, - 49009, - 49010, - 49011, - 49012, - 49013, - 49014, - 49015, - 49016, - 49017, - 49018, - 49019, - 49020, - 49021, - 49022, - 49023, - 49024, - 49025, - 49026, - 49027, - 49028, - 49029, - 49030, - 49031, - 49032, - 49033, - 49034, - 49035, - 49036, - 49037, - 49038, - 49039, - 49040, - 49041, - 49042, - 49043, - 49044, - 49045, - 49046, - 49047, - 49048, - 49049, - 49050, - 49051, - 49052, - 49053, - 49054, - 49055, - 49056, - 49057, - 49058, - 49059, - 49060, - 49061, - 49062, - 49063, - 49064, - 49065, - 49066, - 49067, - 49068, - 49069, - 49070, - 49071, - 49072, - 49073, - 49074, - 49075, - 49076, - 49077, - 49078, - 49079, - 49080, - 49081, - 49082, - 49083, - 49084, - 49085, - 49086, - 49087, - 49088, - 49089, - 49090, - 49091, - 49092, - 49093, - 49094, - 49095, - 49096, - 49097, - 49098, - 49099, - 49100, - 49101, - 49102, - 49103, - 49104, - 49105, - 49106, - 49107, - 49108, - 49109, - 49110, - 49111, - 49112, - 49113, - 49114, - 49115, - 49116, - 49117, - 49118, - 49119, - 49120, - 49121, - 49122, - 49123, - 49124, - 49125, - 49126, - 49127, - 49128, - 49129, - 49130, - 49131, - 49132, - 49133, - 49134, - 49135, - 49136, - 49137, - 49138, - 49139, - 49140, - 49141, - 49142, - 49143, - 49144, - 49145, - 49146, - 49147, - 49148, - 49149, - 49150, - 49151, - 49152, - 49153, - 49154, - 49155, - 49156, - 49157, - 49158, - 49159, - 49160, - 49161, - 49162, - 49163, - 49164, - 49165, - 49166, - 49167, - 49168, - 49169, - 49170, - 49171, - 49172, - 49173, - 49174, - 49175, - 49176, - 49177, - 49178, - 49179, - 49180, - 49181, - 49182, - 49183, - 49184, - 49185, - 49186, - 49187, - 49188, - 49189, - 49190, - 49191, - 49192, - 49193, - 49194, - 49195, - 49196, - 49197, - 49198, - 49199, - 49200, - 49201, - 49202, - 49203, - 49204, - 49205, - 49206, - 49207, - 49208, - 49209, - 49210, - 49211, - 49212, - 49213, - 49214, - 49215, - 49216, - 49217, - 49218, - 49219, - 49220, - 49221, - 49222, - 49223, - 49224, - 49225, - 49226, - 49227, - 49228, - 49229, - 49230, - 49231, - 49232, - 49233, - 49234, - 49235, - 49236, - 49237, - 49238, - 49239, - 49240, - 49241, - 49242, - 49243, - 49244, - 49245, - 49246, - 49247, - 49248, - 49249, - 49250, - 49251, - 49252, - 49253, - 49254, - 49255, - 49256, - 49257, - 49258, - 49259, - 49260, - 49261, - 49262, - 49263, - 49264, - 49265, - 49266, - 49267, - 49268, - 49269, - 49270, - 49271, - 49272, - 49273, - 49274, - 49275, - 49276, - 49277, - 49278, - 49279, - 49280, - 49281, - 49282, - 49283, - 49284, - 49285, - 49286, - 49287, - 49288, - 49289, - 49290, - 49291, - 49292, - 49293, - 49294, - 49295, - 49296, - 49297, - 49298, - 49299, - 49300, - 49301, - 49302, - 49303, - 49304, - 49305, - 49306, - 49307, - 49308, - 49309, - 49310, - 49311, - 49312, - 49313, - 49314, - 49315, - 49316, - 49317, - 49318, - 49319, - 49320, - 49321, - 49322, - 49323, - 49324, - 49325, - 49326, - 49327, - 49328, - 49329, - 49330, - 49331, - 49332, - 49333, - 49334, - 49335, - 49336, - 49337, - 49338, - 49339, - 49340, - 49341, - 49342, - 49343, - 49344, - 49345, - 49346, - 49347, - 49348, - 49349, - 49350, - 49351, - 49352, - 49353, - 49354, - 49355, - 49356, - 49357, - 49358, - 49359, - 49360, - 49361, - 49362, - 49363, - 49364, - 49365, - 49366, - 49367, - 49368, - 49369, - 49370, - 49371, - 49372, - 49373, - 49374, - 49375, - 49376, - 49377, - 49378, - 49379, - 49380, - 49381, - 49382, - 49383, - 49384, - 49385, - 49386, - 49387, - 49388, - 49389, - 49390, - 49391, - 49392, - 49393, - 49394, - 49395, - 49396, - 49397, - 49398, - 49399, - 49400, - 49401, - 49402, - 49403, - 49404, - 49405, - 49406, - 49407, - 49408, - 49409, - 49410, - 49411, - 49412, - 49413, - 49414, - 49415, - 49416, - 49417, - 49418, - 49419, - 49420, - 49421, - 49422, - 49423, - 49424, - 49425, - 49426, - 49427, - 49428, - 49429, - 49430, - 49431, - 49432, - 49433, - 49434, - 49435, - 49436, - 49437, - 49438, - 49439, - 49440, - 49441, - 49442, - 49443, - 49444, - 49445, - 49446, - 49447, - 49448, - 49449, - 49450, - 49451, - 49452, - 49453, - 49454, - 49455, - 49456, - 49457, - 49458, - 49459, - 49460, - 49461, - 49462, - 49463, - 49464, - 49465, - 49466, - 49467, - 49468, - 49469, - 49470, - 49471, - 49472, - 49473, - 49474, - 49475, - 49476, - 49477, - 49478, - 49479, - 49480, - 49481, - 49482, - 49483, - 49484, - 49485, - 49486, - 49487, - 49488, - 49489, - 49490, - 49491, - 49492, - 49493, - 49494, - 49495, - 49496, - 49497, - 49498, - 49499, - 49500, - 49501, - 49502, - 49503, - 49504, - 49505, - 49506, - 49507, - 49508, - 49509, - 49510, - 49511, - 49512, - 49513, - 49514, - 49515, - 49516, - 49517, - 49518, - 49519, - 49520, - 49521, - 49522, - 49523, - 49524, - 49525, - 49526, - 49527, - 49528, - 49529, - 49530, - 49531, - 49532, - 49533, - 49534, - 49535, - 49536, - 49537, - 49538, - 49539, - 49540, - 49541, - 49542, - 49543, - 49544, - 49545, - 49546, - 49547, - 49548, - 49549, - 49550, - 49551, - 49552, - 49553, - 49554, - 49555, - 49556, - 49557, - 49558, - 49559, - 49560, - 49561, - 49562, - 49563, - 49564, - 49565, - 49566, - 49567, - 49568, - 49569, - 49570, - 49571, - 49572, - 49573, - 49574, - 49575, - 49576, - 49577, - 49578, - 49579, - 49580, - 49581, - 49582, - 49583, - 49584, - 49585, - 49586, - 49587, - 49588, - 49589, - 49590, - 49591, - 49592, - 49593, - 49594, - 49595, - 49596, - 49597, - 49598, - 49599, - 49600, - 49601, - 49602, - 49603, - 49604, - 49605, - 49606, - 49607, - 49608, - 49609, - 49610, - 49611, - 49612, - 49613, - 49614, - 49615, - 49616, - 49617, - 49618, - 49619, - 49620, - 49621, - 49622, - 49623, - 49624, - 49625, - 49626, - 49627, - 49628, - 49629, - 49630, - 49631, - 49632, - 49633, - 49634, - 49635, - 49636, - 49637, - 49638, - 49639, - 49640, - 49641, - 49642, - 49643, - 49644, - 49645, - 49646, - 49647, - 49648, - 49649, - 49650, - 49651, - 49652, - 49653, - 49654, - 49655, - 49656, - 49657, - 49658, - 49659, - 49660, - 49661, - 49662, - 49663, - 49664, - 49665, - 49666, - 49667, - 49668, - 49669, - 49670, - 49671, - 49672, - 49673, - 49674, - 49675, - 49676, - 49677, - 49678, - 49679, - 49680, - 49681, - 49682, - 49683, - 49684, - 49685, - 49686, - 49687, - 49688, - 49689, - 49690, - 49691, - 49692, - 49693, - 49694, - 49695, - 49696, - 49697, - 49698, - 49699, - 49700, - 49701, - 49702, - 49703, - 49704, - 49705, - 49706, - 49707, - 49708, - 49709, - 49710, - 49711, - 49712, - 49713, - 49714, - 49715, - 49716, - 49717, - 49718, - 49719, - 49720, - 49721, - 49722, - 49723, - 49724, - 49725, - 49726, - 49727, - 49728, - 49729, - 49730, - 49731, - 49732, - 49733, - 49734, - 49735, - 49736, - 49737, - 49738, - 49739, - 49740, - 49741, - 49742, - 49743, - 49744, - 49745, - 49746, - 49747, - 49748, - 49749, - 49750, - 49751, - 49752, - 49753, - 49754, - 49755, - 49756, - 49757, - 49758, - 49759, - 49760, - 49761, - 49762, - 49763, - 49764, - 49765, - 49766, - 49767, - 49768, - 49769, - 49770, - 49771, - 49772, - 49773, - 49774, - 49775, - 49776, - 49777, - 49778, - 49779, - 49780, - 49781, - 49782, - 49783, - 49784, - 49785, - 49786, - 49787, - 49788, - 49789, - 49790, - 49791, - 49792, - 49793, - 49794, - 49795, - 49796, - 49797, - 49798, - 49799, - 49800, - 49801, - 49802, - 49803, - 49804, - 49805, - 49806, - 49807, - 49808, - 49809, - 49810, - 49811, - 49812, - 49813, - 49814, - 49815, - 49816, - 49817, - 49818, - 49819, - 49820, - 49821, - 49822, - 49823, - 49824, - 49825, - 49826, - 49827, - 49828, - 49829, - 49830, - 49831, - 49832, - 49833, - 49834, - 49835, - 49836, - 49837, - 49838, - 49839, - 49840, - 49841, - 49842, - 49843, - 49844, - 49845, - 49846, - 49847, - 49848, - 49849, - 49850, - 49851, - 49852, - 49853, - 49854, - 49855, - 49856, - 49857, - 49858, - 49859, - 49860, - 49861, - 49862, - 49863, - 49864, - 49865, - 49866, - 49867, - 49868, - 49869, - 49870, - 49871, - 49872, - 49873, - 49874, - 49875, - 49876, - 49877, - 49878, - 49879, - 49880, - 49881, - 49882, - 49883, - 49884, - 49885, - 49886, - 49887, - 49888, - 49889, - 49890, - 49891, - 49892, - 49893, - 49894, - 49895, - 49896, - 49897, - 49898, - 49899, - 49900, - 49901, - 49902, - 49903, - 49904, - 49905, - 49906, - 49907, - 49908, - 49909, - 49910, - 49911, - 49912, - 49913, - 49914, - 49915, - 49916, - 49917, - 49918, - 49919, - 49920, - 49921, - 49922, - 49923, - 49924, - 49925, - 49926, - 49927, - 49928, - 49929, - 49930, - 49931, - 49932, - 49933, - 49934, - 49935, - 49936, - 49937, - 49938, - 49939, - 49940, - 49941, - 49942, - 49943, - 49944, - 49945, - 49946, - 49947, - 49948, - 49949, - 49950, - 49951, - 49952, - 49953, - 49954, - 49955, - 49956, - 49957, - 49958, - 49959, - 49960, - 49961, - 49962, - 49963, - 49964, - 49965, - 49966, - 49967, - 49968, - 49969, - 49970, - 49971, - 49972, - 49973, - 49974, - 49975, - 49976, - 49977, - 49978, - 49979, - 49980, - 49981, - 49982, - 49983, - 49984, - 49985, - 49986, - 49987, - 49988, - 49989, - 49990, - 49991, - 49992, - 49993, - 49994, - 49995, - 49996, - 49997, - 49998, - 49999, - 50000, - 50001, - 50002, - 50003, - 50004, - 50005, - 50006, - 50007, - 50008, - 50009, - 50010, - 50011, - 50012, - 50013, - 50014, - 50015, - 50016, - 50017, - 50018, - 50019, - 50020, - 50021, - 50022, - 50023, - 50024, - 50025, - 50026, - 50027, - 50028, - 50029, - 50030, - 50031, - 50032, - 50033, - 50034, - 50035, - 50036, - 50037, - 50038, - 50039, - 50040, - 50041, - 50042, - 50043, - 50044, - 50045, - 50046, - 50047, - 50048, - 50049, - 50050, - 50051, - 50052, - 50053, - 50054, - 50055, - 50056, - 50057, - 50058, - 50059, - 50060, - 50061, - 50062, - 50063, - 50064, - 50065, - 50066, - 50067, - 50068, - 50069, - 50070, - 50071, - 50072, - 50073, - 50074, - 50075, - 50076, - 50077, - 50078, - 50079, - 50080, - 50081, - 50082, - 50083, - 50084, - 50085, - 50086, - 50087, - 50088, - 50089, - 50090, - 50091, - 50092, - 50093, - 50094, - 50095, - 50096, - 50097, - 50098, - 50099, - 50100, - 50101, - 50102, - 50103, - 50104, - 50105, - 50106, - 50107, - 50108, - 50109, - 50110, - 50111, - 50112, - 50113, - 50114, - 50115, - 50116, - 50117, - 50118, - 50119, - 50120, - 50121, - 50122, - 50123, - 50124, - 50125, - 50126, - 50127, - 50128, - 50129, - 50130, - 50131, - 50132, - 50133, - 50134, - 50135, - 50136, - 50137, - 50138, - 50139, - 50140, - 50141, - 50142, - 50143, - 50144, - 50145, - 50146, - 50147, - 50148, - 50149, - 50150, - 50151, - 50152, - 50153, - 50154, - 50155, - 50156, - 50157, - 50158, - 50159, - 50160, - 50161, - 50162, - 50163, - 50164, - 50165, - 50166, - 50167, - 50168, - 50169, - 50170, - 50171, - 50172, - 50173, - 50174, - 50175, - 50176, - 50177, - 50178, - 50179, - 50180, - 50181, - 50182, - 50183, - 50184, - 50185, - 50186, - 50187, - 50188, - 50189, - 50190, - 50191, - 50192, - 50193, - 50194, - 50195, - 50196, - 50197, - 50198, - 50199, - 50200, - 50201, - 50202, - 50203, - 50204, - 50205, - 50206, - 50207, - 50208, - 50209, - 50210, - 50211, - 50212, - 50213, - 50214, - 50215, - 50216, - 50217, - 50218, - 50219, - 50220, - 50221, - 50222, - 50223, - 50224, - 50225, - 50226, - 50227, - 50228, - 50229, - 50230, - 50231, - 50232, - 50233, - 50234, - 50235, - 50236, - 50237, - 50238, - 50239, - 50240, - 50241, - 50242, - 50243, - 50244, - 50245, - 50246, - 50247, - 50248, - 50249, - 50250, - 50251, - 50252, - 50253, - 50254, - 50255, - 50256, - 50257, - 50258, - 50259, - 50260, - 50261, - 50262, - 50263, - 50264, - 50265, - 50266, - 50267, - 50268, - 50269, - 50270, - 50271, - 50272, - 50273, - 50274, - 50275, - 50276, - 50277, - 50278, - 50279, - 50280, - 50281, - 50282, - 50283, - 50284, - 50285, - 50286, - 50287, - 50288, - 50289, - 50290, - 50291, - 50292, - 50293, - 50294, - 50295, - 50296, - 50297, - 50298, - 50299, - 50300, - 50301, - 50302, - 50303, - 50304, - 50305, - 50306, - 50307, - 50308, - 50309, - 50310, - 50311, - 50312, - 50313, - 50314, - 50315, - 50316, - 50317, - 50318, - 50319, - 50320, - 50321, - 50322, - 50323, - 50324, - 50325, - 50326, - 50327, - 50328, - 50329, - 50330, - 50331, - 50332, - 50333, - 50334, - 50335, - 50336, - 50337, - 50338, - 50339, - 50340, - 50341, - 50342, - 50343, - 50344, - 50345, - 50346, - 50347, - 50348, - 50349, - 50350, - 50351, - 50352, - 50353, - 50354, - 50355, - 50356, - 50357, - 50358, - 50359, - 50360, - 50361, - 50362, - 50363, - 50364, - 50365, - 50366, - 50367, - 50368, - 50369, - 50370, - 50371, - 50372, - 50373, - 50374, - 50375, - 50376, - 50377, - 50378, - 50379, - 50380, - 50381, - 50382, - 50383, - 50384, - 50385, - 50386, - 50387, - 50388, - 50389, - 50390, - 50391, - 50392, - 50393, - 50394, - 50395, - 50396, - 50397, - 50398, - 50399, - 50400, - 50401, - 50402, - 50403, - 50404, - 50405, - 50406, - 50407, - 50408, - 50409, - 50410, - 50411, - 50412, - 50413, - 50414, - 50415, - 50416, - 50417, - 50418, - 50419, - 50420, - 50421, - 50422, - 50423, - 50424, - 50425, - 50426, - 50427, - 50428, - 50429, - 50430, - 50431, - 50432, - 50433, - 50434, - 50435, - 50436, - 50437, - 50438, - 50439, - 50440, - 50441, - 50442, - 50443, - 50444, - 50445, - 50446, - 50447, - 50448, - 50449, - 50450, - 50451, - 50452, - 50453, - 50454, - 50455, - 50456, - 50457, - 50458, - 50459, - 50460, - 50461, - 50462, - 50463, - 50464, - 50465, - 50466, - 50467, - 50468, - 50469, - 50470, - 50471, - 50472, - 50473, - 50474, - 50475, - 50476, - 50477, - 50478, - 50479, - 50480, - 50481, - 50482, - 50483, - 50484, - 50485, - 50486, - 50487, - 50488, - 50489, - 50490, - 50491, - 50492, - 50493, - 50494, - 50495, - 50496, - 50497, - 50498, - 50499, - 50500, - 50501, - 50502, - 50503, - 50504, - 50505, - 50506, - 50507, - 50508, - 50509, - 50510, - 50511, - 50512, - 50513, - 50514, - 50515, - 50516, - 50517, - 50518, - 50519, - 50520, - 50521, - 50522, - 50523, - 50524, - 50525, - 50526, - 50527, - 50528, - 50529, - 50530, - 50531, - 50532, - 50533, - 50534, - 50535, - 50536, - 50537, - 50538, - 50539, - 50540, - 50541, - 50542, - 50543, - 50544, - 50545, - 50546, - 50547, - 50548, - 50549, - 50550, - 50551, - 50552, - 50553, - 50554, - 50555, - 50556, - 50557, - 50558, - 50559, - 50560, - 50561, - 50562, - 50563, - 50564, - 50565, - 50566, - 50567, - 50568, - 50569, - 50570, - 50571, - 50572, - 50573, - 50574, - 50575, - 50576, - 50577, - 50578, - 50579, - 50580, - 50581, - 50582, - 50583, - 50584, - 50585, - 50586, - 50587, - 50588, - 50589, - 50590, - 50591, - 50592, - 50593, - 50594, - 50595, - 50596, - 50597, - 50598, - 50599, - 50600, - 50601, - 50602, - 50603, - 50604, - 50605, - 50606, - 50607, - 50608, - 50609, - 50610, - 50611, - 50612, - 50613, - 50614, - 50615, - 50616, - 50617, - 50618, - 50619, - 50620, - 50621, - 50622, - 50623, - 50624, - 50625, - 50626, - 50627, - 50628, - 50629, - 50630, - 50631, - 50632, - 50633, - 50634, - 50635, - 50636, - 50637, - 50638, - 50639, - 50640, - 50641, - 50642, - 50643, - 50644, - 50645, - 50646, - 50647, - 50648, - 50649, - 50650, - 50651, - 50652, - 50653, - 50654, - 50655, - 50656, - 50657, - 50658, - 50659, - 50660, - 50661, - 50662, - 50663, - 50664, - 50665, - 50666, - 50667, - 50668, - 50669, - 50670, - 50671, - 50672, - 50673, - 50674, - 50675, - 50676, - 50677, - 50678, - 50679, - 50680, - 50681, - 50682, - 50683, - 50684, - 50685, - 50686, - 50687, - 50688, - 50689, - 50690, - 50691, - 50692, - 50693, - 50694, - 50695, - 50696, - 50697, - 50698, - 50699, - 50700, - 50701, - 50702, - 50703, - 50704, - 50705, - 50706, - 50707, - 50708, - 50709, - 50710, - 50711, - 50712, - 50713, - 50714, - 50715, - 50716, - 50717, - 50718, - 50719, - 50720, - 50721, - 50722, - 50723, - 50724, - 50725, - 50726, - 50727, - 50728, - 50729, - 50730, - 50731, - 50732, - 50733, - 50734, - 50735, - 50736, - 50737, - 50738, - 50739, - 50740, - 50741, - 50742, - 50743, - 50744, - 50745, - 50746, - 50747, - 50748, - 50749, - 50750, - 50751, - 50752, - 50753, - 50754, - 50755, - 50756, - 50757, - 50758, - 50759, - 50760, - 50761, - 50762, - 50763, - 50764, - 50765, - 50766, - 50767, - 50768, - 50769, - 50770, - 50771, - 50772, - 50773, - 50774, - 50775, - 50776, - 50777, - 50778, - 50779, - 50780, - 50781, - 50782, - 50783, - 50784, - 50785, - 50786, - 50787, - 50788, - 50789, - 50790, - 50791, - 50792, - 50793, - 50794, - 50795, - 50796, - 50797, - 50798, - 50799, - 50800, - 50801, - 50802, - 50803, - 50804, - 50805, - 50806, - 50807, - 50808, - 50809, - 50810, - 50811, - 50812, - 50813, - 50814, - 50815, - 50816, - 50817, - 50818, - 50819, - 50820, - 50821, - 50822, - 50823, - 50824, - 50825, - 50826, - 50827, - 50828, - 50829, - 50830, - 50831, - 50832, - 50833, - 50834, - 50835, - 50836, - 50837, - 50838, - 50839, - 50840, - 50841, - 50842, - 50843, - 50844, - 50845, - 50846, - 50847, - 50848, - 50849, - 50850, - 50851, - 50852, - 50853, - 50854, - 50855, - 50856, - 50857, - 50858, - 50859, - 50860, - 50861, - 50862, - 50863, - 50864, - 50865, - 50866, - 50867, - 50868, - 50869, - 50870, - 50871, - 50872, - 50873, - 50874, - 50875, - 50876, - 50877, - 50878, - 50879, - 50880, - 50881, - 50882, - 50883, - 50884, - 50885, - 50886, - 50887, - 50888, - 50889, - 50890, - 50891, - 50892, - 50893, - 50894, - 50895, - 50896, - 50897, - 50898, - 50899, - 50900, - 50901, - 50902, - 50903, - 50904, - 50905, - 50906, - 50907, - 50908, - 50909, - 50910, - 50911, - 50912, - 50913, - 50914, - 50915, - 50916, - 50917, - 50918, - 50919, - 50920, - 50921, - 50922, - 50923, - 50924, - 50925, - 50926, - 50927, - 50928, - 50929, - 50930, - 50931, - 50932, - 50933, - 50934, - 50935, - 50936, - 50937, - 50938, - 50939, - 50940, - 50941, - 50942, - 50943, - 50944, - 50945, - 50946, - 50947, - 50948, - 50949, - 50950, - 50951, - 50952, - 50953, - 50954, - 50955, - 50956, - 50957, - 50958, - 50959, - 50960, - 50961, - 50962, - 50963, - 50964, - 50965, - 50966, - 50967, - 50968, - 50969, - 50970, - 50971, - 50972, - 50973, - 50974, - 50975, - 50976, - 50977, - 50978, - 50979, - 50980, - 50981, - 50982, - 50983, - 50984, - 50985, - 50986, - 50987, - 50988, - 50989, - 50990, - 50991, - 50992, - 50993, - 50994, - 50995, - 50996, - 50997, - 50998, - 50999, - 51000, - 51001, - 51002, - 51003, - 51004, - 51005, - 51006, - 51007, - 51008, - 51009, - 51010, - 51011, - 51012, - 51013, - 51014, - 51015, - 51016, - 51017, - 51018, - 51019, - 51020, - 51021, - 51022, - 51023, - 51024, - 51025, - 51026, - 51027, - 51028, - 51029, - 51030, - 51031, - 51032, - 51033, - 51034, - 51035, - 51036, - 51037, - 51038, - 51039, - 51040, - 51041, - 51042, - 51043, - 51044, - 51045, - 51046, - 51047, - 51048, - 51049, - 51050, - 51051, - 51052, - 51053, - 51054, - 51055, - 51056, - 51057, - 51058, - 51059, - 51060, - 51061, - 51062, - 51063, - 51064, - 51065, - 51066, - 51067, - 51068, - 51069, - 51070, - 51071, - 51072, - 51073, - 51074, - 51075, - 51076, - 51077, - 51078, - 51079, - 51080, - 51081, - 51082, - 51083, - 51084, - 51085, - 51086, - 51087, - 51088, - 51089, - 51090, - 51091, - 51092, - 51093, - 51094, - 51095, - 51096, - 51097, - 51098, - 51099, - 51100, - 51101, - 51102, - 51103, - 51104, - 51105, - 51106, - 51107, - 51108, - 51109, - 51110, - 51111, - 51112, - 51113, - 51114, - 51115, - 51116, - 51117, - 51118, - 51119, - 51120, - 51121, - 51122, - 51123, - 51124, - 51125, - 51126, - 51127, - 51128, - 51129, - 51130, - 51131, - 51132, - 51133, - 51134, - 51135, - 51136, - 51137, - 51138, - 51139, - 51140, - 51141, - 51142, - 51143, - 51144, - 51145, - 51146, - 51147, - 51148, - 51149, - 51150, - 51151, - 51152, - 51153, - 51154, - 51155, - 51156, - 51157, - 51158, - 51159, - 51160, - 51161, - 51162, - 51163, - 51164, - 51165, - 51166, - 51167, - 51168, - 51169, - 51170, - 51171, - 51172, - 51173, - 51174, - 51175, - 51176, - 51177, - 51178, - 51179, - 51180, - 51181, - 51182, - 51183, - 51184, - 51185, - 51186, - 51187, - 51188, - 51189, - 51190, - 51191, - 51192, - 51193, - 51194, - 51195, - 51196, - 51197, - 51198, - 51199, - 51200, - 51201, - 51202, - 51203, - 51204, - 51205, - 51206, - 51207, - 51208, - 51209, - 51210, - 51211, - 51212, - 51213, - 51214, - 51215, - 51216, - 51217, - 51218, - 51219, - 51220, - 51221, - 51222, - 51223, - 51224, - 51225, - 51226, - 51227, - 51228, - 51229, - 51230, - 51231, - 51232, - 51233, - 51234, - 51235, - 51236, - 51237, - 51238, - 51239, - 51240, - 51241, - 51242, - 51243, - 51244, - 51245, - 51246, - 51247, - 51248, - 51249, - 51250, - 51251, - 51252, - 51253, - 51254, - 51255, - 51256, - 51257, - 51258, - 51259, - 51260, - 51261, - 51262, - 51263, - 51264, - 51265, - 51266, - 51267, - 51268, - 51269, - 51270, - 51271, - 51272, - 51273, - 51274, - 51275, - 51276, - 51277, - 51278, - 51279, - 51280, - 51281, - 51282, - 51283, - 51284, - 51285, - 51286, - 51287, - 51288, - 51289, - 51290, - 51291, - 51292, - 51293, - 51294, - 51295, - 51296, - 51297, - 51298, - 51299, - 51300, - 51301, - 51302, - 51303, - 51304, - 51305, - 51306, - 51307, - 51308, - 51309, - 51310, - 51311, - 51312, - 51313, - 51314, - 51315, - 51316, - 51317, - 51318, - 51319, - 51320, - 51321, - 51322, - 51323, - 51324, - 51325, - 51326, - 51327, - 51328, - 51329, - 51330, - 51331, - 51332, - 51333, - 51334, - 51335, - 51336, - 51337, - 51338, - 51339, - 51340, - 51341, - 51342, - 51343, - 51344, - 51345, - 51346, - 51347, - 51348, - 51349, - 51350, - 51351, - 51352, - 51353, - 51354, - 51355, - 51356, - 51357, - 51358, - 51359, - 51360, - 51361, - 51362, - 51363, - 51364, - 51365, - 51366, - 51367, - 51368, - 51369, - 51370, - 51371, - 51372, - 51373, - 51374, - 51375, - 51376, - 51377, - 51378, - 51379, - 51380, - 51381, - 51382, - 51383, - 51384, - 51385, - 51386, - 51387, - 51388, - 51389, - 51390, - 51391, - 51392, - 51393, - 51394, - 51395, - 51396, - 51397, - 51398, - 51399, - 51400, - 51401, - 51402, - 51403, - 51404, - 51405, - 51406, - 51407, - 51408, - 51409, - 51410, - 51411, - 51412, - 51413, - 51414, - 51415, - 51416, - 51417, - 51418, - 51419, - 51420, - 51421, - 51422, - 51423, - 51424, - 51425, - 51426, - 51427, - 51428, - 51429, - 51430, - 51431, - 51432, - 51433, - 51434, - 51435, - 51436, - 51437, - 51438, - 51439, - 51440, - 51441, - 51442, - 51443, - 51444, - 51445, - 51446, - 51447, - 51448, - 51449, - 51450, - 51451, - 51452, - 51453, - 51454, - 51455, - 51456, - 51457, - 51458, - 51459, - 51460, - 51461, - 51462, - 51463, - 51464, - 51465, - 51466, - 51467, - 51468, - 51469, - 51470, - 51471, - 51472, - 51473, - 51474, - 51475, - 51476, - 51477, - 51478, - 51479, - 51480, - 51481, - 51482, - 51483, - 51484, - 51485, - 51486, - 51487, - 51488, - 51489, - 51490, - 51491, - 51492, - 51493, - 51494, - 51495, - 51496, - 51497, - 51498, - 51499, - 51500, - 51501, - 51502, - 51503, - 51504, - 51505, - 51506, - 51507, - 51508, - 51509, - 51510, - 51511, - 51512, - 51513, - 51514, - 51515, - 51516, - 51517, - 51518, - 51519, - 51520, - 51521, - 51522, - 51523, - 51524, - 51525, - 51526, - 51527, - 51528, - 51529, - 51530, - 51531, - 51532, - 51533, - 51534, - 51535, - 51536, - 51537, - 51538, - 51539, - 51540, - 51541, - 51542, - 51543, - 51544, - 51545, - 51546, - 51547, - 51548, - 51549, - 51550, - 51551, - 51552, - 51553, - 51554, - 51555, - 51556, - 51557, - 51558, - 51559, - 51560, - 51561, - 51562, - 51563, - 51564, - 51565, - 51566, - 51567, - 51568, - 51569, - 51570, - 51571, - 51572, - 51573, - 51574, - 51575, - 51576, - 51577, - 51578, - 51579, - 51580, - 51581, - 51582, - 51583, - 51584, - 51585, - 51586, - 51587, - 51588, - 51589, - 51590, - 51591, - 51592, - 51593, - 51594, - 51595, - 51596, - 51597, - 51598, - 51599, - 51600, - 51601, - 51602, - 51603, - 51604, - 51605, - 51606, - 51607, - 51608, - 51609, - 51610, - 51611, - 51612, - 51613, - 51614, - 51615, - 51616, - 51617, - 51618, - 51619, - 51620, - 51621, - 51622, - 51623, - 51624, - 51625, - 51626, - 51627, - 51628, - 51629, - 51630, - 51631, - 51632, - 51633, - 51634, - 51635, - 51636, - 51637, - 51638, - 51639, - 51640, - 51641, - 51642, - 51643, - 51644, - 51645, - 51646, - 51647, - 51648, - 51649, - 51650, - 51651, - 51652, - 51653, - 51654, - 51655, - 51656, - 51657, - 51658, - 51659, - 51660, - 51661, - 51662, - 51663, - 51664, - 51665, - 51666, - 51667, - 51668, - 51669, - 51670, - 51671, - 51672, - 51673, - 51674, - 51675, - 51676, - 51677, - 51678, - 51679, - 51680, - 51681, - 51682, - 51683, - 51684, - 51685, - 51686, - 51687, - 51688, - 51689, - 51690, - 51691, - 51692, - 51693, - 51694, - 51695, - 51696, - 51697, - 51698, - 51699, - 51700, - 51701, - 51702, - 51703, - 51704, - 51705, - 51706, - 51707, - 51708, - 51709, - 51710, - 51711, - 51712, - 51713, - 51714, - 51715, - 51716, - 51717, - 51718, - 51719, - 51720, - 51721, - 51722, - 51723, - 51724, - 51725, - 51726, - 51727, - 51728, - 51729, - 51730, - 51731, - 51732, - 51733, - 51734, - 51735, - 51736, - 51737, - 51738, - 51739, - 51740, - 51741, - 51742, - 51743, - 51744, - 51745, - 51746, - 51747, - 51748, - 51749, - 51750, - 51751, - 51752, - 51753, - 51754, - 51755, - 51756, - 51757, - 51758, - 51759, - 51760, - 51761, - 51762, - 51763, - 51764, - 51765, - 51766, - 51767, - 51768, - 51769, - 51770, - 51771, - 51772, - 51773, - 51774, - 51775, - 51776, - 51777, - 51778, - 51779, - 51780, - 51781, - 51782, - 51783, - 51784, - 51785, - 51786, - 51787, - 51788, - 51789, - 51790, - 51791, - 51792, - 51793, - 51794, - 51795, - 51796, - 51797, - 51798, - 51799, - 51800, - 51801, - 51802, - 51803, - 51804, - 51805, - 51806, - 51807, - 51808, - 51809, - 51810, - 51811, - 51812, - 51813, - 51814, - 51815, - 51816, - 51817, - 51818, - 51819, - 51820, - 51821, - 51822, - 51823, - 51824, - 51825, - 51826, - 51827, - 51828, - 51829, - 51830, - 51831, - 51832, - 51833, - 51834, - 51835, - 51836, - 51837, - 51838, - 51839, - 51840, - 51841, - 51842, - 51843, - 51844, - 51845, - 51846, - 51847, - 51848, - 51849, - 51850, - 51851, - 51852, - 51853, - 51854, - 51855, - 51856, - 51857, - 51858, - 51859, - 51860, - 51861, - 51862, - 51863, - 51864, - 51865, - 51866, - 51867, - 51868, - 51869, - 51870, - 51871, - 51872, - 51873, - 51874, - 51875, - 51876, - 51877, - 51878, - 51879, - 51880, - 51881, - 51882, - 51883, - 51884, - 51885, - 51886, - 51887, - 51888, - 51889, - 51890, - 51891, - 51892, - 51893, - 51894, - 51895, - 51896, - 51897, - 51898, - 51899, - 51900, - 51901, - 51902, - 51903, - 51904, - 51905, - 51906, - 51907, - 51908, - 51909, - 51910, - 51911, - 51912, - 51913, - 51914, - 51915, - 51916, - 51917, - 51918, - 51919, - 51920, - 51921, - 51922, - 51923, - 51924, - 51925, - 51926, - 51927, - 51928, - 51929, - 51930, - 51931, - 51932, - 51933, - 51934, - 51935, - 51936, - 51937, - 51938, - 51939, - 51940, - 51941, - 51942, - 51943, - 51944, - 51945, - 51946, - 51947, - 51948, - 51949, - 51950, - 51951, - 51952, - 51953, - 51954, - 51955, - 51956, - 51957, - 51958, - 51959, - 51960, - 51961, - 51962, - 51963, - 51964, - 51965, - 51966, - 51967, - 51968, - 51969, - 51970, - 51971, - 51972, - 51973, - 51974, - 51975, - 51976, - 51977, - 51978, - 51979, - 51980, - 51981, - 51982, - 51983, - 51984, - 51985, - 51986, - 51987, - 51988, - 51989, - 51990, - 51991, - 51992, - 51993, - 51994, - 51995, - 51996, - 51997, - 51998, - 51999, - 52000, - 52001, - 52002, - 52003, - 52004, - 52005, - 52006, - 52007, - 52008, - 52009, - 52010, - 52011, - 52012, - 52013, - 52014, - 52015, - 52016, - 52017, - 52018, - 52019, - 52020, - 52021, - 52022, - 52023, - 52024, - 52025, - 52026, - 52027, - 52028, - 52029, - 52030, - 52031, - 52032, - 52033, - 52034, - 52035, - 52036, - 52037, - 52038, - 52039, - 52040, - 52041, - 52042, - 52043, - 52044, - 52045, - 52046, - 52047, - 52048, - 52049, - 52050, - 52051, - 52052, - 52053, - 52054, - 52055, - 52056, - 52057, - 52058, - 52059, - 52060, - 52061, - 52062, - 52063, - 52064, - 52065, - 52066, - 52067, - 52068, - 52069, - 52070, - 52071, - 52072, - 52073, - 52074, - 52075, - 52076, - 52077, - 52078, - 52079, - 52080, - 52081, - 52082, - 52083, - 52084, - 52085, - 52086, - 52087, - 52088, - 52089, - 52090, - 52091, - 52092, - 52093, - 52094, - 52095, - 52096, - 52097, - 52098, - 52099, - 52100, - 52101, - 52102, - 52103, - 52104, - 52105, - 52106, - 52107, - 52108, - 52109, - 52110, - 52111, - 52112, - 52113, - 52114, - 52115, - 52116, - 52117, - 52118, - 52119, - 52120, - 52121, - 52122, - 52123, - 52124, - 52125, - 52126, - 52127, - 52128, - 52129, - 52130, - 52131, - 52132, - 52133, - 52134, - 52135, - 52136, - 52137, - 52138, - 52139, - 52140, - 52141, - 52142, - 52143, - 52144, - 52145, - 52146, - 52147, - 52148, - 52149, - 52150, - 52151, - 52152, - 52153, - 52154, - 52155, - 52156, - 52157, - 52158, - 52159, - 52160, - 52161, - 52162, - 52163, - 52164, - 52165, - 52166, - 52167, - 52168, - 52169, - 52170, - 52171, - 52172, - 52173, - 52174, - 52175, - 52176, - 52177, - 52178, - 52179, - 52180, - 52181, - 52182, - 52183, - 52184, - 52185, - 52186, - 52187, - 52188, - 52189, - 52190, - 52191, - 52192, - 52193, - 52194, - 52195, - 52196, - 52197, - 52198, - 52199, - 52200, - 52201, - 52202, - 52203, - 52204, - 52205, - 52206, - 52207, - 52208, - 52209, - 52210, - 52211, - 52212, - 52213, - 52214, - 52215, - 52216, - 52217, - 52218, - 52219, - 52220, - 52221, - 52222, - 52223, - 52224, - 52225, - 52226, - 52227, - 52228, - 52229, - 52230, - 52231, - 52232, - 52233, - 52234, - 52235, - 52236, - 52237, - 52238, - 52239, - 52240, - 52241, - 52242, - 52243, - 52244, - 52245, - 52246, - 52247, - 52248, - 52249, - 52250, - 52251, - 52252, - 52253, - 52254, - 52255, - 52256, - 52257, - 52258, - 52259, - 52260, - 52261, - 52262, - 52263, - 52264, - 52265, - 52266, - 52267, - 52268, - 52269, - 52270, - 52271, - 52272, - 52273, - 52274, - 52275, - 52276, - 52277, - 52278, - 52279, - 52280, - 52281, - 52282, - 52283, - 52284, - 52285, - 52286, - 52287, - 52288, - 52289, - 52290, - 52291, - 52292, - 52293, - 52294, - 52295, - 52296, - 52297, - 52298, - 52299, - 52300, - 52301, - 52302, - 52303, - 52304, - 52305, - 52306, - 52307, - 52308, - 52309, - 52310, - 52311, - 52312, - 52313, - 52314, - 52315, - 52316, - 52317, - 52318, - 52319, - 52320, - 52321, - 52322, - 52323, - 52324, - 52325, - 52326, - 52327, - 52328, - 52329, - 52330, - 52331, - 52332, - 52333, - 52334, - 52335, - 52336, - 52337, - 52338, - 52339, - 52340, - 52341, - 52342, - 52343, - 52344, - 52345, - 52346, - 52347, - 52348, - 52349, - 52350, - 52351, - 52352, - 52353, - 52354, - 52355, - 52356, - 52357, - 52358, - 52359, - 52360, - 52361, - 52362, - 52363, - 52364, - 52365, - 52366, - 52367, - 52368, - 52369, - 52370, - 52371, - 52372, - 52373, - 52374, - 52375, - 52376, - 52377, - 52378, - 52379, - 52380, - 52381, - 52382, - 52383, - 52384, - 52385, - 52386, - 52387, - 52388, - 52389, - 52390, - 52391, - 52392, - 52393, - 52394, - 52395, - 52396, - 52397, - 52398, - 52399, - 52400, - 52401, - 52402, - 52403, - 52404, - 52405, - 52406, - 52407, - 52408, - 52409, - 52410, - 52411, - 52412, - 52413, - 52414, - 52415, - 52416, - 52417, - 52418, - 52419, - 52420, - 52421, - 52422, - 52423, - 52424, - 52425, - 52426, - 52427, - 52428, - 52429, - 52430, - 52431, - 52432, - 52433, - 52434, - 52435, - 52436, - 52437, - 52438, - 52439, - 52440, - 52441, - 52442, - 52443, - 52444, - 52445, - 52446, - 52447, - 52448, - 52449, - 52450, - 52451, - 52452, - 52453, - 52454, - 52455, - 52456, - 52457, - 52458, - 52459, - 52460, - 52461, - 52462, - 52463, - 52464, - 52465, - 52466, - 52467, - 52468, - 52469, - 52470, - 52471, - 52472, - 52473, - 52474, - 52475, - 52476, - 52477, - 52478, - 52479, - 52480, - 52481, - 52482, - 52483, - 52484, - 52485, - 52486, - 52487, - 52488, - 52489, - 52490, - 52491, - 52492, - 52493, - 52494, - 52495, - 52496, - 52497, - 52498, - 52499, - 52500, - 52501, - 52502, - 52503, - 52504, - 52505, - 52506, - 52507, - 52508, - 52509, - 52510, - 52511, - 52512, - 52513, - 52514, - 52515, - 52516, - 52517, - 52518, - 52519, - 52520, - 52521, - 52522, - 52523, - 52524, - 52525, - 52526, - 52527, - 52528, - 52529, - 52530, - 52531, - 52532, - 52533, - 52534, - 52535, - 52536, - 52537, - 52538, - 52539, - 52540, - 52541, - 52542, - 52543, - 52544, - 52545, - 52546, - 52547, - 52548, - 52549, - 52550, - 52551, - 52552, - 52553, - 52554, - 52555, - 52556, - 52557, - 52558, - 52559, - 52560, - 52561, - 52562, - 52563, - 52564, - 52565, - 52566, - 52567, - 52568, - 52569, - 52570, - 52571, - 52572, - 52573, - 52574, - 52575, - 52576, - 52577, - 52578, - 52579, - 52580, - 52581, - 52582, - 52583, - 52584, - 52585, - 52586, - 52587, - 52588, - 52589, - 52590, - 52591, - 52592, - 52593, - 52594, - 52595, - 52596, - 52597, - 52598, - 52599, - 52600, - 52601, - 52602, - 52603, - 52604, - 52605, - 52606, - 52607, - 52608, - 52609, - 52610, - 52611, - 52612, - 52613, - 52614, - 52615, - 52616, - 52617, - 52618, - 52619, - 52620, - 52621, - 52622, - 52623, - 52624, - 52625, - 52626, - 52627, - 52628, - 52629, - 52630, - 52631, - 52632, - 52633, - 52634, - 52635, - 52636, - 52637, - 52638, - 52639, - 52640, - 52641, - 52642, - 52643, - 52644, - 52645, - 52646, - 52647, - 52648, - 52649, - 52650, - 52651, - 52652, - 52653, - 52654, - 52655, - 52656, - 52657, - 52658, - 52659, - 52660, - 52661, - 52662, - 52663, - 52664, - 52665, - 52666, - 52667, - 52668, - 52669, - 52670, - 52671, - 52672, - 52673, - 52674, - 52675, - 52676, - 52677, - 52678, - 52679, - 52680, - 52681, - 52682, - 52683, - 52684, - 52685, - 52686, - 52687, - 52688, - 52689, - 52690, - 52691, - 52692, - 52693, - 52694, - 52695, - 52696, - 52697, - 52698, - 52699, - 52700, - 52701, - 52702, - 52703, - 52704, - 52705, - 52706, - 52707, - 52708, - 52709, - 52710, - 52711, - 52712, - 52713, - 52714, - 52715, - 52716, - 52717, - 52718, - 52719, - 52720, - 52721, - 52722, - 52723, - 52724, - 52725, - 52726, - 52727, - 52728, - 52729, - 52730, - 52731, - 52732, - 52733, - 52734, - 52735, - 52736, - 52737, - 52738, - 52739, - 52740, - 52741, - 52742, - 52743, - 52744, - 52745, - 52746, - 52747, - 52748, - 52749, - 52750, - 52751, - 52752, - 52753, - 52754, - 52755, - 52756, - 52757, - 52758, - 52759, - 52760, - 52761, - 52762, - 52763, - 52764, - 52765, - 52766, - 52767, - 52768, - 52769, - 52770, - 52771, - 52772, - 52773, - 52774, - 52775, - 52776, - 52777, - 52778, - 52779, - 52780, - 52781, - 52782, - 52783, - 52784, - 52785, - 52786, - 52787, - 52788, - 52789, - 52790, - 52791, - 52792, - 52793, - 52794, - 52795, - 52796, - 52797, - 52798, - 52799, - 52800, - 52801, - 52802, - 52803, - 52804, - 52805, - 52806, - 52807, - 52808, - 52809, - 52810, - 52811, - 52812, - 52813, - 52814, - 52815, - 52816, - 52817, - 52818, - 52819, - 52820, - 52821, - 52822, - 52823, - 52824, - 52825, - 52826, - 52827, - 52828, - 52829, - 52830, - 52831, - 52832, - 52833, - 52834, - 52835, - 52836, - 52837, - 52838, - 52839, - 52840, - 52841, - 52842, - 52843, - 52844, - 52845, - 52846, - 52847, - 52848, - 52849, - 52850, - 52851, - 52852, - 52853, - 52854, - 52855, - 52856, - 52857, - 52858, - 52859, - 52860, - 52861, - 52862, - 52863, - 52864, - 52865, - 52866, - 52867, - 52868, - 52869, - 52870, - 52871, - 52872, - 52873, - 52874, - 52875, - 52876, - 52877, - 52878, - 52879, - 52880, - 52881, - 52882, - 52883, - 52884, - 52885, - 52886, - 52887, - 52888, - 52889, - 52890, - 52891, - 52892, - 52893, - 52894, - 52895, - 52896, - 52897, - 52898, - 52899, - 52900, - 52901, - 52902, - 52903, - 52904, - 52905, - 52906, - 52907, - 52908, - 52909, - 52910, - 52911, - 52912, - 52913, - 52914, - 52915, - 52916, - 52917, - 52918, - 52919, - 52920, - 52921, - 52922, - 52923, - 52924, - 52925, - 52926, - 52927, - 52928, - 52929, - 52930, - 52931, - 52932, - 52933, - 52934, - 52935, - 52936, - 52937, - 52938, - 52939, - 52940, - 52941, - 52942, - 52943, - 52944, - 52945, - 52946, - 52947, - 52948, - 52949, - 52950, - 52951, - 52952, - 52953, - 52954, - 52955, - 52956, - 52957, - 52958, - 52959, - 52960, - 52961, - 52962, - 52963, - 52964, - 52965, - 52966, - 52967, - 52968, - 52969, - 52970, - 52971, - 52972, - 52973, - 52974, - 52975, - 52976, - 52977, - 52978, - 52979, - 52980, - 52981, - 52982, - 52983, - 52984, - 52985, - 52986, - 52987, - 52988, - 52989, - 52990, - 52991, - 52992, - 52993, - 52994, - 52995, - 52996, - 52997, - 52998, - 52999, - 53000, - 53001, - 53002, - 53003, - 53004, - 53005, - 53006, - 53007, - 53008, - 53009, - 53010, - 53011, - 53012, - 53013, - 53014, - 53015, - 53016, - 53017, - 53018, - 53019, - 53020, - 53021, - 53022, - 53023, - 53024, - 53025, - 53026, - 53027, - 53028, - 53029, - 53030, - 53031, - 53032, - 53033, - 53034, - 53035, - 53036, - 53037, - 53038, - 53039, - 53040, - 53041, - 53042, - 53043, - 53044, - 53045, - 53046, - 53047, - 53048, - 53049, - 53050, - 53051, - 53052, - 53053, - 53054, - 53055, - 53056, - 53057, - 53058, - 53059, - 53060, - 53061, - 53062, - 53063, - 53064, - 53065, - 53066, - 53067, - 53068, - 53069, - 53070, - 53071, - 53072, - 53073, - 53074, - 53075, - 53076, - 53077, - 53078, - 53079, - 53080, - 53081, - 53082, - 53083, - 53084, - 53085, - 53086, - 53087, - 53088, - 53089, - 53090, - 53091, - 53092, - 53093, - 53094, - 53095, - 53096, - 53097, - 53098, - 53099, - 53100, - 53101, - 53102, - 53103, - 53104, - 53105, - 53106, - 53107, - 53108, - 53109, - 53110, - 53111, - 53112, - 53113, - 53114, - 53115, - 53116, - 53117, - 53118, - 53119, - 53120, - 53121, - 53122, - 53123, - 53124, - 53125, - 53126, - 53127, - 53128, - 53129, - 53130, - 53131, - 53132, - 53133, - 53134, - 53135, - 53136, - 53137, - 53138, - 53139, - 53140, - 53141, - 53142, - 53143, - 53144, - 53145, - 53146, - 53147, - 53148, - 53149, - 53150, - 53151, - 53152, - 53153, - 53154, - 53155, - 53156, - 53157, - 53158, - 53159, - 53160, - 53161, - 53162, - 53163, - 53164, - 53165, - 53166, - 53167, - 53168, - 53169, - 53170, - 53171, - 53172, - 53173, - 53174, - 53175, - 53176, - 53177, - 53178, - 53179, - 53180, - 53181, - 53182, - 53183, - 53184, - 53185, - 53186, - 53187, - 53188, - 53189, - 53190, - 53191, - 53192, - 53193, - 53194, - 53195, - 53196, - 53197, - 53198, - 53199, - 53200, - 53201, - 53202, - 53203, - 53204, - 53205, - 53206, - 53207, - 53208, - 53209, - 53210, - 53211, - 53212, - 53213, - 53214, - 53215, - 53216, - 53217, - 53218, - 53219, - 53220, - 53221, - 53222, - 53223, - 53224, - 53225, - 53226, - 53227, - 53228, - 53229, - 53230, - 53231, - 53232, - 53233, - 53234, - 53235, - 53236, - 53237, - 53238, - 53239, - 53240, - 53241, - 53242, - 53243, - 53244, - 53245, - 53246, - 53247, - 53248, - 53249, - 53250, - 53251, - 53252, - 53253, - 53254, - 53255, - 53256, - 53257, - 53258, - 53259, - 53260, - 53261, - 53262, - 53263, - 53264, - 53265, - 53266, - 53267, - 53268, - 53269, - 53270, - 53271, - 53272, - 53273, - 53274, - 53275, - 53276, - 53277, - 53278, - 53279, - 53280, - 53281, - 53282, - 53283, - 53284, - 53285, - 53286, - 53287, - 53288, - 53289, - 53290, - 53291, - 53292, - 53293, - 53294, - 53295, - 53296, - 53297, - 53298, - 53299, - 53300, - 53301, - 53302, - 53303, - 53304, - 53305, - 53306, - 53307, - 53308, - 53309, - 53310, - 53311, - 53312, - 53313, - 53314, - 53315, - 53316, - 53317, - 53318, - 53319, - 53320, - 53321, - 53322, - 53323, - 53324, - 53325, - 53326, - 53327, - 53328, - 53329, - 53330, - 53331, - 53332, - 53333, - 53334, - 53335, - 53336, - 53337, - 53338, - 53339, - 53340, - 53341, - 53342, - 53343, - 53344, - 53345, - 53346, - 53347, - 53348, - 53349, - 53350, - 53351, - 53352, - 53353, - 53354, - 53355, - 53356, - 53357, - 53358, - 53359, - 53360, - 53361, - 53362, - 53363, - 53364, - 53365, - 53366, - 53367, - 53368, - 53369, - 53370, - 53371, - 53372, - 53373, - 53374, - 53375, - 53376, - 53377, - 53378, - 53379, - 53380, - 53381, - 53382, - 53383, - 53384, - 53385, - 53386, - 53387, - 53388, - 53389, - 53390, - 53391, - 53392, - 53393, - 53394, - 53395, - 53396, - 53397, - 53398, - 53399, - 53400, - 53401, - 53402, - 53403, - 53404, - 53405, - 53406, - 53407, - 53408, - 53409, - 53410, - 53411, - 53412, - 53413, - 53414, - 53415, - 53416, - 53417, - 53418, - 53419, - 53420, - 53421, - 53422, - 53423, - 53424, - 53425, - 53426, - 53427, - 53428, - 53429, - 53430, - 53431, - 53432, - 53433, - 53434, - 53435, - 53436, - 53437, - 53438, - 53439, - 53440, - 53441, - 53442, - 53443, - 53444, - 53445, - 53446, - 53447, - 53448, - 53449, - 53450, - 53451, - 53452, - 53453, - 53454, - 53455, - 53456, - 53457, - 53458, - 53459, - 53460, - 53461, - 53462, - 53463, - 53464, - 53465, - 53466, - 53467, - 53468, - 53469, - 53470, - 53471, - 53472, - 53473, - 53474, - 53475, - 53476, - 53477, - 53478, - 53479, - 53480, - 53481, - 53482, - 53483, - 53484, - 53485, - 53486, - 53487, - 53488, - 53489, - 53490, - 53491, - 53492, - 53493, - 53494, - 53495, - 53496, - 53497, - 53498, - 53499, - 53500, - 53501, - 53502, - 53503, - 53504, - 53505, - 53506, - 53507, - 53508, - 53509, - 53510, - 53511, - 53512, - 53513, - 53514, - 53515, - 53516, - 53517, - 53518, - 53519, - 53520, - 53521, - 53522, - 53523, - 53524, - 53525, - 53526, - 53527, - 53528, - 53529, - 53530, - 53531, - 53532, - 53533, - 53534, - 53535, - 53536, - 53537, - 53538, - 53539, - 53540, - 53541, - 53542, - 53543, - 53544, - 53545, - 53546, - 53547, - 53548, - 53549, - 53550, - 53551, - 53552, - 53553, - 53554, - 53555, - 53556, - 53557, - 53558, - 53559, - 53560, - 53561, - 53562, - 53563, - 53564, - 53565, - 53566, - 53567, - 53568, - 53569, - 53570, - 53571, - 53572, - 53573, - 53574, - 53575, - 53576, - 53577, - 53578, - 53579, - 53580, - 53581, - 53582, - 53583, - 53584, - 53585, - 53586, - 53587, - 53588, - 53589, - 53590, - 53591, - 53592, - 53593, - 53594, - 53595, - 53596, - 53597, - 53598, - 53599, - 53600, - 53601, - 53602, - 53603, - 53604, - 53605, - 53606, - 53607, - 53608, - 53609, - 53610, - 53611, - 53612, - 53613, - 53614, - 53615, - 53616, - 53617, - 53618, - 53619, - 53620, - 53621, - 53622, - 53623, - 53624, - 53625, - 53626, - 53627, - 53628, - 53629, - 53630, - 53631, - 53632, - 53633, - 53634, - 53635, - 53636, - 53637, - 53638, - 53639, - 53640, - 53641, - 53642, - 53643, - 53644, - 53645, - 53646, - 53647, - 53648, - 53649, - 53650, - 53651, - 53652, - 53653, - 53654, - 53655, - 53656, - 53657, - 53658, - 53659, - 53660, - 53661, - 53662, - 53663, - 53664, - 53665, - 53666, - 53667, - 53668, - 53669, - 53670, - 53671, - 53672, - 53673, - 53674, - 53675, - 53676, - 53677, - 53678, - 53679, - 53680, - 53681, - 53682, - 53683, - 53684, - 53685, - 53686, - 53687, - 53688, - 53689, - 53690, - 53691, - 53692, - 53693, - 53694, - 53695, - 53696, - 53697, - 53698, - 53699, - 53700, - 53701, - 53702, - 53703, - 53704, - 53705, - 53706, - 53707, - 53708, - 53709, - 53710, - 53711, - 53712, - 53713, - 53714, - 53715, - 53716, - 53717, - 53718, - 53719, - 53720, - 53721, - 53722, - 53723, - 53724, - 53725, - 53726, - 53727, - 53728, - 53729, - 53730, - 53731, - 53732, - 53733, - 53734, - 53735, - 53736, - 53737, - 53738, - 53739, - 53740, - 53741, - 53742, - 53743, - 53744, - 53745, - 53746, - 53747, - 53748, - 53749, - 53750, - 53751, - 53752, - 53753, - 53754, - 53755, - 53756, - 53757, - 53758, - 53759, - 53760, - 53761, - 53762, - 53763, - 53764, - 53765, - 53766, - 53767, - 53768, - 53769, - 53770, - 53771, - 53772, - 53773, - 53774, - 53775, - 53776, - 53777, - 53778, - 53779, - 53780, - 53781, - 53782, - 53783, - 53784, - 53785, - 53786, - 53787, - 53788, - 53789, - 53790, - 53791, - 53792, - 53793, - 53794, - 53795, - 53796, - 53797, - 53798, - 53799, - 53800, - 53801, - 53802, - 53803, - 53804, - 53805, - 53806, - 53807, - 53808, - 53809, - 53810, - 53811, - 53812, - 53813, - 53814, - 53815, - 53816, - 53817, - 53818, - 53819, - 53820, - 53821, - 53822, - 53823, - 53824, - 53825, - 53826, - 53827, - 53828, - 53829, - 53830, - 53831, - 53832, - 53833, - 53834, - 53835, - 53836, - 53837, - 53838, - 53839, - 53840, - 53841, - 53842, - 53843, - 53844, - 53845, - 53846, - 53847, - 53848, - 53849, - 53850, - 53851, - 53852, - 53853, - 53854, - 53855, - 53856, - 53857, - 53858, - 53859, - 53860, - 53861, - 53862, - 53863, - 53864, - 53865, - 53866, - 53867, - 53868, - 53869, - 53870, - 53871, - 53872, - 53873, - 53874, - 53875, - 53876, - 53877, - 53878, - 53879, - 53880, - 53881, - 53882, - 53883, - 53884, - 53885, - 53886, - 53887, - 53888, - 53889, - 53890, - 53891, - 53892, - 53893, - 53894, - 53895, - 53896, - 53897, - 53898, - 53899, - 53900, - 53901, - 53902, - 53903, - 53904, - 53905, - 53906, - 53907, - 53908, - 53909, - 53910, - 53911, - 53912, - 53913, - 53914, - 53915, - 53916, - 53917, - 53918, - 53919, - 53920, - 53921, - 53922, - 53923, - 53924, - 53925, - 53926, - 53927, - 53928, - 53929, - 53930, - 53931, - 53932, - 53933, - 53934, - 53935, - 53936, - 53937, - 53938, - 53939, - 53940, - 53941, - 53942, - 53943, - 53944, - 53945, - 53946, - 53947, - 53948, - 53949, - 53950, - 53951, - 53952, - 53953, - 53954, - 53955, - 53956, - 53957, - 53958, - 53959, - 53960, - 53961, - 53962, - 53963, - 53964, - 53965, - 53966, - 53967, - 53968, - 53969, - 53970, - 53971, - 53972, - 53973, - 53974, - 53975, - 53976, - 53977, - 53978, - 53979, - 53980, - 53981, - 53982, - 53983, - 53984, - 53985, - 53986, - 53987, - 53988, - 53989, - 53990, - 53991, - 53992, - 53993, - 53994, - 53995, - 53996, - 53997, - 53998, - 53999, - 54000, - 54001, - 54002, - 54003, - 54004, - 54005, - 54006, - 54007, - 54008, - 54009, - 54010, - 54011, - 54012, - 54013, - 54014, - 54015, - 54016, - 54017, - 54018, - 54019, - 54020, - 54021, - 54022, - 54023, - 54024, - 54025, - 54026, - 54027, - 54028, - 54029, - 54030, - 54031, - 54032, - 54033, - 54034, - 54035, - 54036, - 54037, - 54038, - 54039, - 54040, - 54041, - 54042, - 54043, - 54044, - 54045, - 54046, - 54047, - 54048, - 54049, - 54050, - 54051, - 54052, - 54053, - 54054, - 54055, - 54056, - 54057, - 54058, - 54059, - 54060, - 54061, - 54062, - 54063, - 54064, - 54065, - 54066, - 54067, - 54068, - 54069, - 54070, - 54071, - 54072, - 54073, - 54074, - 54075, - 54076, - 54077, - 54078, - 54079, - 54080, - 54081, - 54082, - 54083, - 54084, - 54085, - 54086, - 54087, - 54088, - 54089, - 54090, - 54091, - 54092, - 54093, - 54094, - 54095, - 54096, - 54097, - 54098, - 54099, - 54100, - 54101, - 54102, - 54103, - 54104, - 54105, - 54106, - 54107, - 54108, - 54109, - 54110, - 54111, - 54112, - 54113, - 54114, - 54115, - 54116, - 54117, - 54118, - 54119, - 54120, - 54121, - 54122, - 54123, - 54124, - 54125, - 54126, - 54127, - 54128, - 54129, - 54130, - 54131, - 54132, - 54133, - 54134, - 54135, - 54136, - 54137, - 54138, - 54139, - 54140, - 54141, - 54142, - 54143, - 54144, - 54145, - 54146, - 54147, - 54148, - 54149, - 54150, - 54151, - 54152, - 54153, - 54154, - 54155, - 54156, - 54157, - 54158, - 54159, - 54160, - 54161, - 54162, - 54163, - 54164, - 54165, - 54166, - 54167, - 54168, - 54169, - 54170, - 54171, - 54172, - 54173, - 54174, - 54175, - 54176, - 54177, - 54178, - 54179, - 54180, - 54181, - 54182, - 54183, - 54184, - 54185, - 54186, - 54187, - 54188, - 54189, - 54190, - 54191, - 54192, - 54193, - 54194, - 54195, - 54196, - 54197, - 54198, - 54199, - 54200, - 54201, - 54202, - 54203, - 54204, - 54205, - 54206, - 54207, - 54208, - 54209, - 54210, - 54211, - 54212, - 54213, - 54214, - 54215, - 54216, - 54217, - 54218, - 54219, - 54220, - 54221, - 54222, - 54223, - 54224, - 54225, - 54226, - 54227, - 54228, - 54229, - 54230, - 54231, - 54232, - 54233, - 54234, - 54235, - 54236, - 54237, - 54238, - 54239, - 54240, - 54241, - 54242, - 54243, - 54244, - 54245, - 54246, - 54247, - 54248, - 54249, - 54250, - 54251, - 54252, - 54253, - 54254, - 54255, - 54256, - 54257, - 54258, - 54259, - 54260, - 54261, - 54262, - 54263, - 54264, - 54265, - 54266, - 54267, - 54268, - 54269, - 54270, - 54271, - 54272, - 54273, - 54274, - 54275, - 54276, - 54277, - 54278, - 54279, - 54280, - 54281, - 54282, - 54283, - 54284, - 54285, - 54286, - 54287, - 54288, - 54289, - 54290, - 54291, - 54292, - 54293, - 54294, - 54295, - 54296, - 54297, - 54298, - 54299, - 54300, - 54301, - 54302, - 54303, - 54304, - 54305, - 54306, - 54307, - 54308, - 54309, - 54310, - 54311, - 54312, - 54313, - 54314, - 54315, - 54316, - 54317, - 54318, - 54319, - 54320, - 54321, - 54322, - 54323, - 54324, - 54325, - 54326, - 54327, - 54328, - 54329, - 54330, - 54331, - 54332, - 54333, - 54334, - 54335, - 54336, - 54337, - 54338, - 54339, - 54340, - 54341, - 54342, - 54343, - 54344, - 54345, - 54346, - 54347, - 54348, - 54349, - 54350, - 54351, - 54352, - 54353, - 54354, - 54355, - 54356, - 54357, - 54358, - 54359, - 54360, - 54361, - 54362, - 54363, - 54364, - 54365, - 54366, - 54367, - 54368, - 54369, - 54370, - 54371, - 54372, - 54373, - 54374, - 54375, - 54376, - 54377, - 54378, - 54379, - 54380, - 54381, - 54382, - 54383, - 54384, - 54385, - 54386, - 54387, - 54388, - 54389, - 54390, - 54391, - 54392, - 54393, - 54394, - 54395, - 54396, - 54397, - 54398, - 54399, - 54400, - 54401, - 54402, - 54403, - 54404, - 54405, - 54406, - 54407, - 54408, - 54409, - 54410, - 54411, - 54412, - 54413, - 54414, - 54415, - 54416, - 54417, - 54418, - 54419, - 54420, - 54421, - 54422, - 54423, - 54424, - 54425, - 54426, - 54427, - 54428, - 54429, - 54430, - 54431, - 54432, - 54433, - 54434, - 54435, - 54436, - 54437, - 54438, - 54439, - 54440, - 54441, - 54442, - 54443, - 54444, - 54445, - 54446, - 54447, - 54448, - 54449, - 54450, - 54451, - 54452, - 54453, - 54454, - 54455, - 54456, - 54457, - 54458, - 54459, - 54460, - 54461, - 54462, - 54463, - 54464, - 54465, - 54466, - 54467, - 54468, - 54469, - 54470, - 54471, - 54472, - 54473, - 54474, - 54475, - 54476, - 54477, - 54478, - 54479, - 54480, - 54481, - 54482, - 54483, - 54484, - 54485, - 54486, - 54487, - 54488, - 54489, - 54490, - 54491, - 54492, - 54493, - 54494, - 54495, - 54496, - 54497, - 54498, - 54499, - 54500, - 54501, - 54502, - 54503, - 54504, - 54505, - 54506, - 54507, - 54508, - 54509, - 54510, - 54511, - 54512, - 54513, - 54514, - 54515, - 54516, - 54517, - 54518, - 54519, - 54520, - 54521, - 54522, - 54523, - 54524, - 54525, - 54526, - 54527, - 54528, - 54529, - 54530, - 54531, - 54532, - 54533, - 54534, - 54535, - 54536, - 54537, - 54538, - 54539, - 54540, - 54541, - 54542, - 54543, - 54544, - 54545, - 54546, - 54547, - 54548, - 54549, - 54550, - 54551, - 54552, - 54553, - 54554, - 54555, - 54556, - 54557, - 54558, - 54559, - 54560, - 54561, - 54562, - 54563, - 54564, - 54565, - 54566, - 54567, - 54568, - 54569, - 54570, - 54571, - 54572, - 54573, - 54574, - 54575, - 54576, - 54577, - 54578, - 54579, - 54580, - 54581, - 54582, - 54583, - 54584, - 54585, - 54586, - 54587, - 54588, - 54589, - 54590, - 54591, - 54592, - 54593, - 54594, - 54595, - 54596, - 54597, - 54598, - 54599, - 54600, - 54601, - 54602, - 54603, - 54604, - 54605, - 54606, - 54607, - 54608, - 54609, - 54610, - 54611, - 54612, - 54613, - 54614, - 54615, - 54616, - 54617, - 54618, - 54619, - 54620, - 54621, - 54622, - 54623, - 54624, - 54625, - 54626, - 54627, - 54628, - 54629, - 54630, - 54631, - 54632, - 54633, - 54634, - 54635, - 54636, - 54637, - 54638, - 54639, - 54640, - 54641, - 54642, - 54643, - 54644, - 54645, - 54646, - 54647, - 54648, - 54649, - 54650, - 54651, - 54652, - 54653, - 54654, - 54655, - 54656, - 54657, - 54658, - 54659, - 54660, - 54661, - 54662, - 54663, - 54664, - 54665, - 54666, - 54667, - 54668, - 54669, - 54670, - 54671, - 54672, - 54673, - 54674, - 54675, - 54676, - 54677, - 54678, - 54679, - 54680, - 54681, - 54682, - 54683, - 54684, - 54685, - 54686, - 54687, - 54688, - 54689, - 54690, - 54691, - 54692, - 54693, - 54694, - 54695, - 54696, - 54697, - 54698, - 54699, - 54700, - 54701, - 54702, - 54703, - 54704, - 54705, - 54706, - 54707, - 54708, - 54709, - 54710, - 54711, - 54712, - 54713, - 54714, - 54715, - 54716, - 54717, - 54718, - 54719, - 54720, - 54721, - 54722, - 54723, - 54724, - 54725, - 54726, - 54727, - 54728, - 54729, - 54730, - 54731, - 54732, - 54733, - 54734, - 54735, - 54736, - 54737, - 54738, - 54739, - 54740, - 54741, - 54742, - 54743, - 54744, - 54745, - 54746, - 54747, - 54748, - 54749, - 54750, - 54751, - 54752, - 54753, - 54754, - 54755, - 54756, - 54757, - 54758, - 54759, - 54760, - 54761, - 54762, - 54763, - 54764, - 54765, - 54766, - 54767, - 54768, - 54769, - 54770, - 54771, - 54772, - 54773, - 54774, - 54775, - 54776, - 54777, - 54778, - 54779, - 54780, - 54781, - 54782, - 54783, - 54784, - 54785, - 54786, - 54787, - 54788, - 54789, - 54790, - 54791, - 54792, - 54793, - 54794, - 54795, - 54796, - 54797, - 54798, - 54799, - 54800, - 54801, - 54802, - 54803, - 54804, - 54805, - 54806, - 54807, - 54808, - 54809, - 54810, - 54811, - 54812, - 54813, - 54814, - 54815, - 54816, - 54817, - 54818, - 54819, - 54820, - 54821, - 54822, - 54823, - 54824, - 54825, - 54826, - 54827, - 54828, - 54829, - 54830, - 54831, - 54832, - 54833, - 54834, - 54835, - 54836, - 54837, - 54838, - 54839, - 54840, - 54841, - 54842, - 54843, - 54844, - 54845, - 54846, - 54847, - 54848, - 54849, - 54850, - 54851, - 54852, - 54853, - 54854, - 54855, - 54856, - 54857, - 54858, - 54859, - 54860, - 54861, - 54862, - 54863, - 54864, - 54865, - 54866, - 54867, - 54868, - 54869, - 54870, - 54871, - 54872, - 54873, - 54874, - 54875, - 54876, - 54877, - 54878, - 54879, - 54880, - 54881, - 54882, - 54883, - 54884, - 54885, - 54886, - 54887, - 54888, - 54889, - 54890, - 54891, - 54892, - 54893, - 54894, - 54895, - 54896, - 54897, - 54898, - 54899, - 54900, - 54901, - 54902, - 54903, - 54904, - 54905, - 54906, - 54907, - 54908, - 54909, - 54910, - 54911, - 54912, - 54913, - 54914, - 54915, - 54916, - 54917, - 54918, - 54919, - 54920, - 54921, - 54922, - 54923, - 54924, - 54925, - 54926, - 54927, - 54928, - 54929, - 54930, - 54931, - 54932, - 54933, - 54934, - 54935, - 54936, - 54937, - 54938, - 54939, - 54940, - 54941, - 54942, - 54943, - 54944, - 54945, - 54946, - 54947, - 54948, - 54949, - 54950, - 54951, - 54952, - 54953, - 54954, - 54955, - 54956, - 54957, - 54958, - 54959, - 54960, - 54961, - 54962, - 54963, - 54964, - 54965, - 54966, - 54967, - 54968, - 54969, - 54970, - 54971, - 54972, - 54973, - 54974, - 54975, - 54976, - 54977, - 54978, - 54979, - 54980, - 54981, - 54982, - 54983, - 54984, - 54985, - 54986, - 54987, - 54988, - 54989, - 54990, - 54991, - 54992, - 54993, - 54994, - 54995, - 54996, - 54997, - 54998, - 54999, - 55000, - 55001, - 55002, - 55003, - 55004, - 55005, - 55006, - 55007, - 55008, - 55009, - 55010, - 55011, - 55012, - 55013, - 55014, - 55015, - 55016, - 55017, - 55018, - 55019, - 55020, - 55021, - 55022, - 55023, - 55024, - 55025, - 55026, - 55027, - 55028, - 55029, - 55030, - 55031, - 55032, - 55033, - 55034, - 55035, - 55036, - 55037, - 55038, - 55039, - 55040, - 55041, - 55042, - 55043, - 55044, - 55045, - 55046, - 55047, - 55048, - 55049, - 55050, - 55051, - 55052, - 55053, - 55054, - 55055, - 55056, - 55057, - 55058, - 55059, - 55060, - 55061, - 55062, - 55063, - 55064, - 55065, - 55066, - 55067, - 55068, - 55069, - 55070, - 55071, - 55072, - 55073, - 55074, - 55075, - 55076, - 55077, - 55078, - 55079, - 55080, - 55081, - 55082, - 55083, - 55084, - 55085, - 55086, - 55087, - 55088, - 55089, - 55090, - 55091, - 55092, - 55093, - 55094, - 55095, - 55096, - 55097, - 55098, - 55099, - 55100, - 55101, - 55102, - 55103, - 55104, - 55105, - 55106, - 55107, - 55108, - 55109, - 55110, - 55111, - 55112, - 55113, - 55114, - 55115, - 55116, - 55117, - 55118, - 55119, - 55120, - 55121, - 55122, - 55123, - 55124, - 55125, - 55126, - 55127, - 55128, - 55129, - 55130, - 55131, - 55132, - 55133, - 55134, - 55135, - 55136, - 55137, - 55138, - 55139, - 55140, - 55141, - 55142, - 55143, - 55144, - 55145, - 55146, - 55147, - 55148, - 55149, - 55150, - 55151, - 55152, - 55153, - 55154, - 55155, - 55156, - 55157, - 55158, - 55159, - 55160, - 55161, - 55162, - 55163, - 55164, - 55165, - 55166, - 55167, - 55168, - 55169, - 55170, - 55171, - 55172, - 55173, - 55174, - 55175, - 55176, - 55177, - 55178, - 55179, - 55180, - 55181, - 55182, - 55183, - 55184, - 55185, - 55186, - 55187, - 55188, - 55189, - 55190, - 55191, - 55192, - 55193, - 55194, - 55195, - 55196, - 55197, - 55198, - 55199, - 55200, - 55201, - 55202, - 55203, - 55216, - 55217, - 55218, - 55219, - 55220, - 55221, - 55222, - 55223, - 55224, - 55225, - 55226, - 55227, - 55228, - 55229, - 55230, - 55231, - 55232, - 55233, - 55234, - 55235, - 55236, - 55237, - 55238, - 55243, - 55244, - 55245, - 55246, - 55247, - 55248, - 55249, - 55250, - 55251, - 55252, - 55253, - 55254, - 55255, - 55256, - 55257, - 55258, - 55259, - 55260, - 55261, - 55262, - 55263, - 55264, - 55265, - 55266, - 55267, - 55268, - 55269, - 55270, - 55271, - 55272, - 55273, - 55274, - 55275, - 55276, - 55277, - 55278, - 55279, - 55280, - 55281, - 55282, - 55283, - 55284, - 55285, - 55286, - 55287, - 55288, - 55289, - 55290, - 55291, - 63744, - 63745, - 63746, - 63747, - 63748, - 63749, - 63750, - 63751, - 63752, - 63753, - 63754, - 63755, - 63756, - 63757, - 63758, - 63759, - 63760, - 63761, - 63762, - 63763, - 63764, - 63765, - 63766, - 63767, - 63768, - 63769, - 63770, - 63771, - 63772, - 63773, - 63774, - 63775, - 63776, - 63777, - 63778, - 63779, - 63780, - 63781, - 63782, - 63783, - 63784, - 63785, - 63786, - 63787, - 63788, - 63789, - 63790, - 63791, - 63792, - 63793, - 63794, - 63795, - 63796, - 63797, - 63798, - 63799, - 63800, - 63801, - 63802, - 63803, - 63804, - 63805, - 63806, - 63807, - 63808, - 63809, - 63810, - 63811, - 63812, - 63813, - 63814, - 63815, - 63816, - 63817, - 63818, - 63819, - 63820, - 63821, - 63822, - 63823, - 63824, - 63825, - 63826, - 63827, - 63828, - 63829, - 63830, - 63831, - 63832, - 63833, - 63834, - 63835, - 63836, - 63837, - 63838, - 63839, - 63840, - 63841, - 63842, - 63843, - 63844, - 63845, - 63846, - 63847, - 63848, - 63849, - 63850, - 63851, - 63852, - 63853, - 63854, - 63855, - 63856, - 63857, - 63858, - 63859, - 63860, - 63861, - 63862, - 63863, - 63864, - 63865, - 63866, - 63867, - 63868, - 63869, - 63870, - 63871, - 63872, - 63873, - 63874, - 63875, - 63876, - 63877, - 63878, - 63879, - 63880, - 63881, - 63882, - 63883, - 63884, - 63885, - 63886, - 63887, - 63888, - 63889, - 63890, - 63891, - 63892, - 63893, - 63894, - 63895, - 63896, - 63897, - 63898, - 63899, - 63900, - 63901, - 63902, - 63903, - 63904, - 63905, - 63906, - 63907, - 63908, - 63909, - 63910, - 63911, - 63912, - 63913, - 63914, - 63915, - 63916, - 63917, - 63918, - 63919, - 63920, - 63921, - 63922, - 63923, - 63924, - 63925, - 63926, - 63927, - 63928, - 63929, - 63930, - 63931, - 63932, - 63933, - 63934, - 63935, - 63936, - 63937, - 63938, - 63939, - 63940, - 63941, - 63942, - 63943, - 63944, - 63945, - 63946, - 63947, - 63948, - 63949, - 63950, - 63951, - 63952, - 63953, - 63954, - 63955, - 63956, - 63957, - 63958, - 63959, - 63960, - 63961, - 63962, - 63963, - 63964, - 63965, - 63966, - 63967, - 63968, - 63969, - 63970, - 63971, - 63972, - 63973, - 63974, - 63975, - 63976, - 63977, - 63978, - 63979, - 63980, - 63981, - 63982, - 63983, - 63984, - 63985, - 63986, - 63987, - 63988, - 63989, - 63990, - 63991, - 63992, - 63993, - 63994, - 63995, - 63996, - 63997, - 63998, - 63999, - 64000, - 64001, - 64002, - 64003, - 64004, - 64005, - 64006, - 64007, - 64008, - 64009, - 64010, - 64011, - 64012, - 64013, - 64014, - 64015, - 64016, - 64017, - 64018, - 64019, - 64020, - 64021, - 64022, - 64023, - 64024, - 64025, - 64026, - 64027, - 64028, - 64029, - 64030, - 64031, - 64032, - 64033, - 64034, - 64035, - 64036, - 64037, - 64038, - 64039, - 64040, - 64041, - 64042, - 64043, - 64044, - 64045, - 64046, - 64047, - 64048, - 64049, - 64050, - 64051, - 64052, - 64053, - 64054, - 64055, - 64056, - 64057, - 64058, - 64059, - 64060, - 64061, - 64062, - 64063, - 64064, - 64065, - 64066, - 64067, - 64068, - 64069, - 64070, - 64071, - 64072, - 64073, - 64074, - 64075, - 64076, - 64077, - 64078, - 64079, - 64080, - 64081, - 64082, - 64083, - 64084, - 64085, - 64086, - 64087, - 64088, - 64089, - 64090, - 64091, - 64092, - 64093, - 64094, - 64095, - 64096, - 64097, - 64098, - 64099, - 64100, - 64101, - 64102, - 64103, - 64104, - 64105, - 64106, - 64107, - 64108, - 64109, - 64112, - 64113, - 64114, - 64115, - 64116, - 64117, - 64118, - 64119, - 64120, - 64121, - 64122, - 64123, - 64124, - 64125, - 64126, - 64127, - 64128, - 64129, - 64130, - 64131, - 64132, - 64133, - 64134, - 64135, - 64136, - 64137, - 64138, - 64139, - 64140, - 64141, - 64142, - 64143, - 64144, - 64145, - 64146, - 64147, - 64148, - 64149, - 64150, - 64151, - 64152, - 64153, - 64154, - 64155, - 64156, - 64157, - 64158, - 64159, - 64160, - 64161, - 64162, - 64163, - 64164, - 64165, - 64166, - 64167, - 64168, - 64169, - 64170, - 64171, - 64172, - 64173, - 64174, - 64175, - 64176, - 64177, - 64178, - 64179, - 64180, - 64181, - 64182, - 64183, - 64184, - 64185, - 64186, - 64187, - 64188, - 64189, - 64190, - 64191, - 64192, - 64193, - 64194, - 64195, - 64196, - 64197, - 64198, - 64199, - 64200, - 64201, - 64202, - 64203, - 64204, - 64205, - 64206, - 64207, - 64208, - 64209, - 64210, - 64211, - 64212, - 64213, - 64214, - 64215, - 64216, - 64217, - 64256, - 64257, - 64258, - 64259, - 64260, - 64261, - 64262, - 64275, - 64276, - 64277, - 64278, - 64279, - 64285, - 64287, - 64288, - 64289, - 64290, - 64291, - 64292, - 64293, - 64294, - 64295, - 64296, - 64298, - 64299, - 64300, - 64301, - 64302, - 64303, - 64304, - 64305, - 64306, - 64307, - 64308, - 64309, - 64310, - 64312, - 64313, - 64314, - 64315, - 64316, - 64318, - 64320, - 64321, - 64323, - 64324, - 64326, - 64327, - 64328, - 64329, - 64330, - 64331, - 64332, - 64333, - 64334, - 64335, - 64336, - 64337, - 64338, - 64339, - 64340, - 64341, - 64342, - 64343, - 64344, - 64345, - 64346, - 64347, - 64348, - 64349, - 64350, - 64351, - 64352, - 64353, - 64354, - 64355, - 64356, - 64357, - 64358, - 64359, - 64360, - 64361, - 64362, - 64363, - 64364, - 64365, - 64366, - 64367, - 64368, - 64369, - 64370, - 64371, - 64372, - 64373, - 64374, - 64375, - 64376, - 64377, - 64378, - 64379, - 64380, - 64381, - 64382, - 64383, - 64384, - 64385, - 64386, - 64387, - 64388, - 64389, - 64390, - 64391, - 64392, - 64393, - 64394, - 64395, - 64396, - 64397, - 64398, - 64399, - 64400, - 64401, - 64402, - 64403, - 64404, - 64405, - 64406, - 64407, - 64408, - 64409, - 64410, - 64411, - 64412, - 64413, - 64414, - 64415, - 64416, - 64417, - 64418, - 64419, - 64420, - 64421, - 64422, - 64423, - 64424, - 64425, - 64426, - 64427, - 64428, - 64429, - 64430, - 64431, - 64432, - 64433, - 64467, - 64468, - 64469, - 64470, - 64471, - 64472, - 64473, - 64474, - 64475, - 64476, - 64477, - 64478, - 64479, - 64480, - 64481, - 64482, - 64483, - 64484, - 64485, - 64486, - 64487, - 64488, - 64489, - 64490, - 64491, - 64492, - 64493, - 64494, - 64495, - 64496, - 64497, - 64498, - 64499, - 64500, - 64501, - 64502, - 64503, - 64504, - 64505, - 64506, - 64507, - 64508, - 64509, - 64510, - 64511, - 64512, - 64513, - 64514, - 64515, - 64516, - 64517, - 64518, - 64519, - 64520, - 64521, - 64522, - 64523, - 64524, - 64525, - 64526, - 64527, - 64528, - 64529, - 64530, - 64531, - 64532, - 64533, - 64534, - 64535, - 64536, - 64537, - 64538, - 64539, - 64540, - 64541, - 64542, - 64543, - 64544, - 64545, - 64546, - 64547, - 64548, - 64549, - 64550, - 64551, - 64552, - 64553, - 64554, - 64555, - 64556, - 64557, - 64558, - 64559, - 64560, - 64561, - 64562, - 64563, - 64564, - 64565, - 64566, - 64567, - 64568, - 64569, - 64570, - 64571, - 64572, - 64573, - 64574, - 64575, - 64576, - 64577, - 64578, - 64579, - 64580, - 64581, - 64582, - 64583, - 64584, - 64585, - 64586, - 64587, - 64588, - 64589, - 64590, - 64591, - 64592, - 64593, - 64594, - 64595, - 64596, - 64597, - 64598, - 64599, - 64600, - 64601, - 64602, - 64603, - 64604, - 64605, - 64606, - 64607, - 64608, - 64609, - 64610, - 64611, - 64612, - 64613, - 64614, - 64615, - 64616, - 64617, - 64618, - 64619, - 64620, - 64621, - 64622, - 64623, - 64624, - 64625, - 64626, - 64627, - 64628, - 64629, - 64630, - 64631, - 64632, - 64633, - 64634, - 64635, - 64636, - 64637, - 64638, - 64639, - 64640, - 64641, - 64642, - 64643, - 64644, - 64645, - 64646, - 64647, - 64648, - 64649, - 64650, - 64651, - 64652, - 64653, - 64654, - 64655, - 64656, - 64657, - 64658, - 64659, - 64660, - 64661, - 64662, - 64663, - 64664, - 64665, - 64666, - 64667, - 64668, - 64669, - 64670, - 64671, - 64672, - 64673, - 64674, - 64675, - 64676, - 64677, - 64678, - 64679, - 64680, - 64681, - 64682, - 64683, - 64684, - 64685, - 64686, - 64687, - 64688, - 64689, - 64690, - 64691, - 64692, - 64693, - 64694, - 64695, - 64696, - 64697, - 64698, - 64699, - 64700, - 64701, - 64702, - 64703, - 64704, - 64705, - 64706, - 64707, - 64708, - 64709, - 64710, - 64711, - 64712, - 64713, - 64714, - 64715, - 64716, - 64717, - 64718, - 64719, - 64720, - 64721, - 64722, - 64723, - 64724, - 64725, - 64726, - 64727, - 64728, - 64729, - 64730, - 64731, - 64732, - 64733, - 64734, - 64735, - 64736, - 64737, - 64738, - 64739, - 64740, - 64741, - 64742, - 64743, - 64744, - 64745, - 64746, - 64747, - 64748, - 64749, - 64750, - 64751, - 64752, - 64753, - 64754, - 64755, - 64756, - 64757, - 64758, - 64759, - 64760, - 64761, - 64762, - 64763, - 64764, - 64765, - 64766, - 64767, - 64768, - 64769, - 64770, - 64771, - 64772, - 64773, - 64774, - 64775, - 64776, - 64777, - 64778, - 64779, - 64780, - 64781, - 64782, - 64783, - 64784, - 64785, - 64786, - 64787, - 64788, - 64789, - 64790, - 64791, - 64792, - 64793, - 64794, - 64795, - 64796, - 64797, - 64798, - 64799, - 64800, - 64801, - 64802, - 64803, - 64804, - 64805, - 64806, - 64807, - 64808, - 64809, - 64810, - 64811, - 64812, - 64813, - 64814, - 64815, - 64816, - 64817, - 64818, - 64819, - 64820, - 64821, - 64822, - 64823, - 64824, - 64825, - 64826, - 64827, - 64828, - 64829, - 64848, - 64849, - 64850, - 64851, - 64852, - 64853, - 64854, - 64855, - 64856, - 64857, - 64858, - 64859, - 64860, - 64861, - 64862, - 64863, - 64864, - 64865, - 64866, - 64867, - 64868, - 64869, - 64870, - 64871, - 64872, - 64873, - 64874, - 64875, - 64876, - 64877, - 64878, - 64879, - 64880, - 64881, - 64882, - 64883, - 64884, - 64885, - 64886, - 64887, - 64888, - 64889, - 64890, - 64891, - 64892, - 64893, - 64894, - 64895, - 64896, - 64897, - 64898, - 64899, - 64900, - 64901, - 64902, - 64903, - 64904, - 64905, - 64906, - 64907, - 64908, - 64909, - 64910, - 64911, - 64914, - 64915, - 64916, - 64917, - 64918, - 64919, - 64920, - 64921, - 64922, - 64923, - 64924, - 64925, - 64926, - 64927, - 64928, - 64929, - 64930, - 64931, - 64932, - 64933, - 64934, - 64935, - 64936, - 64937, - 64938, - 64939, - 64940, - 64941, - 64942, - 64943, - 64944, - 64945, - 64946, - 64947, - 64948, - 64949, - 64950, - 64951, - 64952, - 64953, - 64954, - 64955, - 64956, - 64957, - 64958, - 64959, - 64960, - 64961, - 64962, - 64963, - 64964, - 64965, - 64966, - 64967, - 65008, - 65009, - 65010, - 65011, - 65012, - 65013, - 65014, - 65015, - 65016, - 65017, - 65018, - 65019, - 65136, - 65137, - 65138, - 65139, - 65140, - 65142, - 65143, - 65144, - 65145, - 65146, - 65147, - 65148, - 65149, - 65150, - 65151, - 65152, - 65153, - 65154, - 65155, - 65156, - 65157, - 65158, - 65159, - 65160, - 65161, - 65162, - 65163, - 65164, - 65165, - 65166, - 65167, - 65168, - 65169, - 65170, - 65171, - 65172, - 65173, - 65174, - 65175, - 65176, - 65177, - 65178, - 65179, - 65180, - 65181, - 65182, - 65183, - 65184, - 65185, - 65186, - 65187, - 65188, - 65189, - 65190, - 65191, - 65192, - 65193, - 65194, - 65195, - 65196, - 65197, - 65198, - 65199, - 65200, - 65201, - 65202, - 65203, - 65204, - 65205, - 65206, - 65207, - 65208, - 65209, - 65210, - 65211, - 65212, - 65213, - 65214, - 65215, - 65216, - 65217, - 65218, - 65219, - 65220, - 65221, - 65222, - 65223, - 65224, - 65225, - 65226, - 65227, - 65228, - 65229, - 65230, - 65231, - 65232, - 65233, - 65234, - 65235, - 65236, - 65237, - 65238, - 65239, - 65240, - 65241, - 65242, - 65243, - 65244, - 65245, - 65246, - 65247, - 65248, - 65249, - 65250, - 65251, - 65252, - 65253, - 65254, - 65255, - 65256, - 65257, - 65258, - 65259, - 65260, - 65261, - 65262, - 65263, - 65264, - 65265, - 65266, - 65267, - 65268, - 65269, - 65270, - 65271, - 65272, - 65273, - 65274, - 65275, - 65276, - 65313, - 65314, - 65315, - 65316, - 65317, - 65318, - 65319, - 65320, - 65321, - 65322, - 65323, - 65324, - 65325, - 65326, - 65327, - 65328, - 65329, - 65330, - 65331, - 65332, - 65333, - 65334, - 65335, - 65336, - 65337, - 65338, - 65345, - 65346, - 65347, - 65348, - 65349, - 65350, - 65351, - 65352, - 65353, - 65354, - 65355, - 65356, - 65357, - 65358, - 65359, - 65360, - 65361, - 65362, - 65363, - 65364, - 65365, - 65366, - 65367, - 65368, - 65369, - 65370, - 65382, - 65383, - 65384, - 65385, - 65386, - 65387, - 65388, - 65389, - 65390, - 65391, - 65392, - 65393, - 65394, - 65395, - 65396, - 65397, - 65398, - 65399, - 65400, - 65401, - 65402, - 65403, - 65404, - 65405, - 65406, - 65407, - 65408, - 65409, - 65410, - 65411, - 65412, - 65413, - 65414, - 65415, - 65416, - 65417, - 65418, - 65419, - 65420, - 65421, - 65422, - 65423, - 65424, - 65425, - 65426, - 65427, - 65428, - 65429, - 65430, - 65431, - 65432, - 65433, - 65434, - 65435, - 65436, - 65437, - 65438, - 65439, - 65440, - 65441, - 65442, - 65443, - 65444, - 65445, - 65446, - 65447, - 65448, - 65449, - 65450, - 65451, - 65452, - 65453, - 65454, - 65455, - 65456, - 65457, - 65458, - 65459, - 65460, - 65461, - 65462, - 65463, - 65464, - 65465, - 65466, - 65467, - 65468, - 65469, - 65470, - 65474, - 65475, - 65476, - 65477, - 65478, - 65479, - 65482, - 65483, - 65484, - 65485, - 65486, - 65487, - 65490, - 65491, - 65492, - 65493, - 65494, - 65495, - 65498, - 65499, - 65500 -]; - -},{}],4:[function(require,module,exports){ -// http://wiki.commonjs.org/wiki/Unit_Testing/1.0 -// -// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! -// -// Originally from narwhal.js (http://narwhaljs.org) -// Copyright (c) 2009 Thomas Robinson <280north.com> -// -// 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. -// -// 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 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. - -// when used in node, this will actually load the util module we depend on -// versus loading the builtin util module as happens otherwise -// this is a bug in node module loading as far as I am concerned -var util = require('util/'); - -var pSlice = Array.prototype.slice; -var hasOwn = Object.prototype.hasOwnProperty; - -// 1. The assert module provides functions that throw -// AssertionError's when particular conditions are not met. The -// assert module must conform to the following interface. - -var assert = module.exports = ok; - -// 2. The AssertionError is defined in assert. -// new assert.AssertionError({ message: message, -// actual: actual, -// expected: expected }) - -assert.AssertionError = function AssertionError(options) { - this.name = 'AssertionError'; - this.actual = options.actual; - this.expected = options.expected; - this.operator = options.operator; - if (options.message) { - this.message = options.message; - this.generatedMessage = false; - } else { - this.message = getMessage(this); - this.generatedMessage = true; - } - var stackStartFunction = options.stackStartFunction || fail; - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, stackStartFunction); - } - else { - // non v8 browsers so we can have a stacktrace - var err = new Error(); - if (err.stack) { - var out = err.stack; - - // try to strip useless frames - var fn_name = stackStartFunction.name; - var idx = out.indexOf('\n' + fn_name); - if (idx >= 0) { - // once we have located the function frame - // we need to strip out everything before it (and its line) - var next_line = out.indexOf('\n', idx + 1); - out = out.substring(next_line + 1); - } - - this.stack = out; - } - } -}; - -// assert.AssertionError instanceof Error -util.inherits(assert.AssertionError, Error); - -function replacer(key, value) { - if (util.isUndefined(value)) { - return '' + value; - } - if (util.isNumber(value) && (isNaN(value) || !isFinite(value))) { - return value.toString(); - } - if (util.isFunction(value) || util.isRegExp(value)) { - return value.toString(); - } - return value; -} - -function truncate(s, n) { - if (util.isString(s)) { - return s.length < n ? s : s.slice(0, n); - } else { - return s; - } -} - -function getMessage(self) { - return truncate(JSON.stringify(self.actual, replacer), 128) + ' ' + - self.operator + ' ' + - truncate(JSON.stringify(self.expected, replacer), 128); -} - -// At present only the three keys mentioned above are used and -// understood by the spec. Implementations or sub modules can pass -// other keys to the AssertionError's constructor - they will be -// ignored. - -// 3. All of the following functions must throw an AssertionError -// when a corresponding condition is not met, with a message that -// may be undefined if not provided. All assertion methods provide -// both the actual and expected values to the assertion error for -// display purposes. - -function fail(actual, expected, message, operator, stackStartFunction) { - throw new assert.AssertionError({ - message: message, - actual: actual, - expected: expected, - operator: operator, - stackStartFunction: stackStartFunction - }); -} - -// EXTENSION! allows for well behaved errors defined elsewhere. -assert.fail = fail; - -// 4. Pure assertion tests whether a value is truthy, as determined -// by !!guard. -// assert.ok(guard, message_opt); -// This statement is equivalent to assert.equal(true, !!guard, -// message_opt);. To test strictly for the value true, use -// assert.strictEqual(true, guard, message_opt);. - -function ok(value, message) { - if (!value) fail(value, true, message, '==', assert.ok); -} -assert.ok = ok; - -// 5. The equality assertion tests shallow, coercive equality with -// ==. -// assert.equal(actual, expected, message_opt); - -assert.equal = function equal(actual, expected, message) { - if (actual != expected) fail(actual, expected, message, '==', assert.equal); -}; - -// 6. The non-equality assertion tests for whether two objects are not equal -// with != assert.notEqual(actual, expected, message_opt); - -assert.notEqual = function notEqual(actual, expected, message) { - if (actual == expected) { - fail(actual, expected, message, '!=', assert.notEqual); - } -}; - -// 7. The equivalence assertion tests a deep equality relation. -// assert.deepEqual(actual, expected, message_opt); - -assert.deepEqual = function deepEqual(actual, expected, message) { - if (!_deepEqual(actual, expected)) { - fail(actual, expected, message, 'deepEqual', assert.deepEqual); - } -}; - -function _deepEqual(actual, expected) { - // 7.1. All identical values are equivalent, as determined by ===. - if (actual === expected) { - return true; - - } else if (util.isBuffer(actual) && util.isBuffer(expected)) { - if (actual.length != expected.length) return false; - - for (var i = 0; i < actual.length; i++) { - if (actual[i] !== expected[i]) return false; - } - - return true; - - // 7.2. If the expected value is a Date object, the actual value is - // equivalent if it is also a Date object that refers to the same time. - } else if (util.isDate(actual) && util.isDate(expected)) { - return actual.getTime() === expected.getTime(); - - // 7.3 If the expected value is a RegExp object, the actual value is - // equivalent if it is also a RegExp object with the same source and - // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). - } else if (util.isRegExp(actual) && util.isRegExp(expected)) { - return actual.source === expected.source && - actual.global === expected.global && - actual.multiline === expected.multiline && - actual.lastIndex === expected.lastIndex && - actual.ignoreCase === expected.ignoreCase; - - // 7.4. Other pairs that do not both pass typeof value == 'object', - // equivalence is determined by ==. - } else if (!util.isObject(actual) && !util.isObject(expected)) { - return actual == expected; - - // 7.5 For all other Object pairs, including Array objects, equivalence is - // determined by having the same number of owned properties (as verified - // with Object.prototype.hasOwnProperty.call), the same set of keys - // (although not necessarily the same order), equivalent values for every - // corresponding key, and an identical 'prototype' property. Note: this - // accounts for both named and indexed properties on Arrays. - } else { - return objEquiv(actual, expected); - } -} - -function isArguments(object) { - return Object.prototype.toString.call(object) == '[object Arguments]'; -} - -function objEquiv(a, b) { - if (util.isNullOrUndefined(a) || util.isNullOrUndefined(b)) - return false; - // an identical 'prototype' property. - if (a.prototype !== b.prototype) return false; - //~~~I've managed to break Object.keys through screwy arguments passing. - // Converting to array solves the problem. - if (isArguments(a)) { - if (!isArguments(b)) { - return false; - } - a = pSlice.call(a); - b = pSlice.call(b); - return _deepEqual(a, b); - } - try { - var ka = objectKeys(a), - kb = objectKeys(b), - key, i; - } catch (e) {//happens when one is a string literal and the other isn't - return false; - } - // having the same number of owned properties (keys incorporates - // hasOwnProperty) - if (ka.length != kb.length) - return false; - //the same set of keys (although not necessarily the same order), - ka.sort(); - kb.sort(); - //~~~cheap key test - for (i = ka.length - 1; i >= 0; i--) { - if (ka[i] != kb[i]) - return false; - } - //equivalent values for every corresponding key, and - //~~~possibly expensive deep test - for (i = ka.length - 1; i >= 0; i--) { - key = ka[i]; - if (!_deepEqual(a[key], b[key])) return false; - } - return true; -} - -// 8. The non-equivalence assertion tests for any deep inequality. -// assert.notDeepEqual(actual, expected, message_opt); - -assert.notDeepEqual = function notDeepEqual(actual, expected, message) { - if (_deepEqual(actual, expected)) { - fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual); - } -}; - -// 9. The strict equality assertion tests strict equality, as determined by ===. -// assert.strictEqual(actual, expected, message_opt); - -assert.strictEqual = function strictEqual(actual, expected, message) { - if (actual !== expected) { - fail(actual, expected, message, '===', assert.strictEqual); - } -}; - -// 10. The strict non-equality assertion tests for strict inequality, as -// determined by !==. assert.notStrictEqual(actual, expected, message_opt); - -assert.notStrictEqual = function notStrictEqual(actual, expected, message) { - if (actual === expected) { - fail(actual, expected, message, '!==', assert.notStrictEqual); - } -}; - -function expectedException(actual, expected) { - if (!actual || !expected) { - return false; - } - - if (Object.prototype.toString.call(expected) == '[object RegExp]') { - return expected.test(actual); - } else if (actual instanceof expected) { - return true; - } else if (expected.call({}, actual) === true) { - return true; - } - - return false; -} - -function _throws(shouldThrow, block, expected, message) { - var actual; - - if (util.isString(expected)) { - message = expected; - expected = null; - } - - try { - block(); - } catch (e) { - actual = e; - } - - message = (expected && expected.name ? ' (' + expected.name + ').' : '.') + - (message ? ' ' + message : '.'); - - if (shouldThrow && !actual) { - fail(actual, expected, 'Missing expected exception' + message); - } - - if (!shouldThrow && expectedException(actual, expected)) { - fail(actual, expected, 'Got unwanted exception' + message); - } - - if ((shouldThrow && actual && expected && - !expectedException(actual, expected)) || (!shouldThrow && actual)) { - throw actual; - } -} - -// 11. Expected to throw an error: -// assert.throws(block, Error_opt, message_opt); - -assert.throws = function(block, /*optional*/error, /*optional*/message) { - _throws.apply(this, [true].concat(pSlice.call(arguments))); -}; - -// EXTENSION! This is annoying to write outside this module. -assert.doesNotThrow = function(block, /*optional*/message) { - _throws.apply(this, [false].concat(pSlice.call(arguments))); -}; - -assert.ifError = function(err) { if (err) {throw err;}}; - -var objectKeys = Object.keys || function (obj) { - var keys = []; - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key); - } - return keys; -}; - -},{"util/":9}],5:[function(require,module,exports){ -// Copyright Joyent, Inc. and other Node contributors. -// -// 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. -// -// 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. - -function EventEmitter() { - this._events = this._events || {}; - this._maxListeners = this._maxListeners || undefined; -} -module.exports = EventEmitter; - -// Backwards-compat with node 0.10.x -EventEmitter.EventEmitter = EventEmitter; - -EventEmitter.prototype._events = undefined; -EventEmitter.prototype._maxListeners = undefined; - -// By default EventEmitters will print a warning if more than 10 listeners are -// added to it. This is a useful default which helps finding memory leaks. -EventEmitter.defaultMaxListeners = 10; - -// Obviously not all Emitters should be limited to 10. This function allows -// that to be increased. Set to zero for unlimited. -EventEmitter.prototype.setMaxListeners = function(n) { - if (!isNumber(n) || n < 0 || isNaN(n)) - throw TypeError('n must be a positive number'); - this._maxListeners = n; - return this; -}; - -EventEmitter.prototype.emit = function(type) { - var er, handler, len, args, i, listeners; - - if (!this._events) - this._events = {}; - - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || - (isObject(this._events.error) && !this._events.error.length)) { - er = arguments[1]; - if (er instanceof Error) { - throw er; // Unhandled 'error' event - } else { - throw TypeError('Uncaught, unspecified "error" event.'); - } - return false; - } - } - - handler = this._events[type]; - - if (isUndefined(handler)) - return false; - - if (isFunction(handler)) { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - len = arguments.length; - args = new Array(len - 1); - for (i = 1; i < len; i++) - args[i - 1] = arguments[i]; - handler.apply(this, args); - } - } else if (isObject(handler)) { - len = arguments.length; - args = new Array(len - 1); - for (i = 1; i < len; i++) - args[i - 1] = arguments[i]; - - listeners = handler.slice(); - len = listeners.length; - for (i = 0; i < len; i++) - listeners[i].apply(this, args); - } - - return true; -}; - -EventEmitter.prototype.addListener = function(type, listener) { - var m; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events) - this._events = {}; - - // To avoid recursion in the case that type === "newListener"! Before - // adding it to the listeners, first emit "newListener". - if (this._events.newListener) - this.emit('newListener', type, - isFunction(listener.listener) ? - listener.listener : listener); - - if (!this._events[type]) - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - else if (isObject(this._events[type])) - // If we've already got an array, just append. - this._events[type].push(listener); - else - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - - // Check for listener leak - if (isObject(this._events[type]) && !this._events[type].warned) { - var m; - if (!isUndefined(this._maxListeners)) { - m = this._maxListeners; - } else { - m = EventEmitter.defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + - 'leak detected. %d listeners added. ' + - 'Use emitter.setMaxListeners() to increase limit.', - this._events[type].length); - console.trace(); - } - } - - return this; -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener) { - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - var fired = false; - - function g() { - this.removeListener(type, g); - - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } - - g.listener = listener; - this.on(type, g); - - return this; -}; - -// emits a 'removeListener' event iff the listener was removed -EventEmitter.prototype.removeListener = function(type, listener) { - var list, position, length, i; - - if (!isFunction(listener)) - throw TypeError('listener must be a function'); - - if (!this._events || !this._events[type]) - return this; - - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || - (isFunction(list.listener) && list.listener === listener)) { - delete this._events[type]; - if (this._events.removeListener) - this.emit('removeListener', type, listener); - - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) { - position = i; - break; - } - } - - if (position < 0) - return this; - - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } - - if (this._events.removeListener) - this.emit('removeListener', type, listener); - } - - return this; -}; - -EventEmitter.prototype.removeAllListeners = function(type) { - var key, listeners; - - if (!this._events) - return this; - - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) - this._events = {}; - else if (this._events[type]) - delete this._events[type]; - return this; - } - - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); - } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; - } - - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else { - // LIFO order - while (listeners.length) - this.removeListener(type, listeners[listeners.length - 1]); - } - delete this._events[type]; - - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - var ret; - if (!this._events || !this._events[type]) - ret = []; - else if (isFunction(this._events[type])) - ret = [this._events[type]]; - else - ret = this._events[type].slice(); - return ret; -}; - -EventEmitter.listenerCount = function(emitter, type) { - var ret; - if (!emitter._events || !emitter._events[type]) - ret = 0; - else if (isFunction(emitter._events[type])) - ret = 1; - else - ret = emitter._events[type].length; - return ret; -}; - -function isFunction(arg) { - return typeof arg === 'function'; -} - -function isNumber(arg) { - return typeof arg === 'number'; -} - -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} - -function isUndefined(arg) { - return arg === void 0; -} - -},{}],6:[function(require,module,exports){ -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } -} - -},{}],7:[function(require,module,exports){ -// shim for using process in browser - -var process = module.exports = {}; - -process.nextTick = (function () { - var canSetImmediate = typeof window !== 'undefined' - && window.setImmediate; - var canPost = typeof window !== 'undefined' - && window.postMessage && window.addEventListener - ; - - if (canSetImmediate) { - return function (f) { return window.setImmediate(f) }; - } - - if (canPost) { - var queue = []; - window.addEventListener('message', function (ev) { - var source = ev.source; - if ((source === window || source === null) && ev.data === 'process-tick') { - ev.stopPropagation(); - if (queue.length > 0) { - var fn = queue.shift(); - fn(); - } - } - }, true); - - return function nextTick(fn) { - queue.push(fn); - window.postMessage('process-tick', '*'); - }; - } - - return function nextTick(fn) { - setTimeout(fn, 0); - }; + var require; + require = (function e(t, n, r) { + function s(o, u) { + if (!n[o]) { + if (!t[o]) { + var a = typeof require == "function" && require; + if (!u && a) return a(o, !0); + if (i) return i(o, !0); + throw new Error("Cannot find module '" + o + "'"); + } + var f = (n[o] = { exports: {} }); + t[o][0].call( + f.exports, + function (e) { + var n = t[o][1][e]; + return s(n ? n : e); + }, + f, + f.exports, + e, + t, + n, + r, + ); + } + return n[o].exports; + } + var i = typeof require == "function" && require; + for (var o = 0; o < r.length; o++) s(r[o]); + return s; + })( + { + 1: [ + function (require, module, exports) { + var identifierStartTable = []; + + for (var i = 0; i < 128; i++) { + identifierStartTable[i] = + i === 36 || // $ + (i >= 65 && i <= 90) || // A-Z + i === 95 || // _ + (i >= 97 && i <= 122); // a-z + } + + var identifierPartTable = []; + + for (var i = 0; i < 128; i++) { + identifierPartTable[i] = + identifierStartTable[i] || // $, _, A-Z, a-z + (i >= 48 && i <= 57); // 0-9 + } + + module.exports = { + asciiIdentifierStartTable: identifierStartTable, + asciiIdentifierPartTable: identifierPartTable, + }; + }, + {}, + ], + 2: [ + function (require, module, exports) { + module.exports = [ + 768, 769, 770, 771, 772, 773, 774, 775, 776, 777, 778, + 779, 780, 781, 782, 783, 784, 785, 786, 787, 788, 789, + 790, 791, 792, 793, 794, 795, 796, 797, 798, 799, 800, + 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, + 812, 813, 814, 815, 816, 817, 818, 819, 820, 821, 822, + 823, 824, 825, 826, 827, 828, 829, 830, 831, 832, 833, + 834, 835, 836, 837, 838, 839, 840, 841, 842, 843, 844, + 845, 846, 847, 848, 849, 850, 851, 852, 853, 854, 855, + 856, 857, 858, 859, 860, 861, 862, 863, 864, 865, 866, + 867, 868, 869, 870, 871, 872, 873, 874, 875, 876, 877, + 878, 879, 1155, 1156, 1157, 1158, 1159, 1425, 1426, + 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435, + 1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, + 1445, 1446, 1447, 1448, 1449, 1450, 1451, 1452, 1453, + 1454, 1455, 1456, 1457, 1458, 1459, 1460, 1461, 1462, + 1463, 1464, 1465, 1466, 1467, 1468, 1469, 1471, 1473, + 1474, 1476, 1477, 1479, 1552, 1553, 1554, 1555, 1556, + 1557, 1558, 1559, 1560, 1561, 1562, 1611, 1612, 1613, + 1614, 1615, 1616, 1617, 1618, 1619, 1620, 1621, 1622, + 1623, 1624, 1625, 1626, 1627, 1628, 1629, 1630, 1631, + 1632, 1633, 1634, 1635, 1636, 1637, 1638, 1639, 1640, + 1641, 1648, 1750, 1751, 1752, 1753, 1754, 1755, 1756, + 1759, 1760, 1761, 1762, 1763, 1764, 1767, 1768, 1770, + 1771, 1772, 1773, 1776, 1777, 1778, 1779, 1780, 1781, + 1782, 1783, 1784, 1785, 1809, 1840, 1841, 1842, 1843, + 1844, 1845, 1846, 1847, 1848, 1849, 1850, 1851, 1852, + 1853, 1854, 1855, 1856, 1857, 1858, 1859, 1860, 1861, + 1862, 1863, 1864, 1865, 1866, 1958, 1959, 1960, 1961, + 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1984, 1985, + 1986, 1987, 1988, 1989, 1990, 1991, 1992, 1993, 2027, + 2028, 2029, 2030, 2031, 2032, 2033, 2034, 2035, 2070, + 2071, 2072, 2073, 2075, 2076, 2077, 2078, 2079, 2080, + 2081, 2082, 2083, 2085, 2086, 2087, 2089, 2090, 2091, + 2092, 2093, 2137, 2138, 2139, 2276, 2277, 2278, 2279, + 2280, 2281, 2282, 2283, 2284, 2285, 2286, 2287, 2288, + 2289, 2290, 2291, 2292, 2293, 2294, 2295, 2296, 2297, + 2298, 2299, 2300, 2301, 2302, 2304, 2305, 2306, 2307, + 2362, 2363, 2364, 2366, 2367, 2368, 2369, 2370, 2371, + 2372, 2373, 2374, 2375, 2376, 2377, 2378, 2379, 2380, + 2381, 2382, 2383, 2385, 2386, 2387, 2388, 2389, 2390, + 2391, 2402, 2403, 2406, 2407, 2408, 2409, 2410, 2411, + 2412, 2413, 2414, 2415, 2433, 2434, 2435, 2492, 2494, + 2495, 2496, 2497, 2498, 2499, 2500, 2503, 2504, 2507, + 2508, 2509, 2519, 2530, 2531, 2534, 2535, 2536, 2537, + 2538, 2539, 2540, 2541, 2542, 2543, 2561, 2562, 2563, + 2620, 2622, 2623, 2624, 2625, 2626, 2631, 2632, 2635, + 2636, 2637, 2641, 2662, 2663, 2664, 2665, 2666, 2667, + 2668, 2669, 2670, 2671, 2672, 2673, 2677, 2689, 2690, + 2691, 2748, 2750, 2751, 2752, 2753, 2754, 2755, 2756, + 2757, 2759, 2760, 2761, 2763, 2764, 2765, 2786, 2787, + 2790, 2791, 2792, 2793, 2794, 2795, 2796, 2797, 2798, + 2799, 2817, 2818, 2819, 2876, 2878, 2879, 2880, 2881, + 2882, 2883, 2884, 2887, 2888, 2891, 2892, 2893, 2902, + 2903, 2914, 2915, 2918, 2919, 2920, 2921, 2922, 2923, + 2924, 2925, 2926, 2927, 2946, 3006, 3007, 3008, 3009, + 3010, 3014, 3015, 3016, 3018, 3019, 3020, 3021, 3031, + 3046, 3047, 3048, 3049, 3050, 3051, 3052, 3053, 3054, + 3055, 3073, 3074, 3075, 3134, 3135, 3136, 3137, 3138, + 3139, 3140, 3142, 3143, 3144, 3146, 3147, 3148, 3149, + 3157, 3158, 3170, 3171, 3174, 3175, 3176, 3177, 3178, + 3179, 3180, 3181, 3182, 3183, 3202, 3203, 3260, 3262, + 3263, 3264, 3265, 3266, 3267, 3268, 3270, 3271, 3272, + 3274, 3275, 3276, 3277, 3285, 3286, 3298, 3299, 3302, + 3303, 3304, 3305, 3306, 3307, 3308, 3309, 3310, 3311, + 3330, 3331, 3390, 3391, 3392, 3393, 3394, 3395, 3396, + 3398, 3399, 3400, 3402, 3403, 3404, 3405, 3415, 3426, + 3427, 3430, 3431, 3432, 3433, 3434, 3435, 3436, 3437, + 3438, 3439, 3458, 3459, 3530, 3535, 3536, 3537, 3538, + 3539, 3540, 3542, 3544, 3545, 3546, 3547, 3548, 3549, + 3550, 3551, 3570, 3571, 3633, 3636, 3637, 3638, 3639, + 3640, 3641, 3642, 3655, 3656, 3657, 3658, 3659, 3660, + 3661, 3662, 3664, 3665, 3666, 3667, 3668, 3669, 3670, + 3671, 3672, 3673, 3761, 3764, 3765, 3766, 3767, 3768, + 3769, 3771, 3772, 3784, 3785, 3786, 3787, 3788, 3789, + 3792, 3793, 3794, 3795, 3796, 3797, 3798, 3799, 3800, + 3801, 3864, 3865, 3872, 3873, 3874, 3875, 3876, 3877, + 3878, 3879, 3880, 3881, 3893, 3895, 3897, 3902, 3903, + 3953, 3954, 3955, 3956, 3957, 3958, 3959, 3960, 3961, + 3962, 3963, 3964, 3965, 3966, 3967, 3968, 3969, 3970, + 3971, 3972, 3974, 3975, 3981, 3982, 3983, 3984, 3985, + 3986, 3987, 3988, 3989, 3990, 3991, 3993, 3994, 3995, + 3996, 3997, 3998, 3999, 4000, 4001, 4002, 4003, 4004, + 4005, 4006, 4007, 4008, 4009, 4010, 4011, 4012, 4013, + 4014, 4015, 4016, 4017, 4018, 4019, 4020, 4021, 4022, + 4023, 4024, 4025, 4026, 4027, 4028, 4038, 4139, 4140, + 4141, 4142, 4143, 4144, 4145, 4146, 4147, 4148, 4149, + 4150, 4151, 4152, 4153, 4154, 4155, 4156, 4157, 4158, + 4160, 4161, 4162, 4163, 4164, 4165, 4166, 4167, 4168, + 4169, 4182, 4183, 4184, 4185, 4190, 4191, 4192, 4194, + 4195, 4196, 4199, 4200, 4201, 4202, 4203, 4204, 4205, + 4209, 4210, 4211, 4212, 4226, 4227, 4228, 4229, 4230, + 4231, 4232, 4233, 4234, 4235, 4236, 4237, 4239, 4240, + 4241, 4242, 4243, 4244, 4245, 4246, 4247, 4248, 4249, + 4250, 4251, 4252, 4253, 4957, 4958, 4959, 5906, 5907, + 5908, 5938, 5939, 5940, 5970, 5971, 6002, 6003, 6068, + 6069, 6070, 6071, 6072, 6073, 6074, 6075, 6076, 6077, + 6078, 6079, 6080, 6081, 6082, 6083, 6084, 6085, 6086, + 6087, 6088, 6089, 6090, 6091, 6092, 6093, 6094, 6095, + 6096, 6097, 6098, 6099, 6109, 6112, 6113, 6114, 6115, + 6116, 6117, 6118, 6119, 6120, 6121, 6155, 6156, 6157, + 6160, 6161, 6162, 6163, 6164, 6165, 6166, 6167, 6168, + 6169, 6313, 6432, 6433, 6434, 6435, 6436, 6437, 6438, + 6439, 6440, 6441, 6442, 6443, 6448, 6449, 6450, 6451, + 6452, 6453, 6454, 6455, 6456, 6457, 6458, 6459, 6470, + 6471, 6472, 6473, 6474, 6475, 6476, 6477, 6478, 6479, + 6576, 6577, 6578, 6579, 6580, 6581, 6582, 6583, 6584, + 6585, 6586, 6587, 6588, 6589, 6590, 6591, 6592, 6600, + 6601, 6608, 6609, 6610, 6611, 6612, 6613, 6614, 6615, + 6616, 6617, 6679, 6680, 6681, 6682, 6683, 6741, 6742, + 6743, 6744, 6745, 6746, 6747, 6748, 6749, 6750, 6752, + 6753, 6754, 6755, 6756, 6757, 6758, 6759, 6760, 6761, + 6762, 6763, 6764, 6765, 6766, 6767, 6768, 6769, 6770, + 6771, 6772, 6773, 6774, 6775, 6776, 6777, 6778, 6779, + 6780, 6783, 6784, 6785, 6786, 6787, 6788, 6789, 6790, + 6791, 6792, 6793, 6800, 6801, 6802, 6803, 6804, 6805, + 6806, 6807, 6808, 6809, 6912, 6913, 6914, 6915, 6916, + 6964, 6965, 6966, 6967, 6968, 6969, 6970, 6971, 6972, + 6973, 6974, 6975, 6976, 6977, 6978, 6979, 6980, 6992, + 6993, 6994, 6995, 6996, 6997, 6998, 6999, 7000, 7001, + 7019, 7020, 7021, 7022, 7023, 7024, 7025, 7026, 7027, + 7040, 7041, 7042, 7073, 7074, 7075, 7076, 7077, 7078, + 7079, 7080, 7081, 7082, 7083, 7084, 7085, 7088, 7089, + 7090, 7091, 7092, 7093, 7094, 7095, 7096, 7097, 7142, + 7143, 7144, 7145, 7146, 7147, 7148, 7149, 7150, 7151, + 7152, 7153, 7154, 7155, 7204, 7205, 7206, 7207, 7208, + 7209, 7210, 7211, 7212, 7213, 7214, 7215, 7216, 7217, + 7218, 7219, 7220, 7221, 7222, 7223, 7232, 7233, 7234, + 7235, 7236, 7237, 7238, 7239, 7240, 7241, 7248, 7249, + 7250, 7251, 7252, 7253, 7254, 7255, 7256, 7257, 7376, + 7377, 7378, 7380, 7381, 7382, 7383, 7384, 7385, 7386, + 7387, 7388, 7389, 7390, 7391, 7392, 7393, 7394, 7395, + 7396, 7397, 7398, 7399, 7400, 7405, 7410, 7411, 7412, + 7616, 7617, 7618, 7619, 7620, 7621, 7622, 7623, 7624, + 7625, 7626, 7627, 7628, 7629, 7630, 7631, 7632, 7633, + 7634, 7635, 7636, 7637, 7638, 7639, 7640, 7641, 7642, + 7643, 7644, 7645, 7646, 7647, 7648, 7649, 7650, 7651, + 7652, 7653, 7654, 7676, 7677, 7678, 7679, 8204, 8205, + 8255, 8256, 8276, 8400, 8401, 8402, 8403, 8404, 8405, + 8406, 8407, 8408, 8409, 8410, 8411, 8412, 8417, 8421, + 8422, 8423, 8424, 8425, 8426, 8427, 8428, 8429, 8430, + 8431, 8432, 11503, 11504, 11505, 11647, 11744, 11745, + 11746, 11747, 11748, 11749, 11750, 11751, 11752, 11753, + 11754, 11755, 11756, 11757, 11758, 11759, 11760, 11761, + 11762, 11763, 11764, 11765, 11766, 11767, 11768, 11769, + 11770, 11771, 11772, 11773, 11774, 11775, 12330, 12331, + 12332, 12333, 12334, 12335, 12441, 12442, 42528, 42529, + 42530, 42531, 42532, 42533, 42534, 42535, 42536, 42537, + 42607, 42612, 42613, 42614, 42615, 42616, 42617, 42618, + 42619, 42620, 42621, 42655, 42736, 42737, 43010, 43014, + 43019, 43043, 43044, 43045, 43046, 43047, 43136, 43137, + 43188, 43189, 43190, 43191, 43192, 43193, 43194, 43195, + 43196, 43197, 43198, 43199, 43200, 43201, 43202, 43203, + 43204, 43216, 43217, 43218, 43219, 43220, 43221, 43222, + 43223, 43224, 43225, 43232, 43233, 43234, 43235, 43236, + 43237, 43238, 43239, 43240, 43241, 43242, 43243, 43244, + 43245, 43246, 43247, 43248, 43249, 43264, 43265, 43266, + 43267, 43268, 43269, 43270, 43271, 43272, 43273, 43302, + 43303, 43304, 43305, 43306, 43307, 43308, 43309, 43335, + 43336, 43337, 43338, 43339, 43340, 43341, 43342, 43343, + 43344, 43345, 43346, 43347, 43392, 43393, 43394, 43395, + 43443, 43444, 43445, 43446, 43447, 43448, 43449, 43450, + 43451, 43452, 43453, 43454, 43455, 43456, 43472, 43473, + 43474, 43475, 43476, 43477, 43478, 43479, 43480, 43481, + 43561, 43562, 43563, 43564, 43565, 43566, 43567, 43568, + 43569, 43570, 43571, 43572, 43573, 43574, 43587, 43596, + 43597, 43600, 43601, 43602, 43603, 43604, 43605, 43606, + 43607, 43608, 43609, 43643, 43696, 43698, 43699, 43700, + 43703, 43704, 43710, 43711, 43713, 43755, 43756, 43757, + 43758, 43759, 43765, 43766, 44003, 44004, 44005, 44006, + 44007, 44008, 44009, 44010, 44012, 44013, 44016, 44017, + 44018, 44019, 44020, 44021, 44022, 44023, 44024, 44025, + 64286, 65024, 65025, 65026, 65027, 65028, 65029, 65030, + 65031, 65032, 65033, 65034, 65035, 65036, 65037, 65038, + 65039, 65056, 65057, 65058, 65059, 65060, 65061, 65062, + 65075, 65076, 65101, 65102, 65103, 65296, 65297, 65298, + 65299, 65300, 65301, 65302, 65303, 65304, 65305, 65343, + ]; + }, + {}, + ], + 3: [ + function (require, module, exports) { + module.exports = [ + 170, 181, 186, 192, 193, 194, 195, 196, 197, 198, 199, + 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, + 211, 212, 213, 214, 216, 217, 218, 219, 220, 221, 222, + 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, + 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, + 245, 246, 248, 249, 250, 251, 252, 253, 254, 255, 256, + 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, + 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, + 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, + 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, + 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, + 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, + 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, + 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, + 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, + 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, + 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, + 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 388, + 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, + 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, + 411, 412, 413, 414, 415, 416, 417, 418, 419, 420, 421, + 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, + 433, 434, 435, 436, 437, 438, 439, 440, 441, 442, 443, + 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, + 455, 456, 457, 458, 459, 460, 461, 462, 463, 464, 465, + 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, + 477, 478, 479, 480, 481, 482, 483, 484, 485, 486, 487, + 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, + 499, 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, + 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, 520, + 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, + 532, 533, 534, 535, 536, 537, 538, 539, 540, 541, 542, + 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, + 554, 555, 556, 557, 558, 559, 560, 561, 562, 563, 564, + 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, + 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, + 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, + 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, 608, + 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, + 620, 621, 622, 623, 624, 625, 626, 627, 628, 629, 630, + 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, + 642, 643, 644, 645, 646, 647, 648, 649, 650, 651, 652, + 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, + 664, 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, + 675, 676, 677, 678, 679, 680, 681, 682, 683, 684, 685, + 686, 687, 688, 689, 690, 691, 692, 693, 694, 695, 696, + 697, 698, 699, 700, 701, 702, 703, 704, 705, 710, 711, + 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, 736, + 737, 738, 739, 740, 748, 750, 880, 881, 882, 883, 884, + 886, 887, 890, 891, 892, 893, 902, 904, 905, 906, 908, + 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, + 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, + 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, + 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, + 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, + 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, + 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, + 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, + 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, + 1008, 1009, 1010, 1011, 1012, 1013, 1015, 1016, 1017, + 1018, 1019, 1020, 1021, 1022, 1023, 1024, 1025, 1026, + 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, + 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, + 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, + 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, + 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, + 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, + 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, + 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, + 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, + 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, + 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124, 1125, + 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, + 1135, 1136, 1137, 1138, 1139, 1140, 1141, 1142, 1143, + 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, + 1153, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, + 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, 1178, + 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, + 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, + 1197, 1198, 1199, 1200, 1201, 1202, 1203, 1204, 1205, + 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, + 1215, 1216, 1217, 1218, 1219, 1220, 1221, 1222, 1223, + 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1232, + 1233, 1234, 1235, 1236, 1237, 1238, 1239, 1240, 1241, + 1242, 1243, 1244, 1245, 1246, 1247, 1248, 1249, 1250, + 1251, 1252, 1253, 1254, 1255, 1256, 1257, 1258, 1259, + 1260, 1261, 1262, 1263, 1264, 1265, 1266, 1267, 1268, + 1269, 1270, 1271, 1272, 1273, 1274, 1275, 1276, 1277, + 1278, 1279, 1280, 1281, 1282, 1283, 1284, 1285, 1286, + 1287, 1288, 1289, 1290, 1291, 1292, 1293, 1294, 1295, + 1296, 1297, 1298, 1299, 1300, 1301, 1302, 1303, 1304, + 1305, 1306, 1307, 1308, 1309, 1310, 1311, 1312, 1313, + 1314, 1315, 1316, 1317, 1318, 1319, 1329, 1330, 1331, + 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340, + 1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, + 1350, 1351, 1352, 1353, 1354, 1355, 1356, 1357, 1358, + 1359, 1360, 1361, 1362, 1363, 1364, 1365, 1366, 1369, + 1377, 1378, 1379, 1380, 1381, 1382, 1383, 1384, 1385, + 1386, 1387, 1388, 1389, 1390, 1391, 1392, 1393, 1394, + 1395, 1396, 1397, 1398, 1399, 1400, 1401, 1402, 1403, + 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, + 1413, 1414, 1415, 1488, 1489, 1490, 1491, 1492, 1493, + 1494, 1495, 1496, 1497, 1498, 1499, 1500, 1501, 1502, + 1503, 1504, 1505, 1506, 1507, 1508, 1509, 1510, 1511, + 1512, 1513, 1514, 1520, 1521, 1522, 1568, 1569, 1570, + 1571, 1572, 1573, 1574, 1575, 1576, 1577, 1578, 1579, + 1580, 1581, 1582, 1583, 1584, 1585, 1586, 1587, 1588, + 1589, 1590, 1591, 1592, 1593, 1594, 1595, 1596, 1597, + 1598, 1599, 1600, 1601, 1602, 1603, 1604, 1605, 1606, + 1607, 1608, 1609, 1610, 1646, 1647, 1649, 1650, 1651, + 1652, 1653, 1654, 1655, 1656, 1657, 1658, 1659, 1660, + 1661, 1662, 1663, 1664, 1665, 1666, 1667, 1668, 1669, + 1670, 1671, 1672, 1673, 1674, 1675, 1676, 1677, 1678, + 1679, 1680, 1681, 1682, 1683, 1684, 1685, 1686, 1687, + 1688, 1689, 1690, 1691, 1692, 1693, 1694, 1695, 1696, + 1697, 1698, 1699, 1700, 1701, 1702, 1703, 1704, 1705, + 1706, 1707, 1708, 1709, 1710, 1711, 1712, 1713, 1714, + 1715, 1716, 1717, 1718, 1719, 1720, 1721, 1722, 1723, + 1724, 1725, 1726, 1727, 1728, 1729, 1730, 1731, 1732, + 1733, 1734, 1735, 1736, 1737, 1738, 1739, 1740, 1741, + 1742, 1743, 1744, 1745, 1746, 1747, 1749, 1765, 1766, + 1774, 1775, 1786, 1787, 1788, 1791, 1808, 1810, 1811, + 1812, 1813, 1814, 1815, 1816, 1817, 1818, 1819, 1820, + 1821, 1822, 1823, 1824, 1825, 1826, 1827, 1828, 1829, + 1830, 1831, 1832, 1833, 1834, 1835, 1836, 1837, 1838, + 1839, 1869, 1870, 1871, 1872, 1873, 1874, 1875, 1876, + 1877, 1878, 1879, 1880, 1881, 1882, 1883, 1884, 1885, + 1886, 1887, 1888, 1889, 1890, 1891, 1892, 1893, 1894, + 1895, 1896, 1897, 1898, 1899, 1900, 1901, 1902, 1903, + 1904, 1905, 1906, 1907, 1908, 1909, 1910, 1911, 1912, + 1913, 1914, 1915, 1916, 1917, 1918, 1919, 1920, 1921, + 1922, 1923, 1924, 1925, 1926, 1927, 1928, 1929, 1930, + 1931, 1932, 1933, 1934, 1935, 1936, 1937, 1938, 1939, + 1940, 1941, 1942, 1943, 1944, 1945, 1946, 1947, 1948, + 1949, 1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, + 1969, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, + 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022, 2023, 2024, 2025, 2026, 2036, 2037, + 2042, 2048, 2049, 2050, 2051, 2052, 2053, 2054, 2055, + 2056, 2057, 2058, 2059, 2060, 2061, 2062, 2063, 2064, + 2065, 2066, 2067, 2068, 2069, 2074, 2084, 2088, 2112, + 2113, 2114, 2115, 2116, 2117, 2118, 2119, 2120, 2121, + 2122, 2123, 2124, 2125, 2126, 2127, 2128, 2129, 2130, + 2131, 2132, 2133, 2134, 2135, 2136, 2208, 2210, 2211, + 2212, 2213, 2214, 2215, 2216, 2217, 2218, 2219, 2220, + 2308, 2309, 2310, 2311, 2312, 2313, 2314, 2315, 2316, + 2317, 2318, 2319, 2320, 2321, 2322, 2323, 2324, 2325, + 2326, 2327, 2328, 2329, 2330, 2331, 2332, 2333, 2334, + 2335, 2336, 2337, 2338, 2339, 2340, 2341, 2342, 2343, + 2344, 2345, 2346, 2347, 2348, 2349, 2350, 2351, 2352, + 2353, 2354, 2355, 2356, 2357, 2358, 2359, 2360, 2361, + 2365, 2384, 2392, 2393, 2394, 2395, 2396, 2397, 2398, + 2399, 2400, 2401, 2417, 2418, 2419, 2420, 2421, 2422, + 2423, 2425, 2426, 2427, 2428, 2429, 2430, 2431, 2437, + 2438, 2439, 2440, 2441, 2442, 2443, 2444, 2447, 2448, + 2451, 2452, 2453, 2454, 2455, 2456, 2457, 2458, 2459, + 2460, 2461, 2462, 2463, 2464, 2465, 2466, 2467, 2468, + 2469, 2470, 2471, 2472, 2474, 2475, 2476, 2477, 2478, + 2479, 2480, 2482, 2486, 2487, 2488, 2489, 2493, 2510, + 2524, 2525, 2527, 2528, 2529, 2544, 2545, 2565, 2566, + 2567, 2568, 2569, 2570, 2575, 2576, 2579, 2580, 2581, + 2582, 2583, 2584, 2585, 2586, 2587, 2588, 2589, 2590, + 2591, 2592, 2593, 2594, 2595, 2596, 2597, 2598, 2599, + 2600, 2602, 2603, 2604, 2605, 2606, 2607, 2608, 2610, + 2611, 2613, 2614, 2616, 2617, 2649, 2650, 2651, 2652, + 2654, 2674, 2675, 2676, 2693, 2694, 2695, 2696, 2697, + 2698, 2699, 2700, 2701, 2703, 2704, 2705, 2707, 2708, + 2709, 2710, 2711, 2712, 2713, 2714, 2715, 2716, 2717, + 2718, 2719, 2720, 2721, 2722, 2723, 2724, 2725, 2726, + 2727, 2728, 2730, 2731, 2732, 2733, 2734, 2735, 2736, + 2738, 2739, 2741, 2742, 2743, 2744, 2745, 2749, 2768, + 2784, 2785, 2821, 2822, 2823, 2824, 2825, 2826, 2827, + 2828, 2831, 2832, 2835, 2836, 2837, 2838, 2839, 2840, + 2841, 2842, 2843, 2844, 2845, 2846, 2847, 2848, 2849, + 2850, 2851, 2852, 2853, 2854, 2855, 2856, 2858, 2859, + 2860, 2861, 2862, 2863, 2864, 2866, 2867, 2869, 2870, + 2871, 2872, 2873, 2877, 2908, 2909, 2911, 2912, 2913, + 2929, 2947, 2949, 2950, 2951, 2952, 2953, 2954, 2958, + 2959, 2960, 2962, 2963, 2964, 2965, 2969, 2970, 2972, + 2974, 2975, 2979, 2980, 2984, 2985, 2986, 2990, 2991, + 2992, 2993, 2994, 2995, 2996, 2997, 2998, 2999, 3000, + 3001, 3024, 3077, 3078, 3079, 3080, 3081, 3082, 3083, + 3084, 3086, 3087, 3088, 3090, 3091, 3092, 3093, 3094, + 3095, 3096, 3097, 3098, 3099, 3100, 3101, 3102, 3103, + 3104, 3105, 3106, 3107, 3108, 3109, 3110, 3111, 3112, + 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3122, + 3123, 3125, 3126, 3127, 3128, 3129, 3133, 3160, 3161, + 3168, 3169, 3205, 3206, 3207, 3208, 3209, 3210, 3211, + 3212, 3214, 3215, 3216, 3218, 3219, 3220, 3221, 3222, + 3223, 3224, 3225, 3226, 3227, 3228, 3229, 3230, 3231, + 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3239, 3240, + 3242, 3243, 3244, 3245, 3246, 3247, 3248, 3249, 3250, + 3251, 3253, 3254, 3255, 3256, 3257, 3261, 3294, 3296, + 3297, 3313, 3314, 3333, 3334, 3335, 3336, 3337, 3338, + 3339, 3340, 3342, 3343, 3344, 3346, 3347, 3348, 3349, + 3350, 3351, 3352, 3353, 3354, 3355, 3356, 3357, 3358, + 3359, 3360, 3361, 3362, 3363, 3364, 3365, 3366, 3367, + 3368, 3369, 3370, 3371, 3372, 3373, 3374, 3375, 3376, + 3377, 3378, 3379, 3380, 3381, 3382, 3383, 3384, 3385, + 3386, 3389, 3406, 3424, 3425, 3450, 3451, 3452, 3453, + 3454, 3455, 3461, 3462, 3463, 3464, 3465, 3466, 3467, + 3468, 3469, 3470, 3471, 3472, 3473, 3474, 3475, 3476, + 3477, 3478, 3482, 3483, 3484, 3485, 3486, 3487, 3488, + 3489, 3490, 3491, 3492, 3493, 3494, 3495, 3496, 3497, + 3498, 3499, 3500, 3501, 3502, 3503, 3504, 3505, 3507, + 3508, 3509, 3510, 3511, 3512, 3513, 3514, 3515, 3517, + 3520, 3521, 3522, 3523, 3524, 3525, 3526, 3585, 3586, + 3587, 3588, 3589, 3590, 3591, 3592, 3593, 3594, 3595, + 3596, 3597, 3598, 3599, 3600, 3601, 3602, 3603, 3604, + 3605, 3606, 3607, 3608, 3609, 3610, 3611, 3612, 3613, + 3614, 3615, 3616, 3617, 3618, 3619, 3620, 3621, 3622, + 3623, 3624, 3625, 3626, 3627, 3628, 3629, 3630, 3631, + 3632, 3634, 3635, 3648, 3649, 3650, 3651, 3652, 3653, + 3654, 3713, 3714, 3716, 3719, 3720, 3722, 3725, 3732, + 3733, 3734, 3735, 3737, 3738, 3739, 3740, 3741, 3742, + 3743, 3745, 3746, 3747, 3749, 3751, 3754, 3755, 3757, + 3758, 3759, 3760, 3762, 3763, 3773, 3776, 3777, 3778, + 3779, 3780, 3782, 3804, 3805, 3806, 3807, 3840, 3904, + 3905, 3906, 3907, 3908, 3909, 3910, 3911, 3913, 3914, + 3915, 3916, 3917, 3918, 3919, 3920, 3921, 3922, 3923, + 3924, 3925, 3926, 3927, 3928, 3929, 3930, 3931, 3932, + 3933, 3934, 3935, 3936, 3937, 3938, 3939, 3940, 3941, + 3942, 3943, 3944, 3945, 3946, 3947, 3948, 3976, 3977, + 3978, 3979, 3980, 4096, 4097, 4098, 4099, 4100, 4101, + 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4110, + 4111, 4112, 4113, 4114, 4115, 4116, 4117, 4118, 4119, + 4120, 4121, 4122, 4123, 4124, 4125, 4126, 4127, 4128, + 4129, 4130, 4131, 4132, 4133, 4134, 4135, 4136, 4137, + 4138, 4159, 4176, 4177, 4178, 4179, 4180, 4181, 4186, + 4187, 4188, 4189, 4193, 4197, 4198, 4206, 4207, 4208, + 4213, 4214, 4215, 4216, 4217, 4218, 4219, 4220, 4221, + 4222, 4223, 4224, 4225, 4238, 4256, 4257, 4258, 4259, + 4260, 4261, 4262, 4263, 4264, 4265, 4266, 4267, 4268, + 4269, 4270, 4271, 4272, 4273, 4274, 4275, 4276, 4277, + 4278, 4279, 4280, 4281, 4282, 4283, 4284, 4285, 4286, + 4287, 4288, 4289, 4290, 4291, 4292, 4293, 4295, 4301, + 4304, 4305, 4306, 4307, 4308, 4309, 4310, 4311, 4312, + 4313, 4314, 4315, 4316, 4317, 4318, 4319, 4320, 4321, + 4322, 4323, 4324, 4325, 4326, 4327, 4328, 4329, 4330, + 4331, 4332, 4333, 4334, 4335, 4336, 4337, 4338, 4339, + 4340, 4341, 4342, 4343, 4344, 4345, 4346, 4348, 4349, + 4350, 4351, 4352, 4353, 4354, 4355, 4356, 4357, 4358, + 4359, 4360, 4361, 4362, 4363, 4364, 4365, 4366, 4367, + 4368, 4369, 4370, 4371, 4372, 4373, 4374, 4375, 4376, + 4377, 4378, 4379, 4380, 4381, 4382, 4383, 4384, 4385, + 4386, 4387, 4388, 4389, 4390, 4391, 4392, 4393, 4394, + 4395, 4396, 4397, 4398, 4399, 4400, 4401, 4402, 4403, + 4404, 4405, 4406, 4407, 4408, 4409, 4410, 4411, 4412, + 4413, 4414, 4415, 4416, 4417, 4418, 4419, 4420, 4421, + 4422, 4423, 4424, 4425, 4426, 4427, 4428, 4429, 4430, + 4431, 4432, 4433, 4434, 4435, 4436, 4437, 4438, 4439, + 4440, 4441, 4442, 4443, 4444, 4445, 4446, 4447, 4448, + 4449, 4450, 4451, 4452, 4453, 4454, 4455, 4456, 4457, + 4458, 4459, 4460, 4461, 4462, 4463, 4464, 4465, 4466, + 4467, 4468, 4469, 4470, 4471, 4472, 4473, 4474, 4475, + 4476, 4477, 4478, 4479, 4480, 4481, 4482, 4483, 4484, + 4485, 4486, 4487, 4488, 4489, 4490, 4491, 4492, 4493, + 4494, 4495, 4496, 4497, 4498, 4499, 4500, 4501, 4502, + 4503, 4504, 4505, 4506, 4507, 4508, 4509, 4510, 4511, + 4512, 4513, 4514, 4515, 4516, 4517, 4518, 4519, 4520, + 4521, 4522, 4523, 4524, 4525, 4526, 4527, 4528, 4529, + 4530, 4531, 4532, 4533, 4534, 4535, 4536, 4537, 4538, + 4539, 4540, 4541, 4542, 4543, 4544, 4545, 4546, 4547, + 4548, 4549, 4550, 4551, 4552, 4553, 4554, 4555, 4556, + 4557, 4558, 4559, 4560, 4561, 4562, 4563, 4564, 4565, + 4566, 4567, 4568, 4569, 4570, 4571, 4572, 4573, 4574, + 4575, 4576, 4577, 4578, 4579, 4580, 4581, 4582, 4583, + 4584, 4585, 4586, 4587, 4588, 4589, 4590, 4591, 4592, + 4593, 4594, 4595, 4596, 4597, 4598, 4599, 4600, 4601, + 4602, 4603, 4604, 4605, 4606, 4607, 4608, 4609, 4610, + 4611, 4612, 4613, 4614, 4615, 4616, 4617, 4618, 4619, + 4620, 4621, 4622, 4623, 4624, 4625, 4626, 4627, 4628, + 4629, 4630, 4631, 4632, 4633, 4634, 4635, 4636, 4637, + 4638, 4639, 4640, 4641, 4642, 4643, 4644, 4645, 4646, + 4647, 4648, 4649, 4650, 4651, 4652, 4653, 4654, 4655, + 4656, 4657, 4658, 4659, 4660, 4661, 4662, 4663, 4664, + 4665, 4666, 4667, 4668, 4669, 4670, 4671, 4672, 4673, + 4674, 4675, 4676, 4677, 4678, 4679, 4680, 4682, 4683, + 4684, 4685, 4688, 4689, 4690, 4691, 4692, 4693, 4694, + 4696, 4698, 4699, 4700, 4701, 4704, 4705, 4706, 4707, + 4708, 4709, 4710, 4711, 4712, 4713, 4714, 4715, 4716, + 4717, 4718, 4719, 4720, 4721, 4722, 4723, 4724, 4725, + 4726, 4727, 4728, 4729, 4730, 4731, 4732, 4733, 4734, + 4735, 4736, 4737, 4738, 4739, 4740, 4741, 4742, 4743, + 4744, 4746, 4747, 4748, 4749, 4752, 4753, 4754, 4755, + 4756, 4757, 4758, 4759, 4760, 4761, 4762, 4763, 4764, + 4765, 4766, 4767, 4768, 4769, 4770, 4771, 4772, 4773, + 4774, 4775, 4776, 4777, 4778, 4779, 4780, 4781, 4782, + 4783, 4784, 4786, 4787, 4788, 4789, 4792, 4793, 4794, + 4795, 4796, 4797, 4798, 4800, 4802, 4803, 4804, 4805, + 4808, 4809, 4810, 4811, 4812, 4813, 4814, 4815, 4816, + 4817, 4818, 4819, 4820, 4821, 4822, 4824, 4825, 4826, + 4827, 4828, 4829, 4830, 4831, 4832, 4833, 4834, 4835, + 4836, 4837, 4838, 4839, 4840, 4841, 4842, 4843, 4844, + 4845, 4846, 4847, 4848, 4849, 4850, 4851, 4852, 4853, + 4854, 4855, 4856, 4857, 4858, 4859, 4860, 4861, 4862, + 4863, 4864, 4865, 4866, 4867, 4868, 4869, 4870, 4871, + 4872, 4873, 4874, 4875, 4876, 4877, 4878, 4879, 4880, + 4882, 4883, 4884, 4885, 4888, 4889, 4890, 4891, 4892, + 4893, 4894, 4895, 4896, 4897, 4898, 4899, 4900, 4901, + 4902, 4903, 4904, 4905, 4906, 4907, 4908, 4909, 4910, + 4911, 4912, 4913, 4914, 4915, 4916, 4917, 4918, 4919, + 4920, 4921, 4922, 4923, 4924, 4925, 4926, 4927, 4928, + 4929, 4930, 4931, 4932, 4933, 4934, 4935, 4936, 4937, + 4938, 4939, 4940, 4941, 4942, 4943, 4944, 4945, 4946, + 4947, 4948, 4949, 4950, 4951, 4952, 4953, 4954, 4992, + 4993, 4994, 4995, 4996, 4997, 4998, 4999, 5000, 5001, + 5002, 5003, 5004, 5005, 5006, 5007, 5024, 5025, 5026, + 5027, 5028, 5029, 5030, 5031, 5032, 5033, 5034, 5035, + 5036, 5037, 5038, 5039, 5040, 5041, 5042, 5043, 5044, + 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, + 5054, 5055, 5056, 5057, 5058, 5059, 5060, 5061, 5062, + 5063, 5064, 5065, 5066, 5067, 5068, 5069, 5070, 5071, + 5072, 5073, 5074, 5075, 5076, 5077, 5078, 5079, 5080, + 5081, 5082, 5083, 5084, 5085, 5086, 5087, 5088, 5089, + 5090, 5091, 5092, 5093, 5094, 5095, 5096, 5097, 5098, + 5099, 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, + 5108, 5121, 5122, 5123, 5124, 5125, 5126, 5127, 5128, + 5129, 5130, 5131, 5132, 5133, 5134, 5135, 5136, 5137, + 5138, 5139, 5140, 5141, 5142, 5143, 5144, 5145, 5146, + 5147, 5148, 5149, 5150, 5151, 5152, 5153, 5154, 5155, + 5156, 5157, 5158, 5159, 5160, 5161, 5162, 5163, 5164, + 5165, 5166, 5167, 5168, 5169, 5170, 5171, 5172, 5173, + 5174, 5175, 5176, 5177, 5178, 5179, 5180, 5181, 5182, + 5183, 5184, 5185, 5186, 5187, 5188, 5189, 5190, 5191, + 5192, 5193, 5194, 5195, 5196, 5197, 5198, 5199, 5200, + 5201, 5202, 5203, 5204, 5205, 5206, 5207, 5208, 5209, + 5210, 5211, 5212, 5213, 5214, 5215, 5216, 5217, 5218, + 5219, 5220, 5221, 5222, 5223, 5224, 5225, 5226, 5227, + 5228, 5229, 5230, 5231, 5232, 5233, 5234, 5235, 5236, + 5237, 5238, 5239, 5240, 5241, 5242, 5243, 5244, 5245, + 5246, 5247, 5248, 5249, 5250, 5251, 5252, 5253, 5254, + 5255, 5256, 5257, 5258, 5259, 5260, 5261, 5262, 5263, + 5264, 5265, 5266, 5267, 5268, 5269, 5270, 5271, 5272, + 5273, 5274, 5275, 5276, 5277, 5278, 5279, 5280, 5281, + 5282, 5283, 5284, 5285, 5286, 5287, 5288, 5289, 5290, + 5291, 5292, 5293, 5294, 5295, 5296, 5297, 5298, 5299, + 5300, 5301, 5302, 5303, 5304, 5305, 5306, 5307, 5308, + 5309, 5310, 5311, 5312, 5313, 5314, 5315, 5316, 5317, + 5318, 5319, 5320, 5321, 5322, 5323, 5324, 5325, 5326, + 5327, 5328, 5329, 5330, 5331, 5332, 5333, 5334, 5335, + 5336, 5337, 5338, 5339, 5340, 5341, 5342, 5343, 5344, + 5345, 5346, 5347, 5348, 5349, 5350, 5351, 5352, 5353, + 5354, 5355, 5356, 5357, 5358, 5359, 5360, 5361, 5362, + 5363, 5364, 5365, 5366, 5367, 5368, 5369, 5370, 5371, + 5372, 5373, 5374, 5375, 5376, 5377, 5378, 5379, 5380, + 5381, 5382, 5383, 5384, 5385, 5386, 5387, 5388, 5389, + 5390, 5391, 5392, 5393, 5394, 5395, 5396, 5397, 5398, + 5399, 5400, 5401, 5402, 5403, 5404, 5405, 5406, 5407, + 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, + 5417, 5418, 5419, 5420, 5421, 5422, 5423, 5424, 5425, + 5426, 5427, 5428, 5429, 5430, 5431, 5432, 5433, 5434, + 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5442, 5443, + 5444, 5445, 5446, 5447, 5448, 5449, 5450, 5451, 5452, + 5453, 5454, 5455, 5456, 5457, 5458, 5459, 5460, 5461, + 5462, 5463, 5464, 5465, 5466, 5467, 5468, 5469, 5470, + 5471, 5472, 5473, 5474, 5475, 5476, 5477, 5478, 5479, + 5480, 5481, 5482, 5483, 5484, 5485, 5486, 5487, 5488, + 5489, 5490, 5491, 5492, 5493, 5494, 5495, 5496, 5497, + 5498, 5499, 5500, 5501, 5502, 5503, 5504, 5505, 5506, + 5507, 5508, 5509, 5510, 5511, 5512, 5513, 5514, 5515, + 5516, 5517, 5518, 5519, 5520, 5521, 5522, 5523, 5524, + 5525, 5526, 5527, 5528, 5529, 5530, 5531, 5532, 5533, + 5534, 5535, 5536, 5537, 5538, 5539, 5540, 5541, 5542, + 5543, 5544, 5545, 5546, 5547, 5548, 5549, 5550, 5551, + 5552, 5553, 5554, 5555, 5556, 5557, 5558, 5559, 5560, + 5561, 5562, 5563, 5564, 5565, 5566, 5567, 5568, 5569, + 5570, 5571, 5572, 5573, 5574, 5575, 5576, 5577, 5578, + 5579, 5580, 5581, 5582, 5583, 5584, 5585, 5586, 5587, + 5588, 5589, 5590, 5591, 5592, 5593, 5594, 5595, 5596, + 5597, 5598, 5599, 5600, 5601, 5602, 5603, 5604, 5605, + 5606, 5607, 5608, 5609, 5610, 5611, 5612, 5613, 5614, + 5615, 5616, 5617, 5618, 5619, 5620, 5621, 5622, 5623, + 5624, 5625, 5626, 5627, 5628, 5629, 5630, 5631, 5632, + 5633, 5634, 5635, 5636, 5637, 5638, 5639, 5640, 5641, + 5642, 5643, 5644, 5645, 5646, 5647, 5648, 5649, 5650, + 5651, 5652, 5653, 5654, 5655, 5656, 5657, 5658, 5659, + 5660, 5661, 5662, 5663, 5664, 5665, 5666, 5667, 5668, + 5669, 5670, 5671, 5672, 5673, 5674, 5675, 5676, 5677, + 5678, 5679, 5680, 5681, 5682, 5683, 5684, 5685, 5686, + 5687, 5688, 5689, 5690, 5691, 5692, 5693, 5694, 5695, + 5696, 5697, 5698, 5699, 5700, 5701, 5702, 5703, 5704, + 5705, 5706, 5707, 5708, 5709, 5710, 5711, 5712, 5713, + 5714, 5715, 5716, 5717, 5718, 5719, 5720, 5721, 5722, + 5723, 5724, 5725, 5726, 5727, 5728, 5729, 5730, 5731, + 5732, 5733, 5734, 5735, 5736, 5737, 5738, 5739, 5740, + 5743, 5744, 5745, 5746, 5747, 5748, 5749, 5750, 5751, + 5752, 5753, 5754, 5755, 5756, 5757, 5758, 5759, 5761, + 5762, 5763, 5764, 5765, 5766, 5767, 5768, 5769, 5770, + 5771, 5772, 5773, 5774, 5775, 5776, 5777, 5778, 5779, + 5780, 5781, 5782, 5783, 5784, 5785, 5786, 5792, 5793, + 5794, 5795, 5796, 5797, 5798, 5799, 5800, 5801, 5802, + 5803, 5804, 5805, 5806, 5807, 5808, 5809, 5810, 5811, + 5812, 5813, 5814, 5815, 5816, 5817, 5818, 5819, 5820, + 5821, 5822, 5823, 5824, 5825, 5826, 5827, 5828, 5829, + 5830, 5831, 5832, 5833, 5834, 5835, 5836, 5837, 5838, + 5839, 5840, 5841, 5842, 5843, 5844, 5845, 5846, 5847, + 5848, 5849, 5850, 5851, 5852, 5853, 5854, 5855, 5856, + 5857, 5858, 5859, 5860, 5861, 5862, 5863, 5864, 5865, + 5866, 5870, 5871, 5872, 5888, 5889, 5890, 5891, 5892, + 5893, 5894, 5895, 5896, 5897, 5898, 5899, 5900, 5902, + 5903, 5904, 5905, 5920, 5921, 5922, 5923, 5924, 5925, + 5926, 5927, 5928, 5929, 5930, 5931, 5932, 5933, 5934, + 5935, 5936, 5937, 5952, 5953, 5954, 5955, 5956, 5957, + 5958, 5959, 5960, 5961, 5962, 5963, 5964, 5965, 5966, + 5967, 5968, 5969, 5984, 5985, 5986, 5987, 5988, 5989, + 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5998, 5999, + 6000, 6016, 6017, 6018, 6019, 6020, 6021, 6022, 6023, + 6024, 6025, 6026, 6027, 6028, 6029, 6030, 6031, 6032, + 6033, 6034, 6035, 6036, 6037, 6038, 6039, 6040, 6041, + 6042, 6043, 6044, 6045, 6046, 6047, 6048, 6049, 6050, + 6051, 6052, 6053, 6054, 6055, 6056, 6057, 6058, 6059, + 6060, 6061, 6062, 6063, 6064, 6065, 6066, 6067, 6103, + 6108, 6176, 6177, 6178, 6179, 6180, 6181, 6182, 6183, + 6184, 6185, 6186, 6187, 6188, 6189, 6190, 6191, 6192, + 6193, 6194, 6195, 6196, 6197, 6198, 6199, 6200, 6201, + 6202, 6203, 6204, 6205, 6206, 6207, 6208, 6209, 6210, + 6211, 6212, 6213, 6214, 6215, 6216, 6217, 6218, 6219, + 6220, 6221, 6222, 6223, 6224, 6225, 6226, 6227, 6228, + 6229, 6230, 6231, 6232, 6233, 6234, 6235, 6236, 6237, + 6238, 6239, 6240, 6241, 6242, 6243, 6244, 6245, 6246, + 6247, 6248, 6249, 6250, 6251, 6252, 6253, 6254, 6255, + 6256, 6257, 6258, 6259, 6260, 6261, 6262, 6263, 6272, + 6273, 6274, 6275, 6276, 6277, 6278, 6279, 6280, 6281, + 6282, 6283, 6284, 6285, 6286, 6287, 6288, 6289, 6290, + 6291, 6292, 6293, 6294, 6295, 6296, 6297, 6298, 6299, + 6300, 6301, 6302, 6303, 6304, 6305, 6306, 6307, 6308, + 6309, 6310, 6311, 6312, 6314, 6320, 6321, 6322, 6323, + 6324, 6325, 6326, 6327, 6328, 6329, 6330, 6331, 6332, + 6333, 6334, 6335, 6336, 6337, 6338, 6339, 6340, 6341, + 6342, 6343, 6344, 6345, 6346, 6347, 6348, 6349, 6350, + 6351, 6352, 6353, 6354, 6355, 6356, 6357, 6358, 6359, + 6360, 6361, 6362, 6363, 6364, 6365, 6366, 6367, 6368, + 6369, 6370, 6371, 6372, 6373, 6374, 6375, 6376, 6377, + 6378, 6379, 6380, 6381, 6382, 6383, 6384, 6385, 6386, + 6387, 6388, 6389, 6400, 6401, 6402, 6403, 6404, 6405, + 6406, 6407, 6408, 6409, 6410, 6411, 6412, 6413, 6414, + 6415, 6416, 6417, 6418, 6419, 6420, 6421, 6422, 6423, + 6424, 6425, 6426, 6427, 6428, 6480, 6481, 6482, 6483, + 6484, 6485, 6486, 6487, 6488, 6489, 6490, 6491, 6492, + 6493, 6494, 6495, 6496, 6497, 6498, 6499, 6500, 6501, + 6502, 6503, 6504, 6505, 6506, 6507, 6508, 6509, 6512, + 6513, 6514, 6515, 6516, 6528, 6529, 6530, 6531, 6532, + 6533, 6534, 6535, 6536, 6537, 6538, 6539, 6540, 6541, + 6542, 6543, 6544, 6545, 6546, 6547, 6548, 6549, 6550, + 6551, 6552, 6553, 6554, 6555, 6556, 6557, 6558, 6559, + 6560, 6561, 6562, 6563, 6564, 6565, 6566, 6567, 6568, + 6569, 6570, 6571, 6593, 6594, 6595, 6596, 6597, 6598, + 6599, 6656, 6657, 6658, 6659, 6660, 6661, 6662, 6663, + 6664, 6665, 6666, 6667, 6668, 6669, 6670, 6671, 6672, + 6673, 6674, 6675, 6676, 6677, 6678, 6688, 6689, 6690, + 6691, 6692, 6693, 6694, 6695, 6696, 6697, 6698, 6699, + 6700, 6701, 6702, 6703, 6704, 6705, 6706, 6707, 6708, + 6709, 6710, 6711, 6712, 6713, 6714, 6715, 6716, 6717, + 6718, 6719, 6720, 6721, 6722, 6723, 6724, 6725, 6726, + 6727, 6728, 6729, 6730, 6731, 6732, 6733, 6734, 6735, + 6736, 6737, 6738, 6739, 6740, 6823, 6917, 6918, 6919, + 6920, 6921, 6922, 6923, 6924, 6925, 6926, 6927, 6928, + 6929, 6930, 6931, 6932, 6933, 6934, 6935, 6936, 6937, + 6938, 6939, 6940, 6941, 6942, 6943, 6944, 6945, 6946, + 6947, 6948, 6949, 6950, 6951, 6952, 6953, 6954, 6955, + 6956, 6957, 6958, 6959, 6960, 6961, 6962, 6963, 6981, + 6982, 6983, 6984, 6985, 6986, 6987, 7043, 7044, 7045, + 7046, 7047, 7048, 7049, 7050, 7051, 7052, 7053, 7054, + 7055, 7056, 7057, 7058, 7059, 7060, 7061, 7062, 7063, + 7064, 7065, 7066, 7067, 7068, 7069, 7070, 7071, 7072, + 7086, 7087, 7098, 7099, 7100, 7101, 7102, 7103, 7104, + 7105, 7106, 7107, 7108, 7109, 7110, 7111, 7112, 7113, + 7114, 7115, 7116, 7117, 7118, 7119, 7120, 7121, 7122, + 7123, 7124, 7125, 7126, 7127, 7128, 7129, 7130, 7131, + 7132, 7133, 7134, 7135, 7136, 7137, 7138, 7139, 7140, + 7141, 7168, 7169, 7170, 7171, 7172, 7173, 7174, 7175, + 7176, 7177, 7178, 7179, 7180, 7181, 7182, 7183, 7184, + 7185, 7186, 7187, 7188, 7189, 7190, 7191, 7192, 7193, + 7194, 7195, 7196, 7197, 7198, 7199, 7200, 7201, 7202, + 7203, 7245, 7246, 7247, 7258, 7259, 7260, 7261, 7262, + 7263, 7264, 7265, 7266, 7267, 7268, 7269, 7270, 7271, + 7272, 7273, 7274, 7275, 7276, 7277, 7278, 7279, 7280, + 7281, 7282, 7283, 7284, 7285, 7286, 7287, 7288, 7289, + 7290, 7291, 7292, 7293, 7401, 7402, 7403, 7404, 7406, + 7407, 7408, 7409, 7413, 7414, 7424, 7425, 7426, 7427, + 7428, 7429, 7430, 7431, 7432, 7433, 7434, 7435, 7436, + 7437, 7438, 7439, 7440, 7441, 7442, 7443, 7444, 7445, + 7446, 7447, 7448, 7449, 7450, 7451, 7452, 7453, 7454, + 7455, 7456, 7457, 7458, 7459, 7460, 7461, 7462, 7463, + 7464, 7465, 7466, 7467, 7468, 7469, 7470, 7471, 7472, + 7473, 7474, 7475, 7476, 7477, 7478, 7479, 7480, 7481, + 7482, 7483, 7484, 7485, 7486, 7487, 7488, 7489, 7490, + 7491, 7492, 7493, 7494, 7495, 7496, 7497, 7498, 7499, + 7500, 7501, 7502, 7503, 7504, 7505, 7506, 7507, 7508, + 7509, 7510, 7511, 7512, 7513, 7514, 7515, 7516, 7517, + 7518, 7519, 7520, 7521, 7522, 7523, 7524, 7525, 7526, + 7527, 7528, 7529, 7530, 7531, 7532, 7533, 7534, 7535, + 7536, 7537, 7538, 7539, 7540, 7541, 7542, 7543, 7544, + 7545, 7546, 7547, 7548, 7549, 7550, 7551, 7552, 7553, + 7554, 7555, 7556, 7557, 7558, 7559, 7560, 7561, 7562, + 7563, 7564, 7565, 7566, 7567, 7568, 7569, 7570, 7571, + 7572, 7573, 7574, 7575, 7576, 7577, 7578, 7579, 7580, + 7581, 7582, 7583, 7584, 7585, 7586, 7587, 7588, 7589, + 7590, 7591, 7592, 7593, 7594, 7595, 7596, 7597, 7598, + 7599, 7600, 7601, 7602, 7603, 7604, 7605, 7606, 7607, + 7608, 7609, 7610, 7611, 7612, 7613, 7614, 7615, 7680, + 7681, 7682, 7683, 7684, 7685, 7686, 7687, 7688, 7689, + 7690, 7691, 7692, 7693, 7694, 7695, 7696, 7697, 7698, + 7699, 7700, 7701, 7702, 7703, 7704, 7705, 7706, 7707, + 7708, 7709, 7710, 7711, 7712, 7713, 7714, 7715, 7716, + 7717, 7718, 7719, 7720, 7721, 7722, 7723, 7724, 7725, + 7726, 7727, 7728, 7729, 7730, 7731, 7732, 7733, 7734, + 7735, 7736, 7737, 7738, 7739, 7740, 7741, 7742, 7743, + 7744, 7745, 7746, 7747, 7748, 7749, 7750, 7751, 7752, + 7753, 7754, 7755, 7756, 7757, 7758, 7759, 7760, 7761, + 7762, 7763, 7764, 7765, 7766, 7767, 7768, 7769, 7770, + 7771, 7772, 7773, 7774, 7775, 7776, 7777, 7778, 7779, + 7780, 7781, 7782, 7783, 7784, 7785, 7786, 7787, 7788, + 7789, 7790, 7791, 7792, 7793, 7794, 7795, 7796, 7797, + 7798, 7799, 7800, 7801, 7802, 7803, 7804, 7805, 7806, + 7807, 7808, 7809, 7810, 7811, 7812, 7813, 7814, 7815, + 7816, 7817, 7818, 7819, 7820, 7821, 7822, 7823, 7824, + 7825, 7826, 7827, 7828, 7829, 7830, 7831, 7832, 7833, + 7834, 7835, 7836, 7837, 7838, 7839, 7840, 7841, 7842, + 7843, 7844, 7845, 7846, 7847, 7848, 7849, 7850, 7851, + 7852, 7853, 7854, 7855, 7856, 7857, 7858, 7859, 7860, + 7861, 7862, 7863, 7864, 7865, 7866, 7867, 7868, 7869, + 7870, 7871, 7872, 7873, 7874, 7875, 7876, 7877, 7878, + 7879, 7880, 7881, 7882, 7883, 7884, 7885, 7886, 7887, + 7888, 7889, 7890, 7891, 7892, 7893, 7894, 7895, 7896, + 7897, 7898, 7899, 7900, 7901, 7902, 7903, 7904, 7905, + 7906, 7907, 7908, 7909, 7910, 7911, 7912, 7913, 7914, + 7915, 7916, 7917, 7918, 7919, 7920, 7921, 7922, 7923, + 7924, 7925, 7926, 7927, 7928, 7929, 7930, 7931, 7932, + 7933, 7934, 7935, 7936, 7937, 7938, 7939, 7940, 7941, + 7942, 7943, 7944, 7945, 7946, 7947, 7948, 7949, 7950, + 7951, 7952, 7953, 7954, 7955, 7956, 7957, 7960, 7961, + 7962, 7963, 7964, 7965, 7968, 7969, 7970, 7971, 7972, + 7973, 7974, 7975, 7976, 7977, 7978, 7979, 7980, 7981, + 7982, 7983, 7984, 7985, 7986, 7987, 7988, 7989, 7990, + 7991, 7992, 7993, 7994, 7995, 7996, 7997, 7998, 7999, + 8000, 8001, 8002, 8003, 8004, 8005, 8008, 8009, 8010, + 8011, 8012, 8013, 8016, 8017, 8018, 8019, 8020, 8021, + 8022, 8023, 8025, 8027, 8029, 8031, 8032, 8033, 8034, + 8035, 8036, 8037, 8038, 8039, 8040, 8041, 8042, 8043, + 8044, 8045, 8046, 8047, 8048, 8049, 8050, 8051, 8052, + 8053, 8054, 8055, 8056, 8057, 8058, 8059, 8060, 8061, + 8064, 8065, 8066, 8067, 8068, 8069, 8070, 8071, 8072, + 8073, 8074, 8075, 8076, 8077, 8078, 8079, 8080, 8081, + 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, + 8091, 8092, 8093, 8094, 8095, 8096, 8097, 8098, 8099, + 8100, 8101, 8102, 8103, 8104, 8105, 8106, 8107, 8108, + 8109, 8110, 8111, 8112, 8113, 8114, 8115, 8116, 8118, + 8119, 8120, 8121, 8122, 8123, 8124, 8126, 8130, 8131, + 8132, 8134, 8135, 8136, 8137, 8138, 8139, 8140, 8144, + 8145, 8146, 8147, 8150, 8151, 8152, 8153, 8154, 8155, + 8160, 8161, 8162, 8163, 8164, 8165, 8166, 8167, 8168, + 8169, 8170, 8171, 8172, 8178, 8179, 8180, 8182, 8183, + 8184, 8185, 8186, 8187, 8188, 8305, 8319, 8336, 8337, + 8338, 8339, 8340, 8341, 8342, 8343, 8344, 8345, 8346, + 8347, 8348, 8450, 8455, 8458, 8459, 8460, 8461, 8462, + 8463, 8464, 8465, 8466, 8467, 8469, 8473, 8474, 8475, + 8476, 8477, 8484, 8486, 8488, 8490, 8491, 8492, 8493, + 8495, 8496, 8497, 8498, 8499, 8500, 8501, 8502, 8503, + 8504, 8505, 8508, 8509, 8510, 8511, 8517, 8518, 8519, + 8520, 8521, 8526, 8544, 8545, 8546, 8547, 8548, 8549, + 8550, 8551, 8552, 8553, 8554, 8555, 8556, 8557, 8558, + 8559, 8560, 8561, 8562, 8563, 8564, 8565, 8566, 8567, + 8568, 8569, 8570, 8571, 8572, 8573, 8574, 8575, 8576, + 8577, 8578, 8579, 8580, 8581, 8582, 8583, 8584, 11264, + 11265, 11266, 11267, 11268, 11269, 11270, 11271, 11272, + 11273, 11274, 11275, 11276, 11277, 11278, 11279, 11280, + 11281, 11282, 11283, 11284, 11285, 11286, 11287, 11288, + 11289, 11290, 11291, 11292, 11293, 11294, 11295, 11296, + 11297, 11298, 11299, 11300, 11301, 11302, 11303, 11304, + 11305, 11306, 11307, 11308, 11309, 11310, 11312, 11313, + 11314, 11315, 11316, 11317, 11318, 11319, 11320, 11321, + 11322, 11323, 11324, 11325, 11326, 11327, 11328, 11329, + 11330, 11331, 11332, 11333, 11334, 11335, 11336, 11337, + 11338, 11339, 11340, 11341, 11342, 11343, 11344, 11345, + 11346, 11347, 11348, 11349, 11350, 11351, 11352, 11353, + 11354, 11355, 11356, 11357, 11358, 11360, 11361, 11362, + 11363, 11364, 11365, 11366, 11367, 11368, 11369, 11370, + 11371, 11372, 11373, 11374, 11375, 11376, 11377, 11378, + 11379, 11380, 11381, 11382, 11383, 11384, 11385, 11386, + 11387, 11388, 11389, 11390, 11391, 11392, 11393, 11394, + 11395, 11396, 11397, 11398, 11399, 11400, 11401, 11402, + 11403, 11404, 11405, 11406, 11407, 11408, 11409, 11410, + 11411, 11412, 11413, 11414, 11415, 11416, 11417, 11418, + 11419, 11420, 11421, 11422, 11423, 11424, 11425, 11426, + 11427, 11428, 11429, 11430, 11431, 11432, 11433, 11434, + 11435, 11436, 11437, 11438, 11439, 11440, 11441, 11442, + 11443, 11444, 11445, 11446, 11447, 11448, 11449, 11450, + 11451, 11452, 11453, 11454, 11455, 11456, 11457, 11458, + 11459, 11460, 11461, 11462, 11463, 11464, 11465, 11466, + 11467, 11468, 11469, 11470, 11471, 11472, 11473, 11474, + 11475, 11476, 11477, 11478, 11479, 11480, 11481, 11482, + 11483, 11484, 11485, 11486, 11487, 11488, 11489, 11490, + 11491, 11492, 11499, 11500, 11501, 11502, 11506, 11507, + 11520, 11521, 11522, 11523, 11524, 11525, 11526, 11527, + 11528, 11529, 11530, 11531, 11532, 11533, 11534, 11535, + 11536, 11537, 11538, 11539, 11540, 11541, 11542, 11543, + 11544, 11545, 11546, 11547, 11548, 11549, 11550, 11551, + 11552, 11553, 11554, 11555, 11556, 11557, 11559, 11565, + 11568, 11569, 11570, 11571, 11572, 11573, 11574, 11575, + 11576, 11577, 11578, 11579, 11580, 11581, 11582, 11583, + 11584, 11585, 11586, 11587, 11588, 11589, 11590, 11591, + 11592, 11593, 11594, 11595, 11596, 11597, 11598, 11599, + 11600, 11601, 11602, 11603, 11604, 11605, 11606, 11607, + 11608, 11609, 11610, 11611, 11612, 11613, 11614, 11615, + 11616, 11617, 11618, 11619, 11620, 11621, 11622, 11623, + 11631, 11648, 11649, 11650, 11651, 11652, 11653, 11654, + 11655, 11656, 11657, 11658, 11659, 11660, 11661, 11662, + 11663, 11664, 11665, 11666, 11667, 11668, 11669, 11670, + 11680, 11681, 11682, 11683, 11684, 11685, 11686, 11688, + 11689, 11690, 11691, 11692, 11693, 11694, 11696, 11697, + 11698, 11699, 11700, 11701, 11702, 11704, 11705, 11706, + 11707, 11708, 11709, 11710, 11712, 11713, 11714, 11715, + 11716, 11717, 11718, 11720, 11721, 11722, 11723, 11724, + 11725, 11726, 11728, 11729, 11730, 11731, 11732, 11733, + 11734, 11736, 11737, 11738, 11739, 11740, 11741, 11742, + 11823, 12293, 12294, 12295, 12321, 12322, 12323, 12324, + 12325, 12326, 12327, 12328, 12329, 12337, 12338, 12339, + 12340, 12341, 12344, 12345, 12346, 12347, 12348, 12353, + 12354, 12355, 12356, 12357, 12358, 12359, 12360, 12361, + 12362, 12363, 12364, 12365, 12366, 12367, 12368, 12369, + 12370, 12371, 12372, 12373, 12374, 12375, 12376, 12377, + 12378, 12379, 12380, 12381, 12382, 12383, 12384, 12385, + 12386, 12387, 12388, 12389, 12390, 12391, 12392, 12393, + 12394, 12395, 12396, 12397, 12398, 12399, 12400, 12401, + 12402, 12403, 12404, 12405, 12406, 12407, 12408, 12409, + 12410, 12411, 12412, 12413, 12414, 12415, 12416, 12417, + 12418, 12419, 12420, 12421, 12422, 12423, 12424, 12425, + 12426, 12427, 12428, 12429, 12430, 12431, 12432, 12433, + 12434, 12435, 12436, 12437, 12438, 12445, 12446, 12447, + 12449, 12450, 12451, 12452, 12453, 12454, 12455, 12456, + 12457, 12458, 12459, 12460, 12461, 12462, 12463, 12464, + 12465, 12466, 12467, 12468, 12469, 12470, 12471, 12472, + 12473, 12474, 12475, 12476, 12477, 12478, 12479, 12480, + 12481, 12482, 12483, 12484, 12485, 12486, 12487, 12488, + 12489, 12490, 12491, 12492, 12493, 12494, 12495, 12496, + 12497, 12498, 12499, 12500, 12501, 12502, 12503, 12504, + 12505, 12506, 12507, 12508, 12509, 12510, 12511, 12512, + 12513, 12514, 12515, 12516, 12517, 12518, 12519, 12520, + 12521, 12522, 12523, 12524, 12525, 12526, 12527, 12528, + 12529, 12530, 12531, 12532, 12533, 12534, 12535, 12536, + 12537, 12538, 12540, 12541, 12542, 12543, 12549, 12550, + 12551, 12552, 12553, 12554, 12555, 12556, 12557, 12558, + 12559, 12560, 12561, 12562, 12563, 12564, 12565, 12566, + 12567, 12568, 12569, 12570, 12571, 12572, 12573, 12574, + 12575, 12576, 12577, 12578, 12579, 12580, 12581, 12582, + 12583, 12584, 12585, 12586, 12587, 12588, 12589, 12593, + 12594, 12595, 12596, 12597, 12598, 12599, 12600, 12601, + 12602, 12603, 12604, 12605, 12606, 12607, 12608, 12609, + 12610, 12611, 12612, 12613, 12614, 12615, 12616, 12617, + 12618, 12619, 12620, 12621, 12622, 12623, 12624, 12625, + 12626, 12627, 12628, 12629, 12630, 12631, 12632, 12633, + 12634, 12635, 12636, 12637, 12638, 12639, 12640, 12641, + 12642, 12643, 12644, 12645, 12646, 12647, 12648, 12649, + 12650, 12651, 12652, 12653, 12654, 12655, 12656, 12657, + 12658, 12659, 12660, 12661, 12662, 12663, 12664, 12665, + 12666, 12667, 12668, 12669, 12670, 12671, 12672, 12673, + 12674, 12675, 12676, 12677, 12678, 12679, 12680, 12681, + 12682, 12683, 12684, 12685, 12686, 12704, 12705, 12706, + 12707, 12708, 12709, 12710, 12711, 12712, 12713, 12714, + 12715, 12716, 12717, 12718, 12719, 12720, 12721, 12722, + 12723, 12724, 12725, 12726, 12727, 12728, 12729, 12730, + 12784, 12785, 12786, 12787, 12788, 12789, 12790, 12791, + 12792, 12793, 12794, 12795, 12796, 12797, 12798, 12799, + 13312, 13313, 13314, 13315, 13316, 13317, 13318, 13319, + 13320, 13321, 13322, 13323, 13324, 13325, 13326, 13327, + 13328, 13329, 13330, 13331, 13332, 13333, 13334, 13335, + 13336, 13337, 13338, 13339, 13340, 13341, 13342, 13343, + 13344, 13345, 13346, 13347, 13348, 13349, 13350, 13351, + 13352, 13353, 13354, 13355, 13356, 13357, 13358, 13359, + 13360, 13361, 13362, 13363, 13364, 13365, 13366, 13367, + 13368, 13369, 13370, 13371, 13372, 13373, 13374, 13375, + 13376, 13377, 13378, 13379, 13380, 13381, 13382, 13383, + 13384, 13385, 13386, 13387, 13388, 13389, 13390, 13391, + 13392, 13393, 13394, 13395, 13396, 13397, 13398, 13399, + 13400, 13401, 13402, 13403, 13404, 13405, 13406, 13407, + 13408, 13409, 13410, 13411, 13412, 13413, 13414, 13415, + 13416, 13417, 13418, 13419, 13420, 13421, 13422, 13423, + 13424, 13425, 13426, 13427, 13428, 13429, 13430, 13431, + 13432, 13433, 13434, 13435, 13436, 13437, 13438, 13439, + 13440, 13441, 13442, 13443, 13444, 13445, 13446, 13447, + 13448, 13449, 13450, 13451, 13452, 13453, 13454, 13455, + 13456, 13457, 13458, 13459, 13460, 13461, 13462, 13463, + 13464, 13465, 13466, 13467, 13468, 13469, 13470, 13471, + 13472, 13473, 13474, 13475, 13476, 13477, 13478, 13479, + 13480, 13481, 13482, 13483, 13484, 13485, 13486, 13487, + 13488, 13489, 13490, 13491, 13492, 13493, 13494, 13495, + 13496, 13497, 13498, 13499, 13500, 13501, 13502, 13503, + 13504, 13505, 13506, 13507, 13508, 13509, 13510, 13511, + 13512, 13513, 13514, 13515, 13516, 13517, 13518, 13519, + 13520, 13521, 13522, 13523, 13524, 13525, 13526, 13527, + 13528, 13529, 13530, 13531, 13532, 13533, 13534, 13535, + 13536, 13537, 13538, 13539, 13540, 13541, 13542, 13543, + 13544, 13545, 13546, 13547, 13548, 13549, 13550, 13551, + 13552, 13553, 13554, 13555, 13556, 13557, 13558, 13559, + 13560, 13561, 13562, 13563, 13564, 13565, 13566, 13567, + 13568, 13569, 13570, 13571, 13572, 13573, 13574, 13575, + 13576, 13577, 13578, 13579, 13580, 13581, 13582, 13583, + 13584, 13585, 13586, 13587, 13588, 13589, 13590, 13591, + 13592, 13593, 13594, 13595, 13596, 13597, 13598, 13599, + 13600, 13601, 13602, 13603, 13604, 13605, 13606, 13607, + 13608, 13609, 13610, 13611, 13612, 13613, 13614, 13615, + 13616, 13617, 13618, 13619, 13620, 13621, 13622, 13623, + 13624, 13625, 13626, 13627, 13628, 13629, 13630, 13631, + 13632, 13633, 13634, 13635, 13636, 13637, 13638, 13639, + 13640, 13641, 13642, 13643, 13644, 13645, 13646, 13647, + 13648, 13649, 13650, 13651, 13652, 13653, 13654, 13655, + 13656, 13657, 13658, 13659, 13660, 13661, 13662, 13663, + 13664, 13665, 13666, 13667, 13668, 13669, 13670, 13671, + 13672, 13673, 13674, 13675, 13676, 13677, 13678, 13679, + 13680, 13681, 13682, 13683, 13684, 13685, 13686, 13687, + 13688, 13689, 13690, 13691, 13692, 13693, 13694, 13695, + 13696, 13697, 13698, 13699, 13700, 13701, 13702, 13703, + 13704, 13705, 13706, 13707, 13708, 13709, 13710, 13711, + 13712, 13713, 13714, 13715, 13716, 13717, 13718, 13719, + 13720, 13721, 13722, 13723, 13724, 13725, 13726, 13727, + 13728, 13729, 13730, 13731, 13732, 13733, 13734, 13735, + 13736, 13737, 13738, 13739, 13740, 13741, 13742, 13743, + 13744, 13745, 13746, 13747, 13748, 13749, 13750, 13751, + 13752, 13753, 13754, 13755, 13756, 13757, 13758, 13759, + 13760, 13761, 13762, 13763, 13764, 13765, 13766, 13767, + 13768, 13769, 13770, 13771, 13772, 13773, 13774, 13775, + 13776, 13777, 13778, 13779, 13780, 13781, 13782, 13783, + 13784, 13785, 13786, 13787, 13788, 13789, 13790, 13791, + 13792, 13793, 13794, 13795, 13796, 13797, 13798, 13799, + 13800, 13801, 13802, 13803, 13804, 13805, 13806, 13807, + 13808, 13809, 13810, 13811, 13812, 13813, 13814, 13815, + 13816, 13817, 13818, 13819, 13820, 13821, 13822, 13823, + 13824, 13825, 13826, 13827, 13828, 13829, 13830, 13831, + 13832, 13833, 13834, 13835, 13836, 13837, 13838, 13839, + 13840, 13841, 13842, 13843, 13844, 13845, 13846, 13847, + 13848, 13849, 13850, 13851, 13852, 13853, 13854, 13855, + 13856, 13857, 13858, 13859, 13860, 13861, 13862, 13863, + 13864, 13865, 13866, 13867, 13868, 13869, 13870, 13871, + 13872, 13873, 13874, 13875, 13876, 13877, 13878, 13879, + 13880, 13881, 13882, 13883, 13884, 13885, 13886, 13887, + 13888, 13889, 13890, 13891, 13892, 13893, 13894, 13895, + 13896, 13897, 13898, 13899, 13900, 13901, 13902, 13903, + 13904, 13905, 13906, 13907, 13908, 13909, 13910, 13911, + 13912, 13913, 13914, 13915, 13916, 13917, 13918, 13919, + 13920, 13921, 13922, 13923, 13924, 13925, 13926, 13927, + 13928, 13929, 13930, 13931, 13932, 13933, 13934, 13935, + 13936, 13937, 13938, 13939, 13940, 13941, 13942, 13943, + 13944, 13945, 13946, 13947, 13948, 13949, 13950, 13951, + 13952, 13953, 13954, 13955, 13956, 13957, 13958, 13959, + 13960, 13961, 13962, 13963, 13964, 13965, 13966, 13967, + 13968, 13969, 13970, 13971, 13972, 13973, 13974, 13975, + 13976, 13977, 13978, 13979, 13980, 13981, 13982, 13983, + 13984, 13985, 13986, 13987, 13988, 13989, 13990, 13991, + 13992, 13993, 13994, 13995, 13996, 13997, 13998, 13999, + 14000, 14001, 14002, 14003, 14004, 14005, 14006, 14007, + 14008, 14009, 14010, 14011, 14012, 14013, 14014, 14015, + 14016, 14017, 14018, 14019, 14020, 14021, 14022, 14023, + 14024, 14025, 14026, 14027, 14028, 14029, 14030, 14031, + 14032, 14033, 14034, 14035, 14036, 14037, 14038, 14039, + 14040, 14041, 14042, 14043, 14044, 14045, 14046, 14047, + 14048, 14049, 14050, 14051, 14052, 14053, 14054, 14055, + 14056, 14057, 14058, 14059, 14060, 14061, 14062, 14063, + 14064, 14065, 14066, 14067, 14068, 14069, 14070, 14071, + 14072, 14073, 14074, 14075, 14076, 14077, 14078, 14079, + 14080, 14081, 14082, 14083, 14084, 14085, 14086, 14087, + 14088, 14089, 14090, 14091, 14092, 14093, 14094, 14095, + 14096, 14097, 14098, 14099, 14100, 14101, 14102, 14103, + 14104, 14105, 14106, 14107, 14108, 14109, 14110, 14111, + 14112, 14113, 14114, 14115, 14116, 14117, 14118, 14119, + 14120, 14121, 14122, 14123, 14124, 14125, 14126, 14127, + 14128, 14129, 14130, 14131, 14132, 14133, 14134, 14135, + 14136, 14137, 14138, 14139, 14140, 14141, 14142, 14143, + 14144, 14145, 14146, 14147, 14148, 14149, 14150, 14151, + 14152, 14153, 14154, 14155, 14156, 14157, 14158, 14159, + 14160, 14161, 14162, 14163, 14164, 14165, 14166, 14167, + 14168, 14169, 14170, 14171, 14172, 14173, 14174, 14175, + 14176, 14177, 14178, 14179, 14180, 14181, 14182, 14183, + 14184, 14185, 14186, 14187, 14188, 14189, 14190, 14191, + 14192, 14193, 14194, 14195, 14196, 14197, 14198, 14199, + 14200, 14201, 14202, 14203, 14204, 14205, 14206, 14207, + 14208, 14209, 14210, 14211, 14212, 14213, 14214, 14215, + 14216, 14217, 14218, 14219, 14220, 14221, 14222, 14223, + 14224, 14225, 14226, 14227, 14228, 14229, 14230, 14231, + 14232, 14233, 14234, 14235, 14236, 14237, 14238, 14239, + 14240, 14241, 14242, 14243, 14244, 14245, 14246, 14247, + 14248, 14249, 14250, 14251, 14252, 14253, 14254, 14255, + 14256, 14257, 14258, 14259, 14260, 14261, 14262, 14263, + 14264, 14265, 14266, 14267, 14268, 14269, 14270, 14271, + 14272, 14273, 14274, 14275, 14276, 14277, 14278, 14279, + 14280, 14281, 14282, 14283, 14284, 14285, 14286, 14287, + 14288, 14289, 14290, 14291, 14292, 14293, 14294, 14295, + 14296, 14297, 14298, 14299, 14300, 14301, 14302, 14303, + 14304, 14305, 14306, 14307, 14308, 14309, 14310, 14311, + 14312, 14313, 14314, 14315, 14316, 14317, 14318, 14319, + 14320, 14321, 14322, 14323, 14324, 14325, 14326, 14327, + 14328, 14329, 14330, 14331, 14332, 14333, 14334, 14335, + 14336, 14337, 14338, 14339, 14340, 14341, 14342, 14343, + 14344, 14345, 14346, 14347, 14348, 14349, 14350, 14351, + 14352, 14353, 14354, 14355, 14356, 14357, 14358, 14359, + 14360, 14361, 14362, 14363, 14364, 14365, 14366, 14367, + 14368, 14369, 14370, 14371, 14372, 14373, 14374, 14375, + 14376, 14377, 14378, 14379, 14380, 14381, 14382, 14383, + 14384, 14385, 14386, 14387, 14388, 14389, 14390, 14391, + 14392, 14393, 14394, 14395, 14396, 14397, 14398, 14399, + 14400, 14401, 14402, 14403, 14404, 14405, 14406, 14407, + 14408, 14409, 14410, 14411, 14412, 14413, 14414, 14415, + 14416, 14417, 14418, 14419, 14420, 14421, 14422, 14423, + 14424, 14425, 14426, 14427, 14428, 14429, 14430, 14431, + 14432, 14433, 14434, 14435, 14436, 14437, 14438, 14439, + 14440, 14441, 14442, 14443, 14444, 14445, 14446, 14447, + 14448, 14449, 14450, 14451, 14452, 14453, 14454, 14455, + 14456, 14457, 14458, 14459, 14460, 14461, 14462, 14463, + 14464, 14465, 14466, 14467, 14468, 14469, 14470, 14471, + 14472, 14473, 14474, 14475, 14476, 14477, 14478, 14479, + 14480, 14481, 14482, 14483, 14484, 14485, 14486, 14487, + 14488, 14489, 14490, 14491, 14492, 14493, 14494, 14495, + 14496, 14497, 14498, 14499, 14500, 14501, 14502, 14503, + 14504, 14505, 14506, 14507, 14508, 14509, 14510, 14511, + 14512, 14513, 14514, 14515, 14516, 14517, 14518, 14519, + 14520, 14521, 14522, 14523, 14524, 14525, 14526, 14527, + 14528, 14529, 14530, 14531, 14532, 14533, 14534, 14535, + 14536, 14537, 14538, 14539, 14540, 14541, 14542, 14543, + 14544, 14545, 14546, 14547, 14548, 14549, 14550, 14551, + 14552, 14553, 14554, 14555, 14556, 14557, 14558, 14559, + 14560, 14561, 14562, 14563, 14564, 14565, 14566, 14567, + 14568, 14569, 14570, 14571, 14572, 14573, 14574, 14575, + 14576, 14577, 14578, 14579, 14580, 14581, 14582, 14583, + 14584, 14585, 14586, 14587, 14588, 14589, 14590, 14591, + 14592, 14593, 14594, 14595, 14596, 14597, 14598, 14599, + 14600, 14601, 14602, 14603, 14604, 14605, 14606, 14607, + 14608, 14609, 14610, 14611, 14612, 14613, 14614, 14615, + 14616, 14617, 14618, 14619, 14620, 14621, 14622, 14623, + 14624, 14625, 14626, 14627, 14628, 14629, 14630, 14631, + 14632, 14633, 14634, 14635, 14636, 14637, 14638, 14639, + 14640, 14641, 14642, 14643, 14644, 14645, 14646, 14647, + 14648, 14649, 14650, 14651, 14652, 14653, 14654, 14655, + 14656, 14657, 14658, 14659, 14660, 14661, 14662, 14663, + 14664, 14665, 14666, 14667, 14668, 14669, 14670, 14671, + 14672, 14673, 14674, 14675, 14676, 14677, 14678, 14679, + 14680, 14681, 14682, 14683, 14684, 14685, 14686, 14687, + 14688, 14689, 14690, 14691, 14692, 14693, 14694, 14695, + 14696, 14697, 14698, 14699, 14700, 14701, 14702, 14703, + 14704, 14705, 14706, 14707, 14708, 14709, 14710, 14711, + 14712, 14713, 14714, 14715, 14716, 14717, 14718, 14719, + 14720, 14721, 14722, 14723, 14724, 14725, 14726, 14727, + 14728, 14729, 14730, 14731, 14732, 14733, 14734, 14735, + 14736, 14737, 14738, 14739, 14740, 14741, 14742, 14743, + 14744, 14745, 14746, 14747, 14748, 14749, 14750, 14751, + 14752, 14753, 14754, 14755, 14756, 14757, 14758, 14759, + 14760, 14761, 14762, 14763, 14764, 14765, 14766, 14767, + 14768, 14769, 14770, 14771, 14772, 14773, 14774, 14775, + 14776, 14777, 14778, 14779, 14780, 14781, 14782, 14783, + 14784, 14785, 14786, 14787, 14788, 14789, 14790, 14791, + 14792, 14793, 14794, 14795, 14796, 14797, 14798, 14799, + 14800, 14801, 14802, 14803, 14804, 14805, 14806, 14807, + 14808, 14809, 14810, 14811, 14812, 14813, 14814, 14815, + 14816, 14817, 14818, 14819, 14820, 14821, 14822, 14823, + 14824, 14825, 14826, 14827, 14828, 14829, 14830, 14831, + 14832, 14833, 14834, 14835, 14836, 14837, 14838, 14839, + 14840, 14841, 14842, 14843, 14844, 14845, 14846, 14847, + 14848, 14849, 14850, 14851, 14852, 14853, 14854, 14855, + 14856, 14857, 14858, 14859, 14860, 14861, 14862, 14863, + 14864, 14865, 14866, 14867, 14868, 14869, 14870, 14871, + 14872, 14873, 14874, 14875, 14876, 14877, 14878, 14879, + 14880, 14881, 14882, 14883, 14884, 14885, 14886, 14887, + 14888, 14889, 14890, 14891, 14892, 14893, 14894, 14895, + 14896, 14897, 14898, 14899, 14900, 14901, 14902, 14903, + 14904, 14905, 14906, 14907, 14908, 14909, 14910, 14911, + 14912, 14913, 14914, 14915, 14916, 14917, 14918, 14919, + 14920, 14921, 14922, 14923, 14924, 14925, 14926, 14927, + 14928, 14929, 14930, 14931, 14932, 14933, 14934, 14935, + 14936, 14937, 14938, 14939, 14940, 14941, 14942, 14943, + 14944, 14945, 14946, 14947, 14948, 14949, 14950, 14951, + 14952, 14953, 14954, 14955, 14956, 14957, 14958, 14959, + 14960, 14961, 14962, 14963, 14964, 14965, 14966, 14967, + 14968, 14969, 14970, 14971, 14972, 14973, 14974, 14975, + 14976, 14977, 14978, 14979, 14980, 14981, 14982, 14983, + 14984, 14985, 14986, 14987, 14988, 14989, 14990, 14991, + 14992, 14993, 14994, 14995, 14996, 14997, 14998, 14999, + 15000, 15001, 15002, 15003, 15004, 15005, 15006, 15007, + 15008, 15009, 15010, 15011, 15012, 15013, 15014, 15015, + 15016, 15017, 15018, 15019, 15020, 15021, 15022, 15023, + 15024, 15025, 15026, 15027, 15028, 15029, 15030, 15031, + 15032, 15033, 15034, 15035, 15036, 15037, 15038, 15039, + 15040, 15041, 15042, 15043, 15044, 15045, 15046, 15047, + 15048, 15049, 15050, 15051, 15052, 15053, 15054, 15055, + 15056, 15057, 15058, 15059, 15060, 15061, 15062, 15063, + 15064, 15065, 15066, 15067, 15068, 15069, 15070, 15071, + 15072, 15073, 15074, 15075, 15076, 15077, 15078, 15079, + 15080, 15081, 15082, 15083, 15084, 15085, 15086, 15087, + 15088, 15089, 15090, 15091, 15092, 15093, 15094, 15095, + 15096, 15097, 15098, 15099, 15100, 15101, 15102, 15103, + 15104, 15105, 15106, 15107, 15108, 15109, 15110, 15111, + 15112, 15113, 15114, 15115, 15116, 15117, 15118, 15119, + 15120, 15121, 15122, 15123, 15124, 15125, 15126, 15127, + 15128, 15129, 15130, 15131, 15132, 15133, 15134, 15135, + 15136, 15137, 15138, 15139, 15140, 15141, 15142, 15143, + 15144, 15145, 15146, 15147, 15148, 15149, 15150, 15151, + 15152, 15153, 15154, 15155, 15156, 15157, 15158, 15159, + 15160, 15161, 15162, 15163, 15164, 15165, 15166, 15167, + 15168, 15169, 15170, 15171, 15172, 15173, 15174, 15175, + 15176, 15177, 15178, 15179, 15180, 15181, 15182, 15183, + 15184, 15185, 15186, 15187, 15188, 15189, 15190, 15191, + 15192, 15193, 15194, 15195, 15196, 15197, 15198, 15199, + 15200, 15201, 15202, 15203, 15204, 15205, 15206, 15207, + 15208, 15209, 15210, 15211, 15212, 15213, 15214, 15215, + 15216, 15217, 15218, 15219, 15220, 15221, 15222, 15223, + 15224, 15225, 15226, 15227, 15228, 15229, 15230, 15231, + 15232, 15233, 15234, 15235, 15236, 15237, 15238, 15239, + 15240, 15241, 15242, 15243, 15244, 15245, 15246, 15247, + 15248, 15249, 15250, 15251, 15252, 15253, 15254, 15255, + 15256, 15257, 15258, 15259, 15260, 15261, 15262, 15263, + 15264, 15265, 15266, 15267, 15268, 15269, 15270, 15271, + 15272, 15273, 15274, 15275, 15276, 15277, 15278, 15279, + 15280, 15281, 15282, 15283, 15284, 15285, 15286, 15287, + 15288, 15289, 15290, 15291, 15292, 15293, 15294, 15295, + 15296, 15297, 15298, 15299, 15300, 15301, 15302, 15303, + 15304, 15305, 15306, 15307, 15308, 15309, 15310, 15311, + 15312, 15313, 15314, 15315, 15316, 15317, 15318, 15319, + 15320, 15321, 15322, 15323, 15324, 15325, 15326, 15327, + 15328, 15329, 15330, 15331, 15332, 15333, 15334, 15335, + 15336, 15337, 15338, 15339, 15340, 15341, 15342, 15343, + 15344, 15345, 15346, 15347, 15348, 15349, 15350, 15351, + 15352, 15353, 15354, 15355, 15356, 15357, 15358, 15359, + 15360, 15361, 15362, 15363, 15364, 15365, 15366, 15367, + 15368, 15369, 15370, 15371, 15372, 15373, 15374, 15375, + 15376, 15377, 15378, 15379, 15380, 15381, 15382, 15383, + 15384, 15385, 15386, 15387, 15388, 15389, 15390, 15391, + 15392, 15393, 15394, 15395, 15396, 15397, 15398, 15399, + 15400, 15401, 15402, 15403, 15404, 15405, 15406, 15407, + 15408, 15409, 15410, 15411, 15412, 15413, 15414, 15415, + 15416, 15417, 15418, 15419, 15420, 15421, 15422, 15423, + 15424, 15425, 15426, 15427, 15428, 15429, 15430, 15431, + 15432, 15433, 15434, 15435, 15436, 15437, 15438, 15439, + 15440, 15441, 15442, 15443, 15444, 15445, 15446, 15447, + 15448, 15449, 15450, 15451, 15452, 15453, 15454, 15455, + 15456, 15457, 15458, 15459, 15460, 15461, 15462, 15463, + 15464, 15465, 15466, 15467, 15468, 15469, 15470, 15471, + 15472, 15473, 15474, 15475, 15476, 15477, 15478, 15479, + 15480, 15481, 15482, 15483, 15484, 15485, 15486, 15487, + 15488, 15489, 15490, 15491, 15492, 15493, 15494, 15495, + 15496, 15497, 15498, 15499, 15500, 15501, 15502, 15503, + 15504, 15505, 15506, 15507, 15508, 15509, 15510, 15511, + 15512, 15513, 15514, 15515, 15516, 15517, 15518, 15519, + 15520, 15521, 15522, 15523, 15524, 15525, 15526, 15527, + 15528, 15529, 15530, 15531, 15532, 15533, 15534, 15535, + 15536, 15537, 15538, 15539, 15540, 15541, 15542, 15543, + 15544, 15545, 15546, 15547, 15548, 15549, 15550, 15551, + 15552, 15553, 15554, 15555, 15556, 15557, 15558, 15559, + 15560, 15561, 15562, 15563, 15564, 15565, 15566, 15567, + 15568, 15569, 15570, 15571, 15572, 15573, 15574, 15575, + 15576, 15577, 15578, 15579, 15580, 15581, 15582, 15583, + 15584, 15585, 15586, 15587, 15588, 15589, 15590, 15591, + 15592, 15593, 15594, 15595, 15596, 15597, 15598, 15599, + 15600, 15601, 15602, 15603, 15604, 15605, 15606, 15607, + 15608, 15609, 15610, 15611, 15612, 15613, 15614, 15615, + 15616, 15617, 15618, 15619, 15620, 15621, 15622, 15623, + 15624, 15625, 15626, 15627, 15628, 15629, 15630, 15631, + 15632, 15633, 15634, 15635, 15636, 15637, 15638, 15639, + 15640, 15641, 15642, 15643, 15644, 15645, 15646, 15647, + 15648, 15649, 15650, 15651, 15652, 15653, 15654, 15655, + 15656, 15657, 15658, 15659, 15660, 15661, 15662, 15663, + 15664, 15665, 15666, 15667, 15668, 15669, 15670, 15671, + 15672, 15673, 15674, 15675, 15676, 15677, 15678, 15679, + 15680, 15681, 15682, 15683, 15684, 15685, 15686, 15687, + 15688, 15689, 15690, 15691, 15692, 15693, 15694, 15695, + 15696, 15697, 15698, 15699, 15700, 15701, 15702, 15703, + 15704, 15705, 15706, 15707, 15708, 15709, 15710, 15711, + 15712, 15713, 15714, 15715, 15716, 15717, 15718, 15719, + 15720, 15721, 15722, 15723, 15724, 15725, 15726, 15727, + 15728, 15729, 15730, 15731, 15732, 15733, 15734, 15735, + 15736, 15737, 15738, 15739, 15740, 15741, 15742, 15743, + 15744, 15745, 15746, 15747, 15748, 15749, 15750, 15751, + 15752, 15753, 15754, 15755, 15756, 15757, 15758, 15759, + 15760, 15761, 15762, 15763, 15764, 15765, 15766, 15767, + 15768, 15769, 15770, 15771, 15772, 15773, 15774, 15775, + 15776, 15777, 15778, 15779, 15780, 15781, 15782, 15783, + 15784, 15785, 15786, 15787, 15788, 15789, 15790, 15791, + 15792, 15793, 15794, 15795, 15796, 15797, 15798, 15799, + 15800, 15801, 15802, 15803, 15804, 15805, 15806, 15807, + 15808, 15809, 15810, 15811, 15812, 15813, 15814, 15815, + 15816, 15817, 15818, 15819, 15820, 15821, 15822, 15823, + 15824, 15825, 15826, 15827, 15828, 15829, 15830, 15831, + 15832, 15833, 15834, 15835, 15836, 15837, 15838, 15839, + 15840, 15841, 15842, 15843, 15844, 15845, 15846, 15847, + 15848, 15849, 15850, 15851, 15852, 15853, 15854, 15855, + 15856, 15857, 15858, 15859, 15860, 15861, 15862, 15863, + 15864, 15865, 15866, 15867, 15868, 15869, 15870, 15871, + 15872, 15873, 15874, 15875, 15876, 15877, 15878, 15879, + 15880, 15881, 15882, 15883, 15884, 15885, 15886, 15887, + 15888, 15889, 15890, 15891, 15892, 15893, 15894, 15895, + 15896, 15897, 15898, 15899, 15900, 15901, 15902, 15903, + 15904, 15905, 15906, 15907, 15908, 15909, 15910, 15911, + 15912, 15913, 15914, 15915, 15916, 15917, 15918, 15919, + 15920, 15921, 15922, 15923, 15924, 15925, 15926, 15927, + 15928, 15929, 15930, 15931, 15932, 15933, 15934, 15935, + 15936, 15937, 15938, 15939, 15940, 15941, 15942, 15943, + 15944, 15945, 15946, 15947, 15948, 15949, 15950, 15951, + 15952, 15953, 15954, 15955, 15956, 15957, 15958, 15959, + 15960, 15961, 15962, 15963, 15964, 15965, 15966, 15967, + 15968, 15969, 15970, 15971, 15972, 15973, 15974, 15975, + 15976, 15977, 15978, 15979, 15980, 15981, 15982, 15983, + 15984, 15985, 15986, 15987, 15988, 15989, 15990, 15991, + 15992, 15993, 15994, 15995, 15996, 15997, 15998, 15999, + 16000, 16001, 16002, 16003, 16004, 16005, 16006, 16007, + 16008, 16009, 16010, 16011, 16012, 16013, 16014, 16015, + 16016, 16017, 16018, 16019, 16020, 16021, 16022, 16023, + 16024, 16025, 16026, 16027, 16028, 16029, 16030, 16031, + 16032, 16033, 16034, 16035, 16036, 16037, 16038, 16039, + 16040, 16041, 16042, 16043, 16044, 16045, 16046, 16047, + 16048, 16049, 16050, 16051, 16052, 16053, 16054, 16055, + 16056, 16057, 16058, 16059, 16060, 16061, 16062, 16063, + 16064, 16065, 16066, 16067, 16068, 16069, 16070, 16071, + 16072, 16073, 16074, 16075, 16076, 16077, 16078, 16079, + 16080, 16081, 16082, 16083, 16084, 16085, 16086, 16087, + 16088, 16089, 16090, 16091, 16092, 16093, 16094, 16095, + 16096, 16097, 16098, 16099, 16100, 16101, 16102, 16103, + 16104, 16105, 16106, 16107, 16108, 16109, 16110, 16111, + 16112, 16113, 16114, 16115, 16116, 16117, 16118, 16119, + 16120, 16121, 16122, 16123, 16124, 16125, 16126, 16127, + 16128, 16129, 16130, 16131, 16132, 16133, 16134, 16135, + 16136, 16137, 16138, 16139, 16140, 16141, 16142, 16143, + 16144, 16145, 16146, 16147, 16148, 16149, 16150, 16151, + 16152, 16153, 16154, 16155, 16156, 16157, 16158, 16159, + 16160, 16161, 16162, 16163, 16164, 16165, 16166, 16167, + 16168, 16169, 16170, 16171, 16172, 16173, 16174, 16175, + 16176, 16177, 16178, 16179, 16180, 16181, 16182, 16183, + 16184, 16185, 16186, 16187, 16188, 16189, 16190, 16191, + 16192, 16193, 16194, 16195, 16196, 16197, 16198, 16199, + 16200, 16201, 16202, 16203, 16204, 16205, 16206, 16207, + 16208, 16209, 16210, 16211, 16212, 16213, 16214, 16215, + 16216, 16217, 16218, 16219, 16220, 16221, 16222, 16223, + 16224, 16225, 16226, 16227, 16228, 16229, 16230, 16231, + 16232, 16233, 16234, 16235, 16236, 16237, 16238, 16239, + 16240, 16241, 16242, 16243, 16244, 16245, 16246, 16247, + 16248, 16249, 16250, 16251, 16252, 16253, 16254, 16255, + 16256, 16257, 16258, 16259, 16260, 16261, 16262, 16263, + 16264, 16265, 16266, 16267, 16268, 16269, 16270, 16271, + 16272, 16273, 16274, 16275, 16276, 16277, 16278, 16279, + 16280, 16281, 16282, 16283, 16284, 16285, 16286, 16287, + 16288, 16289, 16290, 16291, 16292, 16293, 16294, 16295, + 16296, 16297, 16298, 16299, 16300, 16301, 16302, 16303, + 16304, 16305, 16306, 16307, 16308, 16309, 16310, 16311, + 16312, 16313, 16314, 16315, 16316, 16317, 16318, 16319, + 16320, 16321, 16322, 16323, 16324, 16325, 16326, 16327, + 16328, 16329, 16330, 16331, 16332, 16333, 16334, 16335, + 16336, 16337, 16338, 16339, 16340, 16341, 16342, 16343, + 16344, 16345, 16346, 16347, 16348, 16349, 16350, 16351, + 16352, 16353, 16354, 16355, 16356, 16357, 16358, 16359, + 16360, 16361, 16362, 16363, 16364, 16365, 16366, 16367, + 16368, 16369, 16370, 16371, 16372, 16373, 16374, 16375, + 16376, 16377, 16378, 16379, 16380, 16381, 16382, 16383, + 16384, 16385, 16386, 16387, 16388, 16389, 16390, 16391, + 16392, 16393, 16394, 16395, 16396, 16397, 16398, 16399, + 16400, 16401, 16402, 16403, 16404, 16405, 16406, 16407, + 16408, 16409, 16410, 16411, 16412, 16413, 16414, 16415, + 16416, 16417, 16418, 16419, 16420, 16421, 16422, 16423, + 16424, 16425, 16426, 16427, 16428, 16429, 16430, 16431, + 16432, 16433, 16434, 16435, 16436, 16437, 16438, 16439, + 16440, 16441, 16442, 16443, 16444, 16445, 16446, 16447, + 16448, 16449, 16450, 16451, 16452, 16453, 16454, 16455, + 16456, 16457, 16458, 16459, 16460, 16461, 16462, 16463, + 16464, 16465, 16466, 16467, 16468, 16469, 16470, 16471, + 16472, 16473, 16474, 16475, 16476, 16477, 16478, 16479, + 16480, 16481, 16482, 16483, 16484, 16485, 16486, 16487, + 16488, 16489, 16490, 16491, 16492, 16493, 16494, 16495, + 16496, 16497, 16498, 16499, 16500, 16501, 16502, 16503, + 16504, 16505, 16506, 16507, 16508, 16509, 16510, 16511, + 16512, 16513, 16514, 16515, 16516, 16517, 16518, 16519, + 16520, 16521, 16522, 16523, 16524, 16525, 16526, 16527, + 16528, 16529, 16530, 16531, 16532, 16533, 16534, 16535, + 16536, 16537, 16538, 16539, 16540, 16541, 16542, 16543, + 16544, 16545, 16546, 16547, 16548, 16549, 16550, 16551, + 16552, 16553, 16554, 16555, 16556, 16557, 16558, 16559, + 16560, 16561, 16562, 16563, 16564, 16565, 16566, 16567, + 16568, 16569, 16570, 16571, 16572, 16573, 16574, 16575, + 16576, 16577, 16578, 16579, 16580, 16581, 16582, 16583, + 16584, 16585, 16586, 16587, 16588, 16589, 16590, 16591, + 16592, 16593, 16594, 16595, 16596, 16597, 16598, 16599, + 16600, 16601, 16602, 16603, 16604, 16605, 16606, 16607, + 16608, 16609, 16610, 16611, 16612, 16613, 16614, 16615, + 16616, 16617, 16618, 16619, 16620, 16621, 16622, 16623, + 16624, 16625, 16626, 16627, 16628, 16629, 16630, 16631, + 16632, 16633, 16634, 16635, 16636, 16637, 16638, 16639, + 16640, 16641, 16642, 16643, 16644, 16645, 16646, 16647, + 16648, 16649, 16650, 16651, 16652, 16653, 16654, 16655, + 16656, 16657, 16658, 16659, 16660, 16661, 16662, 16663, + 16664, 16665, 16666, 16667, 16668, 16669, 16670, 16671, + 16672, 16673, 16674, 16675, 16676, 16677, 16678, 16679, + 16680, 16681, 16682, 16683, 16684, 16685, 16686, 16687, + 16688, 16689, 16690, 16691, 16692, 16693, 16694, 16695, + 16696, 16697, 16698, 16699, 16700, 16701, 16702, 16703, + 16704, 16705, 16706, 16707, 16708, 16709, 16710, 16711, + 16712, 16713, 16714, 16715, 16716, 16717, 16718, 16719, + 16720, 16721, 16722, 16723, 16724, 16725, 16726, 16727, + 16728, 16729, 16730, 16731, 16732, 16733, 16734, 16735, + 16736, 16737, 16738, 16739, 16740, 16741, 16742, 16743, + 16744, 16745, 16746, 16747, 16748, 16749, 16750, 16751, + 16752, 16753, 16754, 16755, 16756, 16757, 16758, 16759, + 16760, 16761, 16762, 16763, 16764, 16765, 16766, 16767, + 16768, 16769, 16770, 16771, 16772, 16773, 16774, 16775, + 16776, 16777, 16778, 16779, 16780, 16781, 16782, 16783, + 16784, 16785, 16786, 16787, 16788, 16789, 16790, 16791, + 16792, 16793, 16794, 16795, 16796, 16797, 16798, 16799, + 16800, 16801, 16802, 16803, 16804, 16805, 16806, 16807, + 16808, 16809, 16810, 16811, 16812, 16813, 16814, 16815, + 16816, 16817, 16818, 16819, 16820, 16821, 16822, 16823, + 16824, 16825, 16826, 16827, 16828, 16829, 16830, 16831, + 16832, 16833, 16834, 16835, 16836, 16837, 16838, 16839, + 16840, 16841, 16842, 16843, 16844, 16845, 16846, 16847, + 16848, 16849, 16850, 16851, 16852, 16853, 16854, 16855, + 16856, 16857, 16858, 16859, 16860, 16861, 16862, 16863, + 16864, 16865, 16866, 16867, 16868, 16869, 16870, 16871, + 16872, 16873, 16874, 16875, 16876, 16877, 16878, 16879, + 16880, 16881, 16882, 16883, 16884, 16885, 16886, 16887, + 16888, 16889, 16890, 16891, 16892, 16893, 16894, 16895, + 16896, 16897, 16898, 16899, 16900, 16901, 16902, 16903, + 16904, 16905, 16906, 16907, 16908, 16909, 16910, 16911, + 16912, 16913, 16914, 16915, 16916, 16917, 16918, 16919, + 16920, 16921, 16922, 16923, 16924, 16925, 16926, 16927, + 16928, 16929, 16930, 16931, 16932, 16933, 16934, 16935, + 16936, 16937, 16938, 16939, 16940, 16941, 16942, 16943, + 16944, 16945, 16946, 16947, 16948, 16949, 16950, 16951, + 16952, 16953, 16954, 16955, 16956, 16957, 16958, 16959, + 16960, 16961, 16962, 16963, 16964, 16965, 16966, 16967, + 16968, 16969, 16970, 16971, 16972, 16973, 16974, 16975, + 16976, 16977, 16978, 16979, 16980, 16981, 16982, 16983, + 16984, 16985, 16986, 16987, 16988, 16989, 16990, 16991, + 16992, 16993, 16994, 16995, 16996, 16997, 16998, 16999, + 17000, 17001, 17002, 17003, 17004, 17005, 17006, 17007, + 17008, 17009, 17010, 17011, 17012, 17013, 17014, 17015, + 17016, 17017, 17018, 17019, 17020, 17021, 17022, 17023, + 17024, 17025, 17026, 17027, 17028, 17029, 17030, 17031, + 17032, 17033, 17034, 17035, 17036, 17037, 17038, 17039, + 17040, 17041, 17042, 17043, 17044, 17045, 17046, 17047, + 17048, 17049, 17050, 17051, 17052, 17053, 17054, 17055, + 17056, 17057, 17058, 17059, 17060, 17061, 17062, 17063, + 17064, 17065, 17066, 17067, 17068, 17069, 17070, 17071, + 17072, 17073, 17074, 17075, 17076, 17077, 17078, 17079, + 17080, 17081, 17082, 17083, 17084, 17085, 17086, 17087, + 17088, 17089, 17090, 17091, 17092, 17093, 17094, 17095, + 17096, 17097, 17098, 17099, 17100, 17101, 17102, 17103, + 17104, 17105, 17106, 17107, 17108, 17109, 17110, 17111, + 17112, 17113, 17114, 17115, 17116, 17117, 17118, 17119, + 17120, 17121, 17122, 17123, 17124, 17125, 17126, 17127, + 17128, 17129, 17130, 17131, 17132, 17133, 17134, 17135, + 17136, 17137, 17138, 17139, 17140, 17141, 17142, 17143, + 17144, 17145, 17146, 17147, 17148, 17149, 17150, 17151, + 17152, 17153, 17154, 17155, 17156, 17157, 17158, 17159, + 17160, 17161, 17162, 17163, 17164, 17165, 17166, 17167, + 17168, 17169, 17170, 17171, 17172, 17173, 17174, 17175, + 17176, 17177, 17178, 17179, 17180, 17181, 17182, 17183, + 17184, 17185, 17186, 17187, 17188, 17189, 17190, 17191, + 17192, 17193, 17194, 17195, 17196, 17197, 17198, 17199, + 17200, 17201, 17202, 17203, 17204, 17205, 17206, 17207, + 17208, 17209, 17210, 17211, 17212, 17213, 17214, 17215, + 17216, 17217, 17218, 17219, 17220, 17221, 17222, 17223, + 17224, 17225, 17226, 17227, 17228, 17229, 17230, 17231, + 17232, 17233, 17234, 17235, 17236, 17237, 17238, 17239, + 17240, 17241, 17242, 17243, 17244, 17245, 17246, 17247, + 17248, 17249, 17250, 17251, 17252, 17253, 17254, 17255, + 17256, 17257, 17258, 17259, 17260, 17261, 17262, 17263, + 17264, 17265, 17266, 17267, 17268, 17269, 17270, 17271, + 17272, 17273, 17274, 17275, 17276, 17277, 17278, 17279, + 17280, 17281, 17282, 17283, 17284, 17285, 17286, 17287, + 17288, 17289, 17290, 17291, 17292, 17293, 17294, 17295, + 17296, 17297, 17298, 17299, 17300, 17301, 17302, 17303, + 17304, 17305, 17306, 17307, 17308, 17309, 17310, 17311, + 17312, 17313, 17314, 17315, 17316, 17317, 17318, 17319, + 17320, 17321, 17322, 17323, 17324, 17325, 17326, 17327, + 17328, 17329, 17330, 17331, 17332, 17333, 17334, 17335, + 17336, 17337, 17338, 17339, 17340, 17341, 17342, 17343, + 17344, 17345, 17346, 17347, 17348, 17349, 17350, 17351, + 17352, 17353, 17354, 17355, 17356, 17357, 17358, 17359, + 17360, 17361, 17362, 17363, 17364, 17365, 17366, 17367, + 17368, 17369, 17370, 17371, 17372, 17373, 17374, 17375, + 17376, 17377, 17378, 17379, 17380, 17381, 17382, 17383, + 17384, 17385, 17386, 17387, 17388, 17389, 17390, 17391, + 17392, 17393, 17394, 17395, 17396, 17397, 17398, 17399, + 17400, 17401, 17402, 17403, 17404, 17405, 17406, 17407, + 17408, 17409, 17410, 17411, 17412, 17413, 17414, 17415, + 17416, 17417, 17418, 17419, 17420, 17421, 17422, 17423, + 17424, 17425, 17426, 17427, 17428, 17429, 17430, 17431, + 17432, 17433, 17434, 17435, 17436, 17437, 17438, 17439, + 17440, 17441, 17442, 17443, 17444, 17445, 17446, 17447, + 17448, 17449, 17450, 17451, 17452, 17453, 17454, 17455, + 17456, 17457, 17458, 17459, 17460, 17461, 17462, 17463, + 17464, 17465, 17466, 17467, 17468, 17469, 17470, 17471, + 17472, 17473, 17474, 17475, 17476, 17477, 17478, 17479, + 17480, 17481, 17482, 17483, 17484, 17485, 17486, 17487, + 17488, 17489, 17490, 17491, 17492, 17493, 17494, 17495, + 17496, 17497, 17498, 17499, 17500, 17501, 17502, 17503, + 17504, 17505, 17506, 17507, 17508, 17509, 17510, 17511, + 17512, 17513, 17514, 17515, 17516, 17517, 17518, 17519, + 17520, 17521, 17522, 17523, 17524, 17525, 17526, 17527, + 17528, 17529, 17530, 17531, 17532, 17533, 17534, 17535, + 17536, 17537, 17538, 17539, 17540, 17541, 17542, 17543, + 17544, 17545, 17546, 17547, 17548, 17549, 17550, 17551, + 17552, 17553, 17554, 17555, 17556, 17557, 17558, 17559, + 17560, 17561, 17562, 17563, 17564, 17565, 17566, 17567, + 17568, 17569, 17570, 17571, 17572, 17573, 17574, 17575, + 17576, 17577, 17578, 17579, 17580, 17581, 17582, 17583, + 17584, 17585, 17586, 17587, 17588, 17589, 17590, 17591, + 17592, 17593, 17594, 17595, 17596, 17597, 17598, 17599, + 17600, 17601, 17602, 17603, 17604, 17605, 17606, 17607, + 17608, 17609, 17610, 17611, 17612, 17613, 17614, 17615, + 17616, 17617, 17618, 17619, 17620, 17621, 17622, 17623, + 17624, 17625, 17626, 17627, 17628, 17629, 17630, 17631, + 17632, 17633, 17634, 17635, 17636, 17637, 17638, 17639, + 17640, 17641, 17642, 17643, 17644, 17645, 17646, 17647, + 17648, 17649, 17650, 17651, 17652, 17653, 17654, 17655, + 17656, 17657, 17658, 17659, 17660, 17661, 17662, 17663, + 17664, 17665, 17666, 17667, 17668, 17669, 17670, 17671, + 17672, 17673, 17674, 17675, 17676, 17677, 17678, 17679, + 17680, 17681, 17682, 17683, 17684, 17685, 17686, 17687, + 17688, 17689, 17690, 17691, 17692, 17693, 17694, 17695, + 17696, 17697, 17698, 17699, 17700, 17701, 17702, 17703, + 17704, 17705, 17706, 17707, 17708, 17709, 17710, 17711, + 17712, 17713, 17714, 17715, 17716, 17717, 17718, 17719, + 17720, 17721, 17722, 17723, 17724, 17725, 17726, 17727, + 17728, 17729, 17730, 17731, 17732, 17733, 17734, 17735, + 17736, 17737, 17738, 17739, 17740, 17741, 17742, 17743, + 17744, 17745, 17746, 17747, 17748, 17749, 17750, 17751, + 17752, 17753, 17754, 17755, 17756, 17757, 17758, 17759, + 17760, 17761, 17762, 17763, 17764, 17765, 17766, 17767, + 17768, 17769, 17770, 17771, 17772, 17773, 17774, 17775, + 17776, 17777, 17778, 17779, 17780, 17781, 17782, 17783, + 17784, 17785, 17786, 17787, 17788, 17789, 17790, 17791, + 17792, 17793, 17794, 17795, 17796, 17797, 17798, 17799, + 17800, 17801, 17802, 17803, 17804, 17805, 17806, 17807, + 17808, 17809, 17810, 17811, 17812, 17813, 17814, 17815, + 17816, 17817, 17818, 17819, 17820, 17821, 17822, 17823, + 17824, 17825, 17826, 17827, 17828, 17829, 17830, 17831, + 17832, 17833, 17834, 17835, 17836, 17837, 17838, 17839, + 17840, 17841, 17842, 17843, 17844, 17845, 17846, 17847, + 17848, 17849, 17850, 17851, 17852, 17853, 17854, 17855, + 17856, 17857, 17858, 17859, 17860, 17861, 17862, 17863, + 17864, 17865, 17866, 17867, 17868, 17869, 17870, 17871, + 17872, 17873, 17874, 17875, 17876, 17877, 17878, 17879, + 17880, 17881, 17882, 17883, 17884, 17885, 17886, 17887, + 17888, 17889, 17890, 17891, 17892, 17893, 17894, 17895, + 17896, 17897, 17898, 17899, 17900, 17901, 17902, 17903, + 17904, 17905, 17906, 17907, 17908, 17909, 17910, 17911, + 17912, 17913, 17914, 17915, 17916, 17917, 17918, 17919, + 17920, 17921, 17922, 17923, 17924, 17925, 17926, 17927, + 17928, 17929, 17930, 17931, 17932, 17933, 17934, 17935, + 17936, 17937, 17938, 17939, 17940, 17941, 17942, 17943, + 17944, 17945, 17946, 17947, 17948, 17949, 17950, 17951, + 17952, 17953, 17954, 17955, 17956, 17957, 17958, 17959, + 17960, 17961, 17962, 17963, 17964, 17965, 17966, 17967, + 17968, 17969, 17970, 17971, 17972, 17973, 17974, 17975, + 17976, 17977, 17978, 17979, 17980, 17981, 17982, 17983, + 17984, 17985, 17986, 17987, 17988, 17989, 17990, 17991, + 17992, 17993, 17994, 17995, 17996, 17997, 17998, 17999, + 18000, 18001, 18002, 18003, 18004, 18005, 18006, 18007, + 18008, 18009, 18010, 18011, 18012, 18013, 18014, 18015, + 18016, 18017, 18018, 18019, 18020, 18021, 18022, 18023, + 18024, 18025, 18026, 18027, 18028, 18029, 18030, 18031, + 18032, 18033, 18034, 18035, 18036, 18037, 18038, 18039, + 18040, 18041, 18042, 18043, 18044, 18045, 18046, 18047, + 18048, 18049, 18050, 18051, 18052, 18053, 18054, 18055, + 18056, 18057, 18058, 18059, 18060, 18061, 18062, 18063, + 18064, 18065, 18066, 18067, 18068, 18069, 18070, 18071, + 18072, 18073, 18074, 18075, 18076, 18077, 18078, 18079, + 18080, 18081, 18082, 18083, 18084, 18085, 18086, 18087, + 18088, 18089, 18090, 18091, 18092, 18093, 18094, 18095, + 18096, 18097, 18098, 18099, 18100, 18101, 18102, 18103, + 18104, 18105, 18106, 18107, 18108, 18109, 18110, 18111, + 18112, 18113, 18114, 18115, 18116, 18117, 18118, 18119, + 18120, 18121, 18122, 18123, 18124, 18125, 18126, 18127, + 18128, 18129, 18130, 18131, 18132, 18133, 18134, 18135, + 18136, 18137, 18138, 18139, 18140, 18141, 18142, 18143, + 18144, 18145, 18146, 18147, 18148, 18149, 18150, 18151, + 18152, 18153, 18154, 18155, 18156, 18157, 18158, 18159, + 18160, 18161, 18162, 18163, 18164, 18165, 18166, 18167, + 18168, 18169, 18170, 18171, 18172, 18173, 18174, 18175, + 18176, 18177, 18178, 18179, 18180, 18181, 18182, 18183, + 18184, 18185, 18186, 18187, 18188, 18189, 18190, 18191, + 18192, 18193, 18194, 18195, 18196, 18197, 18198, 18199, + 18200, 18201, 18202, 18203, 18204, 18205, 18206, 18207, + 18208, 18209, 18210, 18211, 18212, 18213, 18214, 18215, + 18216, 18217, 18218, 18219, 18220, 18221, 18222, 18223, + 18224, 18225, 18226, 18227, 18228, 18229, 18230, 18231, + 18232, 18233, 18234, 18235, 18236, 18237, 18238, 18239, + 18240, 18241, 18242, 18243, 18244, 18245, 18246, 18247, + 18248, 18249, 18250, 18251, 18252, 18253, 18254, 18255, + 18256, 18257, 18258, 18259, 18260, 18261, 18262, 18263, + 18264, 18265, 18266, 18267, 18268, 18269, 18270, 18271, + 18272, 18273, 18274, 18275, 18276, 18277, 18278, 18279, + 18280, 18281, 18282, 18283, 18284, 18285, 18286, 18287, + 18288, 18289, 18290, 18291, 18292, 18293, 18294, 18295, + 18296, 18297, 18298, 18299, 18300, 18301, 18302, 18303, + 18304, 18305, 18306, 18307, 18308, 18309, 18310, 18311, + 18312, 18313, 18314, 18315, 18316, 18317, 18318, 18319, + 18320, 18321, 18322, 18323, 18324, 18325, 18326, 18327, + 18328, 18329, 18330, 18331, 18332, 18333, 18334, 18335, + 18336, 18337, 18338, 18339, 18340, 18341, 18342, 18343, + 18344, 18345, 18346, 18347, 18348, 18349, 18350, 18351, + 18352, 18353, 18354, 18355, 18356, 18357, 18358, 18359, + 18360, 18361, 18362, 18363, 18364, 18365, 18366, 18367, + 18368, 18369, 18370, 18371, 18372, 18373, 18374, 18375, + 18376, 18377, 18378, 18379, 18380, 18381, 18382, 18383, + 18384, 18385, 18386, 18387, 18388, 18389, 18390, 18391, + 18392, 18393, 18394, 18395, 18396, 18397, 18398, 18399, + 18400, 18401, 18402, 18403, 18404, 18405, 18406, 18407, + 18408, 18409, 18410, 18411, 18412, 18413, 18414, 18415, + 18416, 18417, 18418, 18419, 18420, 18421, 18422, 18423, + 18424, 18425, 18426, 18427, 18428, 18429, 18430, 18431, + 18432, 18433, 18434, 18435, 18436, 18437, 18438, 18439, + 18440, 18441, 18442, 18443, 18444, 18445, 18446, 18447, + 18448, 18449, 18450, 18451, 18452, 18453, 18454, 18455, + 18456, 18457, 18458, 18459, 18460, 18461, 18462, 18463, + 18464, 18465, 18466, 18467, 18468, 18469, 18470, 18471, + 18472, 18473, 18474, 18475, 18476, 18477, 18478, 18479, + 18480, 18481, 18482, 18483, 18484, 18485, 18486, 18487, + 18488, 18489, 18490, 18491, 18492, 18493, 18494, 18495, + 18496, 18497, 18498, 18499, 18500, 18501, 18502, 18503, + 18504, 18505, 18506, 18507, 18508, 18509, 18510, 18511, + 18512, 18513, 18514, 18515, 18516, 18517, 18518, 18519, + 18520, 18521, 18522, 18523, 18524, 18525, 18526, 18527, + 18528, 18529, 18530, 18531, 18532, 18533, 18534, 18535, + 18536, 18537, 18538, 18539, 18540, 18541, 18542, 18543, + 18544, 18545, 18546, 18547, 18548, 18549, 18550, 18551, + 18552, 18553, 18554, 18555, 18556, 18557, 18558, 18559, + 18560, 18561, 18562, 18563, 18564, 18565, 18566, 18567, + 18568, 18569, 18570, 18571, 18572, 18573, 18574, 18575, + 18576, 18577, 18578, 18579, 18580, 18581, 18582, 18583, + 18584, 18585, 18586, 18587, 18588, 18589, 18590, 18591, + 18592, 18593, 18594, 18595, 18596, 18597, 18598, 18599, + 18600, 18601, 18602, 18603, 18604, 18605, 18606, 18607, + 18608, 18609, 18610, 18611, 18612, 18613, 18614, 18615, + 18616, 18617, 18618, 18619, 18620, 18621, 18622, 18623, + 18624, 18625, 18626, 18627, 18628, 18629, 18630, 18631, + 18632, 18633, 18634, 18635, 18636, 18637, 18638, 18639, + 18640, 18641, 18642, 18643, 18644, 18645, 18646, 18647, + 18648, 18649, 18650, 18651, 18652, 18653, 18654, 18655, + 18656, 18657, 18658, 18659, 18660, 18661, 18662, 18663, + 18664, 18665, 18666, 18667, 18668, 18669, 18670, 18671, + 18672, 18673, 18674, 18675, 18676, 18677, 18678, 18679, + 18680, 18681, 18682, 18683, 18684, 18685, 18686, 18687, + 18688, 18689, 18690, 18691, 18692, 18693, 18694, 18695, + 18696, 18697, 18698, 18699, 18700, 18701, 18702, 18703, + 18704, 18705, 18706, 18707, 18708, 18709, 18710, 18711, + 18712, 18713, 18714, 18715, 18716, 18717, 18718, 18719, + 18720, 18721, 18722, 18723, 18724, 18725, 18726, 18727, + 18728, 18729, 18730, 18731, 18732, 18733, 18734, 18735, + 18736, 18737, 18738, 18739, 18740, 18741, 18742, 18743, + 18744, 18745, 18746, 18747, 18748, 18749, 18750, 18751, + 18752, 18753, 18754, 18755, 18756, 18757, 18758, 18759, + 18760, 18761, 18762, 18763, 18764, 18765, 18766, 18767, + 18768, 18769, 18770, 18771, 18772, 18773, 18774, 18775, + 18776, 18777, 18778, 18779, 18780, 18781, 18782, 18783, + 18784, 18785, 18786, 18787, 18788, 18789, 18790, 18791, + 18792, 18793, 18794, 18795, 18796, 18797, 18798, 18799, + 18800, 18801, 18802, 18803, 18804, 18805, 18806, 18807, + 18808, 18809, 18810, 18811, 18812, 18813, 18814, 18815, + 18816, 18817, 18818, 18819, 18820, 18821, 18822, 18823, + 18824, 18825, 18826, 18827, 18828, 18829, 18830, 18831, + 18832, 18833, 18834, 18835, 18836, 18837, 18838, 18839, + 18840, 18841, 18842, 18843, 18844, 18845, 18846, 18847, + 18848, 18849, 18850, 18851, 18852, 18853, 18854, 18855, + 18856, 18857, 18858, 18859, 18860, 18861, 18862, 18863, + 18864, 18865, 18866, 18867, 18868, 18869, 18870, 18871, + 18872, 18873, 18874, 18875, 18876, 18877, 18878, 18879, + 18880, 18881, 18882, 18883, 18884, 18885, 18886, 18887, + 18888, 18889, 18890, 18891, 18892, 18893, 18894, 18895, + 18896, 18897, 18898, 18899, 18900, 18901, 18902, 18903, + 18904, 18905, 18906, 18907, 18908, 18909, 18910, 18911, + 18912, 18913, 18914, 18915, 18916, 18917, 18918, 18919, + 18920, 18921, 18922, 18923, 18924, 18925, 18926, 18927, + 18928, 18929, 18930, 18931, 18932, 18933, 18934, 18935, + 18936, 18937, 18938, 18939, 18940, 18941, 18942, 18943, + 18944, 18945, 18946, 18947, 18948, 18949, 18950, 18951, + 18952, 18953, 18954, 18955, 18956, 18957, 18958, 18959, + 18960, 18961, 18962, 18963, 18964, 18965, 18966, 18967, + 18968, 18969, 18970, 18971, 18972, 18973, 18974, 18975, + 18976, 18977, 18978, 18979, 18980, 18981, 18982, 18983, + 18984, 18985, 18986, 18987, 18988, 18989, 18990, 18991, + 18992, 18993, 18994, 18995, 18996, 18997, 18998, 18999, + 19000, 19001, 19002, 19003, 19004, 19005, 19006, 19007, + 19008, 19009, 19010, 19011, 19012, 19013, 19014, 19015, + 19016, 19017, 19018, 19019, 19020, 19021, 19022, 19023, + 19024, 19025, 19026, 19027, 19028, 19029, 19030, 19031, + 19032, 19033, 19034, 19035, 19036, 19037, 19038, 19039, + 19040, 19041, 19042, 19043, 19044, 19045, 19046, 19047, + 19048, 19049, 19050, 19051, 19052, 19053, 19054, 19055, + 19056, 19057, 19058, 19059, 19060, 19061, 19062, 19063, + 19064, 19065, 19066, 19067, 19068, 19069, 19070, 19071, + 19072, 19073, 19074, 19075, 19076, 19077, 19078, 19079, + 19080, 19081, 19082, 19083, 19084, 19085, 19086, 19087, + 19088, 19089, 19090, 19091, 19092, 19093, 19094, 19095, + 19096, 19097, 19098, 19099, 19100, 19101, 19102, 19103, + 19104, 19105, 19106, 19107, 19108, 19109, 19110, 19111, + 19112, 19113, 19114, 19115, 19116, 19117, 19118, 19119, + 19120, 19121, 19122, 19123, 19124, 19125, 19126, 19127, + 19128, 19129, 19130, 19131, 19132, 19133, 19134, 19135, + 19136, 19137, 19138, 19139, 19140, 19141, 19142, 19143, + 19144, 19145, 19146, 19147, 19148, 19149, 19150, 19151, + 19152, 19153, 19154, 19155, 19156, 19157, 19158, 19159, + 19160, 19161, 19162, 19163, 19164, 19165, 19166, 19167, + 19168, 19169, 19170, 19171, 19172, 19173, 19174, 19175, + 19176, 19177, 19178, 19179, 19180, 19181, 19182, 19183, + 19184, 19185, 19186, 19187, 19188, 19189, 19190, 19191, + 19192, 19193, 19194, 19195, 19196, 19197, 19198, 19199, + 19200, 19201, 19202, 19203, 19204, 19205, 19206, 19207, + 19208, 19209, 19210, 19211, 19212, 19213, 19214, 19215, + 19216, 19217, 19218, 19219, 19220, 19221, 19222, 19223, + 19224, 19225, 19226, 19227, 19228, 19229, 19230, 19231, + 19232, 19233, 19234, 19235, 19236, 19237, 19238, 19239, + 19240, 19241, 19242, 19243, 19244, 19245, 19246, 19247, + 19248, 19249, 19250, 19251, 19252, 19253, 19254, 19255, + 19256, 19257, 19258, 19259, 19260, 19261, 19262, 19263, + 19264, 19265, 19266, 19267, 19268, 19269, 19270, 19271, + 19272, 19273, 19274, 19275, 19276, 19277, 19278, 19279, + 19280, 19281, 19282, 19283, 19284, 19285, 19286, 19287, + 19288, 19289, 19290, 19291, 19292, 19293, 19294, 19295, + 19296, 19297, 19298, 19299, 19300, 19301, 19302, 19303, + 19304, 19305, 19306, 19307, 19308, 19309, 19310, 19311, + 19312, 19313, 19314, 19315, 19316, 19317, 19318, 19319, + 19320, 19321, 19322, 19323, 19324, 19325, 19326, 19327, + 19328, 19329, 19330, 19331, 19332, 19333, 19334, 19335, + 19336, 19337, 19338, 19339, 19340, 19341, 19342, 19343, + 19344, 19345, 19346, 19347, 19348, 19349, 19350, 19351, + 19352, 19353, 19354, 19355, 19356, 19357, 19358, 19359, + 19360, 19361, 19362, 19363, 19364, 19365, 19366, 19367, + 19368, 19369, 19370, 19371, 19372, 19373, 19374, 19375, + 19376, 19377, 19378, 19379, 19380, 19381, 19382, 19383, + 19384, 19385, 19386, 19387, 19388, 19389, 19390, 19391, + 19392, 19393, 19394, 19395, 19396, 19397, 19398, 19399, + 19400, 19401, 19402, 19403, 19404, 19405, 19406, 19407, + 19408, 19409, 19410, 19411, 19412, 19413, 19414, 19415, + 19416, 19417, 19418, 19419, 19420, 19421, 19422, 19423, + 19424, 19425, 19426, 19427, 19428, 19429, 19430, 19431, + 19432, 19433, 19434, 19435, 19436, 19437, 19438, 19439, + 19440, 19441, 19442, 19443, 19444, 19445, 19446, 19447, + 19448, 19449, 19450, 19451, 19452, 19453, 19454, 19455, + 19456, 19457, 19458, 19459, 19460, 19461, 19462, 19463, + 19464, 19465, 19466, 19467, 19468, 19469, 19470, 19471, + 19472, 19473, 19474, 19475, 19476, 19477, 19478, 19479, + 19480, 19481, 19482, 19483, 19484, 19485, 19486, 19487, + 19488, 19489, 19490, 19491, 19492, 19493, 19494, 19495, + 19496, 19497, 19498, 19499, 19500, 19501, 19502, 19503, + 19504, 19505, 19506, 19507, 19508, 19509, 19510, 19511, + 19512, 19513, 19514, 19515, 19516, 19517, 19518, 19519, + 19520, 19521, 19522, 19523, 19524, 19525, 19526, 19527, + 19528, 19529, 19530, 19531, 19532, 19533, 19534, 19535, + 19536, 19537, 19538, 19539, 19540, 19541, 19542, 19543, + 19544, 19545, 19546, 19547, 19548, 19549, 19550, 19551, + 19552, 19553, 19554, 19555, 19556, 19557, 19558, 19559, + 19560, 19561, 19562, 19563, 19564, 19565, 19566, 19567, + 19568, 19569, 19570, 19571, 19572, 19573, 19574, 19575, + 19576, 19577, 19578, 19579, 19580, 19581, 19582, 19583, + 19584, 19585, 19586, 19587, 19588, 19589, 19590, 19591, + 19592, 19593, 19594, 19595, 19596, 19597, 19598, 19599, + 19600, 19601, 19602, 19603, 19604, 19605, 19606, 19607, + 19608, 19609, 19610, 19611, 19612, 19613, 19614, 19615, + 19616, 19617, 19618, 19619, 19620, 19621, 19622, 19623, + 19624, 19625, 19626, 19627, 19628, 19629, 19630, 19631, + 19632, 19633, 19634, 19635, 19636, 19637, 19638, 19639, + 19640, 19641, 19642, 19643, 19644, 19645, 19646, 19647, + 19648, 19649, 19650, 19651, 19652, 19653, 19654, 19655, + 19656, 19657, 19658, 19659, 19660, 19661, 19662, 19663, + 19664, 19665, 19666, 19667, 19668, 19669, 19670, 19671, + 19672, 19673, 19674, 19675, 19676, 19677, 19678, 19679, + 19680, 19681, 19682, 19683, 19684, 19685, 19686, 19687, + 19688, 19689, 19690, 19691, 19692, 19693, 19694, 19695, + 19696, 19697, 19698, 19699, 19700, 19701, 19702, 19703, + 19704, 19705, 19706, 19707, 19708, 19709, 19710, 19711, + 19712, 19713, 19714, 19715, 19716, 19717, 19718, 19719, + 19720, 19721, 19722, 19723, 19724, 19725, 19726, 19727, + 19728, 19729, 19730, 19731, 19732, 19733, 19734, 19735, + 19736, 19737, 19738, 19739, 19740, 19741, 19742, 19743, + 19744, 19745, 19746, 19747, 19748, 19749, 19750, 19751, + 19752, 19753, 19754, 19755, 19756, 19757, 19758, 19759, + 19760, 19761, 19762, 19763, 19764, 19765, 19766, 19767, + 19768, 19769, 19770, 19771, 19772, 19773, 19774, 19775, + 19776, 19777, 19778, 19779, 19780, 19781, 19782, 19783, + 19784, 19785, 19786, 19787, 19788, 19789, 19790, 19791, + 19792, 19793, 19794, 19795, 19796, 19797, 19798, 19799, + 19800, 19801, 19802, 19803, 19804, 19805, 19806, 19807, + 19808, 19809, 19810, 19811, 19812, 19813, 19814, 19815, + 19816, 19817, 19818, 19819, 19820, 19821, 19822, 19823, + 19824, 19825, 19826, 19827, 19828, 19829, 19830, 19831, + 19832, 19833, 19834, 19835, 19836, 19837, 19838, 19839, + 19840, 19841, 19842, 19843, 19844, 19845, 19846, 19847, + 19848, 19849, 19850, 19851, 19852, 19853, 19854, 19855, + 19856, 19857, 19858, 19859, 19860, 19861, 19862, 19863, + 19864, 19865, 19866, 19867, 19868, 19869, 19870, 19871, + 19872, 19873, 19874, 19875, 19876, 19877, 19878, 19879, + 19880, 19881, 19882, 19883, 19884, 19885, 19886, 19887, + 19888, 19889, 19890, 19891, 19892, 19893, 19968, 19969, + 19970, 19971, 19972, 19973, 19974, 19975, 19976, 19977, + 19978, 19979, 19980, 19981, 19982, 19983, 19984, 19985, + 19986, 19987, 19988, 19989, 19990, 19991, 19992, 19993, + 19994, 19995, 19996, 19997, 19998, 19999, 20000, 20001, + 20002, 20003, 20004, 20005, 20006, 20007, 20008, 20009, + 20010, 20011, 20012, 20013, 20014, 20015, 20016, 20017, + 20018, 20019, 20020, 20021, 20022, 20023, 20024, 20025, + 20026, 20027, 20028, 20029, 20030, 20031, 20032, 20033, + 20034, 20035, 20036, 20037, 20038, 20039, 20040, 20041, + 20042, 20043, 20044, 20045, 20046, 20047, 20048, 20049, + 20050, 20051, 20052, 20053, 20054, 20055, 20056, 20057, + 20058, 20059, 20060, 20061, 20062, 20063, 20064, 20065, + 20066, 20067, 20068, 20069, 20070, 20071, 20072, 20073, + 20074, 20075, 20076, 20077, 20078, 20079, 20080, 20081, + 20082, 20083, 20084, 20085, 20086, 20087, 20088, 20089, + 20090, 20091, 20092, 20093, 20094, 20095, 20096, 20097, + 20098, 20099, 20100, 20101, 20102, 20103, 20104, 20105, + 20106, 20107, 20108, 20109, 20110, 20111, 20112, 20113, + 20114, 20115, 20116, 20117, 20118, 20119, 20120, 20121, + 20122, 20123, 20124, 20125, 20126, 20127, 20128, 20129, + 20130, 20131, 20132, 20133, 20134, 20135, 20136, 20137, + 20138, 20139, 20140, 20141, 20142, 20143, 20144, 20145, + 20146, 20147, 20148, 20149, 20150, 20151, 20152, 20153, + 20154, 20155, 20156, 20157, 20158, 20159, 20160, 20161, + 20162, 20163, 20164, 20165, 20166, 20167, 20168, 20169, + 20170, 20171, 20172, 20173, 20174, 20175, 20176, 20177, + 20178, 20179, 20180, 20181, 20182, 20183, 20184, 20185, + 20186, 20187, 20188, 20189, 20190, 20191, 20192, 20193, + 20194, 20195, 20196, 20197, 20198, 20199, 20200, 20201, + 20202, 20203, 20204, 20205, 20206, 20207, 20208, 20209, + 20210, 20211, 20212, 20213, 20214, 20215, 20216, 20217, + 20218, 20219, 20220, 20221, 20222, 20223, 20224, 20225, + 20226, 20227, 20228, 20229, 20230, 20231, 20232, 20233, + 20234, 20235, 20236, 20237, 20238, 20239, 20240, 20241, + 20242, 20243, 20244, 20245, 20246, 20247, 20248, 20249, + 20250, 20251, 20252, 20253, 20254, 20255, 20256, 20257, + 20258, 20259, 20260, 20261, 20262, 20263, 20264, 20265, + 20266, 20267, 20268, 20269, 20270, 20271, 20272, 20273, + 20274, 20275, 20276, 20277, 20278, 20279, 20280, 20281, + 20282, 20283, 20284, 20285, 20286, 20287, 20288, 20289, + 20290, 20291, 20292, 20293, 20294, 20295, 20296, 20297, + 20298, 20299, 20300, 20301, 20302, 20303, 20304, 20305, + 20306, 20307, 20308, 20309, 20310, 20311, 20312, 20313, + 20314, 20315, 20316, 20317, 20318, 20319, 20320, 20321, + 20322, 20323, 20324, 20325, 20326, 20327, 20328, 20329, + 20330, 20331, 20332, 20333, 20334, 20335, 20336, 20337, + 20338, 20339, 20340, 20341, 20342, 20343, 20344, 20345, + 20346, 20347, 20348, 20349, 20350, 20351, 20352, 20353, + 20354, 20355, 20356, 20357, 20358, 20359, 20360, 20361, + 20362, 20363, 20364, 20365, 20366, 20367, 20368, 20369, + 20370, 20371, 20372, 20373, 20374, 20375, 20376, 20377, + 20378, 20379, 20380, 20381, 20382, 20383, 20384, 20385, + 20386, 20387, 20388, 20389, 20390, 20391, 20392, 20393, + 20394, 20395, 20396, 20397, 20398, 20399, 20400, 20401, + 20402, 20403, 20404, 20405, 20406, 20407, 20408, 20409, + 20410, 20411, 20412, 20413, 20414, 20415, 20416, 20417, + 20418, 20419, 20420, 20421, 20422, 20423, 20424, 20425, + 20426, 20427, 20428, 20429, 20430, 20431, 20432, 20433, + 20434, 20435, 20436, 20437, 20438, 20439, 20440, 20441, + 20442, 20443, 20444, 20445, 20446, 20447, 20448, 20449, + 20450, 20451, 20452, 20453, 20454, 20455, 20456, 20457, + 20458, 20459, 20460, 20461, 20462, 20463, 20464, 20465, + 20466, 20467, 20468, 20469, 20470, 20471, 20472, 20473, + 20474, 20475, 20476, 20477, 20478, 20479, 20480, 20481, + 20482, 20483, 20484, 20485, 20486, 20487, 20488, 20489, + 20490, 20491, 20492, 20493, 20494, 20495, 20496, 20497, + 20498, 20499, 20500, 20501, 20502, 20503, 20504, 20505, + 20506, 20507, 20508, 20509, 20510, 20511, 20512, 20513, + 20514, 20515, 20516, 20517, 20518, 20519, 20520, 20521, + 20522, 20523, 20524, 20525, 20526, 20527, 20528, 20529, + 20530, 20531, 20532, 20533, 20534, 20535, 20536, 20537, + 20538, 20539, 20540, 20541, 20542, 20543, 20544, 20545, + 20546, 20547, 20548, 20549, 20550, 20551, 20552, 20553, + 20554, 20555, 20556, 20557, 20558, 20559, 20560, 20561, + 20562, 20563, 20564, 20565, 20566, 20567, 20568, 20569, + 20570, 20571, 20572, 20573, 20574, 20575, 20576, 20577, + 20578, 20579, 20580, 20581, 20582, 20583, 20584, 20585, + 20586, 20587, 20588, 20589, 20590, 20591, 20592, 20593, + 20594, 20595, 20596, 20597, 20598, 20599, 20600, 20601, + 20602, 20603, 20604, 20605, 20606, 20607, 20608, 20609, + 20610, 20611, 20612, 20613, 20614, 20615, 20616, 20617, + 20618, 20619, 20620, 20621, 20622, 20623, 20624, 20625, + 20626, 20627, 20628, 20629, 20630, 20631, 20632, 20633, + 20634, 20635, 20636, 20637, 20638, 20639, 20640, 20641, + 20642, 20643, 20644, 20645, 20646, 20647, 20648, 20649, + 20650, 20651, 20652, 20653, 20654, 20655, 20656, 20657, + 20658, 20659, 20660, 20661, 20662, 20663, 20664, 20665, + 20666, 20667, 20668, 20669, 20670, 20671, 20672, 20673, + 20674, 20675, 20676, 20677, 20678, 20679, 20680, 20681, + 20682, 20683, 20684, 20685, 20686, 20687, 20688, 20689, + 20690, 20691, 20692, 20693, 20694, 20695, 20696, 20697, + 20698, 20699, 20700, 20701, 20702, 20703, 20704, 20705, + 20706, 20707, 20708, 20709, 20710, 20711, 20712, 20713, + 20714, 20715, 20716, 20717, 20718, 20719, 20720, 20721, + 20722, 20723, 20724, 20725, 20726, 20727, 20728, 20729, + 20730, 20731, 20732, 20733, 20734, 20735, 20736, 20737, + 20738, 20739, 20740, 20741, 20742, 20743, 20744, 20745, + 20746, 20747, 20748, 20749, 20750, 20751, 20752, 20753, + 20754, 20755, 20756, 20757, 20758, 20759, 20760, 20761, + 20762, 20763, 20764, 20765, 20766, 20767, 20768, 20769, + 20770, 20771, 20772, 20773, 20774, 20775, 20776, 20777, + 20778, 20779, 20780, 20781, 20782, 20783, 20784, 20785, + 20786, 20787, 20788, 20789, 20790, 20791, 20792, 20793, + 20794, 20795, 20796, 20797, 20798, 20799, 20800, 20801, + 20802, 20803, 20804, 20805, 20806, 20807, 20808, 20809, + 20810, 20811, 20812, 20813, 20814, 20815, 20816, 20817, + 20818, 20819, 20820, 20821, 20822, 20823, 20824, 20825, + 20826, 20827, 20828, 20829, 20830, 20831, 20832, 20833, + 20834, 20835, 20836, 20837, 20838, 20839, 20840, 20841, + 20842, 20843, 20844, 20845, 20846, 20847, 20848, 20849, + 20850, 20851, 20852, 20853, 20854, 20855, 20856, 20857, + 20858, 20859, 20860, 20861, 20862, 20863, 20864, 20865, + 20866, 20867, 20868, 20869, 20870, 20871, 20872, 20873, + 20874, 20875, 20876, 20877, 20878, 20879, 20880, 20881, + 20882, 20883, 20884, 20885, 20886, 20887, 20888, 20889, + 20890, 20891, 20892, 20893, 20894, 20895, 20896, 20897, + 20898, 20899, 20900, 20901, 20902, 20903, 20904, 20905, + 20906, 20907, 20908, 20909, 20910, 20911, 20912, 20913, + 20914, 20915, 20916, 20917, 20918, 20919, 20920, 20921, + 20922, 20923, 20924, 20925, 20926, 20927, 20928, 20929, + 20930, 20931, 20932, 20933, 20934, 20935, 20936, 20937, + 20938, 20939, 20940, 20941, 20942, 20943, 20944, 20945, + 20946, 20947, 20948, 20949, 20950, 20951, 20952, 20953, + 20954, 20955, 20956, 20957, 20958, 20959, 20960, 20961, + 20962, 20963, 20964, 20965, 20966, 20967, 20968, 20969, + 20970, 20971, 20972, 20973, 20974, 20975, 20976, 20977, + 20978, 20979, 20980, 20981, 20982, 20983, 20984, 20985, + 20986, 20987, 20988, 20989, 20990, 20991, 20992, 20993, + 20994, 20995, 20996, 20997, 20998, 20999, 21000, 21001, + 21002, 21003, 21004, 21005, 21006, 21007, 21008, 21009, + 21010, 21011, 21012, 21013, 21014, 21015, 21016, 21017, + 21018, 21019, 21020, 21021, 21022, 21023, 21024, 21025, + 21026, 21027, 21028, 21029, 21030, 21031, 21032, 21033, + 21034, 21035, 21036, 21037, 21038, 21039, 21040, 21041, + 21042, 21043, 21044, 21045, 21046, 21047, 21048, 21049, + 21050, 21051, 21052, 21053, 21054, 21055, 21056, 21057, + 21058, 21059, 21060, 21061, 21062, 21063, 21064, 21065, + 21066, 21067, 21068, 21069, 21070, 21071, 21072, 21073, + 21074, 21075, 21076, 21077, 21078, 21079, 21080, 21081, + 21082, 21083, 21084, 21085, 21086, 21087, 21088, 21089, + 21090, 21091, 21092, 21093, 21094, 21095, 21096, 21097, + 21098, 21099, 21100, 21101, 21102, 21103, 21104, 21105, + 21106, 21107, 21108, 21109, 21110, 21111, 21112, 21113, + 21114, 21115, 21116, 21117, 21118, 21119, 21120, 21121, + 21122, 21123, 21124, 21125, 21126, 21127, 21128, 21129, + 21130, 21131, 21132, 21133, 21134, 21135, 21136, 21137, + 21138, 21139, 21140, 21141, 21142, 21143, 21144, 21145, + 21146, 21147, 21148, 21149, 21150, 21151, 21152, 21153, + 21154, 21155, 21156, 21157, 21158, 21159, 21160, 21161, + 21162, 21163, 21164, 21165, 21166, 21167, 21168, 21169, + 21170, 21171, 21172, 21173, 21174, 21175, 21176, 21177, + 21178, 21179, 21180, 21181, 21182, 21183, 21184, 21185, + 21186, 21187, 21188, 21189, 21190, 21191, 21192, 21193, + 21194, 21195, 21196, 21197, 21198, 21199, 21200, 21201, + 21202, 21203, 21204, 21205, 21206, 21207, 21208, 21209, + 21210, 21211, 21212, 21213, 21214, 21215, 21216, 21217, + 21218, 21219, 21220, 21221, 21222, 21223, 21224, 21225, + 21226, 21227, 21228, 21229, 21230, 21231, 21232, 21233, + 21234, 21235, 21236, 21237, 21238, 21239, 21240, 21241, + 21242, 21243, 21244, 21245, 21246, 21247, 21248, 21249, + 21250, 21251, 21252, 21253, 21254, 21255, 21256, 21257, + 21258, 21259, 21260, 21261, 21262, 21263, 21264, 21265, + 21266, 21267, 21268, 21269, 21270, 21271, 21272, 21273, + 21274, 21275, 21276, 21277, 21278, 21279, 21280, 21281, + 21282, 21283, 21284, 21285, 21286, 21287, 21288, 21289, + 21290, 21291, 21292, 21293, 21294, 21295, 21296, 21297, + 21298, 21299, 21300, 21301, 21302, 21303, 21304, 21305, + 21306, 21307, 21308, 21309, 21310, 21311, 21312, 21313, + 21314, 21315, 21316, 21317, 21318, 21319, 21320, 21321, + 21322, 21323, 21324, 21325, 21326, 21327, 21328, 21329, + 21330, 21331, 21332, 21333, 21334, 21335, 21336, 21337, + 21338, 21339, 21340, 21341, 21342, 21343, 21344, 21345, + 21346, 21347, 21348, 21349, 21350, 21351, 21352, 21353, + 21354, 21355, 21356, 21357, 21358, 21359, 21360, 21361, + 21362, 21363, 21364, 21365, 21366, 21367, 21368, 21369, + 21370, 21371, 21372, 21373, 21374, 21375, 21376, 21377, + 21378, 21379, 21380, 21381, 21382, 21383, 21384, 21385, + 21386, 21387, 21388, 21389, 21390, 21391, 21392, 21393, + 21394, 21395, 21396, 21397, 21398, 21399, 21400, 21401, + 21402, 21403, 21404, 21405, 21406, 21407, 21408, 21409, + 21410, 21411, 21412, 21413, 21414, 21415, 21416, 21417, + 21418, 21419, 21420, 21421, 21422, 21423, 21424, 21425, + 21426, 21427, 21428, 21429, 21430, 21431, 21432, 21433, + 21434, 21435, 21436, 21437, 21438, 21439, 21440, 21441, + 21442, 21443, 21444, 21445, 21446, 21447, 21448, 21449, + 21450, 21451, 21452, 21453, 21454, 21455, 21456, 21457, + 21458, 21459, 21460, 21461, 21462, 21463, 21464, 21465, + 21466, 21467, 21468, 21469, 21470, 21471, 21472, 21473, + 21474, 21475, 21476, 21477, 21478, 21479, 21480, 21481, + 21482, 21483, 21484, 21485, 21486, 21487, 21488, 21489, + 21490, 21491, 21492, 21493, 21494, 21495, 21496, 21497, + 21498, 21499, 21500, 21501, 21502, 21503, 21504, 21505, + 21506, 21507, 21508, 21509, 21510, 21511, 21512, 21513, + 21514, 21515, 21516, 21517, 21518, 21519, 21520, 21521, + 21522, 21523, 21524, 21525, 21526, 21527, 21528, 21529, + 21530, 21531, 21532, 21533, 21534, 21535, 21536, 21537, + 21538, 21539, 21540, 21541, 21542, 21543, 21544, 21545, + 21546, 21547, 21548, 21549, 21550, 21551, 21552, 21553, + 21554, 21555, 21556, 21557, 21558, 21559, 21560, 21561, + 21562, 21563, 21564, 21565, 21566, 21567, 21568, 21569, + 21570, 21571, 21572, 21573, 21574, 21575, 21576, 21577, + 21578, 21579, 21580, 21581, 21582, 21583, 21584, 21585, + 21586, 21587, 21588, 21589, 21590, 21591, 21592, 21593, + 21594, 21595, 21596, 21597, 21598, 21599, 21600, 21601, + 21602, 21603, 21604, 21605, 21606, 21607, 21608, 21609, + 21610, 21611, 21612, 21613, 21614, 21615, 21616, 21617, + 21618, 21619, 21620, 21621, 21622, 21623, 21624, 21625, + 21626, 21627, 21628, 21629, 21630, 21631, 21632, 21633, + 21634, 21635, 21636, 21637, 21638, 21639, 21640, 21641, + 21642, 21643, 21644, 21645, 21646, 21647, 21648, 21649, + 21650, 21651, 21652, 21653, 21654, 21655, 21656, 21657, + 21658, 21659, 21660, 21661, 21662, 21663, 21664, 21665, + 21666, 21667, 21668, 21669, 21670, 21671, 21672, 21673, + 21674, 21675, 21676, 21677, 21678, 21679, 21680, 21681, + 21682, 21683, 21684, 21685, 21686, 21687, 21688, 21689, + 21690, 21691, 21692, 21693, 21694, 21695, 21696, 21697, + 21698, 21699, 21700, 21701, 21702, 21703, 21704, 21705, + 21706, 21707, 21708, 21709, 21710, 21711, 21712, 21713, + 21714, 21715, 21716, 21717, 21718, 21719, 21720, 21721, + 21722, 21723, 21724, 21725, 21726, 21727, 21728, 21729, + 21730, 21731, 21732, 21733, 21734, 21735, 21736, 21737, + 21738, 21739, 21740, 21741, 21742, 21743, 21744, 21745, + 21746, 21747, 21748, 21749, 21750, 21751, 21752, 21753, + 21754, 21755, 21756, 21757, 21758, 21759, 21760, 21761, + 21762, 21763, 21764, 21765, 21766, 21767, 21768, 21769, + 21770, 21771, 21772, 21773, 21774, 21775, 21776, 21777, + 21778, 21779, 21780, 21781, 21782, 21783, 21784, 21785, + 21786, 21787, 21788, 21789, 21790, 21791, 21792, 21793, + 21794, 21795, 21796, 21797, 21798, 21799, 21800, 21801, + 21802, 21803, 21804, 21805, 21806, 21807, 21808, 21809, + 21810, 21811, 21812, 21813, 21814, 21815, 21816, 21817, + 21818, 21819, 21820, 21821, 21822, 21823, 21824, 21825, + 21826, 21827, 21828, 21829, 21830, 21831, 21832, 21833, + 21834, 21835, 21836, 21837, 21838, 21839, 21840, 21841, + 21842, 21843, 21844, 21845, 21846, 21847, 21848, 21849, + 21850, 21851, 21852, 21853, 21854, 21855, 21856, 21857, + 21858, 21859, 21860, 21861, 21862, 21863, 21864, 21865, + 21866, 21867, 21868, 21869, 21870, 21871, 21872, 21873, + 21874, 21875, 21876, 21877, 21878, 21879, 21880, 21881, + 21882, 21883, 21884, 21885, 21886, 21887, 21888, 21889, + 21890, 21891, 21892, 21893, 21894, 21895, 21896, 21897, + 21898, 21899, 21900, 21901, 21902, 21903, 21904, 21905, + 21906, 21907, 21908, 21909, 21910, 21911, 21912, 21913, + 21914, 21915, 21916, 21917, 21918, 21919, 21920, 21921, + 21922, 21923, 21924, 21925, 21926, 21927, 21928, 21929, + 21930, 21931, 21932, 21933, 21934, 21935, 21936, 21937, + 21938, 21939, 21940, 21941, 21942, 21943, 21944, 21945, + 21946, 21947, 21948, 21949, 21950, 21951, 21952, 21953, + 21954, 21955, 21956, 21957, 21958, 21959, 21960, 21961, + 21962, 21963, 21964, 21965, 21966, 21967, 21968, 21969, + 21970, 21971, 21972, 21973, 21974, 21975, 21976, 21977, + 21978, 21979, 21980, 21981, 21982, 21983, 21984, 21985, + 21986, 21987, 21988, 21989, 21990, 21991, 21992, 21993, + 21994, 21995, 21996, 21997, 21998, 21999, 22000, 22001, + 22002, 22003, 22004, 22005, 22006, 22007, 22008, 22009, + 22010, 22011, 22012, 22013, 22014, 22015, 22016, 22017, + 22018, 22019, 22020, 22021, 22022, 22023, 22024, 22025, + 22026, 22027, 22028, 22029, 22030, 22031, 22032, 22033, + 22034, 22035, 22036, 22037, 22038, 22039, 22040, 22041, + 22042, 22043, 22044, 22045, 22046, 22047, 22048, 22049, + 22050, 22051, 22052, 22053, 22054, 22055, 22056, 22057, + 22058, 22059, 22060, 22061, 22062, 22063, 22064, 22065, + 22066, 22067, 22068, 22069, 22070, 22071, 22072, 22073, + 22074, 22075, 22076, 22077, 22078, 22079, 22080, 22081, + 22082, 22083, 22084, 22085, 22086, 22087, 22088, 22089, + 22090, 22091, 22092, 22093, 22094, 22095, 22096, 22097, + 22098, 22099, 22100, 22101, 22102, 22103, 22104, 22105, + 22106, 22107, 22108, 22109, 22110, 22111, 22112, 22113, + 22114, 22115, 22116, 22117, 22118, 22119, 22120, 22121, + 22122, 22123, 22124, 22125, 22126, 22127, 22128, 22129, + 22130, 22131, 22132, 22133, 22134, 22135, 22136, 22137, + 22138, 22139, 22140, 22141, 22142, 22143, 22144, 22145, + 22146, 22147, 22148, 22149, 22150, 22151, 22152, 22153, + 22154, 22155, 22156, 22157, 22158, 22159, 22160, 22161, + 22162, 22163, 22164, 22165, 22166, 22167, 22168, 22169, + 22170, 22171, 22172, 22173, 22174, 22175, 22176, 22177, + 22178, 22179, 22180, 22181, 22182, 22183, 22184, 22185, + 22186, 22187, 22188, 22189, 22190, 22191, 22192, 22193, + 22194, 22195, 22196, 22197, 22198, 22199, 22200, 22201, + 22202, 22203, 22204, 22205, 22206, 22207, 22208, 22209, + 22210, 22211, 22212, 22213, 22214, 22215, 22216, 22217, + 22218, 22219, 22220, 22221, 22222, 22223, 22224, 22225, + 22226, 22227, 22228, 22229, 22230, 22231, 22232, 22233, + 22234, 22235, 22236, 22237, 22238, 22239, 22240, 22241, + 22242, 22243, 22244, 22245, 22246, 22247, 22248, 22249, + 22250, 22251, 22252, 22253, 22254, 22255, 22256, 22257, + 22258, 22259, 22260, 22261, 22262, 22263, 22264, 22265, + 22266, 22267, 22268, 22269, 22270, 22271, 22272, 22273, + 22274, 22275, 22276, 22277, 22278, 22279, 22280, 22281, + 22282, 22283, 22284, 22285, 22286, 22287, 22288, 22289, + 22290, 22291, 22292, 22293, 22294, 22295, 22296, 22297, + 22298, 22299, 22300, 22301, 22302, 22303, 22304, 22305, + 22306, 22307, 22308, 22309, 22310, 22311, 22312, 22313, + 22314, 22315, 22316, 22317, 22318, 22319, 22320, 22321, + 22322, 22323, 22324, 22325, 22326, 22327, 22328, 22329, + 22330, 22331, 22332, 22333, 22334, 22335, 22336, 22337, + 22338, 22339, 22340, 22341, 22342, 22343, 22344, 22345, + 22346, 22347, 22348, 22349, 22350, 22351, 22352, 22353, + 22354, 22355, 22356, 22357, 22358, 22359, 22360, 22361, + 22362, 22363, 22364, 22365, 22366, 22367, 22368, 22369, + 22370, 22371, 22372, 22373, 22374, 22375, 22376, 22377, + 22378, 22379, 22380, 22381, 22382, 22383, 22384, 22385, + 22386, 22387, 22388, 22389, 22390, 22391, 22392, 22393, + 22394, 22395, 22396, 22397, 22398, 22399, 22400, 22401, + 22402, 22403, 22404, 22405, 22406, 22407, 22408, 22409, + 22410, 22411, 22412, 22413, 22414, 22415, 22416, 22417, + 22418, 22419, 22420, 22421, 22422, 22423, 22424, 22425, + 22426, 22427, 22428, 22429, 22430, 22431, 22432, 22433, + 22434, 22435, 22436, 22437, 22438, 22439, 22440, 22441, + 22442, 22443, 22444, 22445, 22446, 22447, 22448, 22449, + 22450, 22451, 22452, 22453, 22454, 22455, 22456, 22457, + 22458, 22459, 22460, 22461, 22462, 22463, 22464, 22465, + 22466, 22467, 22468, 22469, 22470, 22471, 22472, 22473, + 22474, 22475, 22476, 22477, 22478, 22479, 22480, 22481, + 22482, 22483, 22484, 22485, 22486, 22487, 22488, 22489, + 22490, 22491, 22492, 22493, 22494, 22495, 22496, 22497, + 22498, 22499, 22500, 22501, 22502, 22503, 22504, 22505, + 22506, 22507, 22508, 22509, 22510, 22511, 22512, 22513, + 22514, 22515, 22516, 22517, 22518, 22519, 22520, 22521, + 22522, 22523, 22524, 22525, 22526, 22527, 22528, 22529, + 22530, 22531, 22532, 22533, 22534, 22535, 22536, 22537, + 22538, 22539, 22540, 22541, 22542, 22543, 22544, 22545, + 22546, 22547, 22548, 22549, 22550, 22551, 22552, 22553, + 22554, 22555, 22556, 22557, 22558, 22559, 22560, 22561, + 22562, 22563, 22564, 22565, 22566, 22567, 22568, 22569, + 22570, 22571, 22572, 22573, 22574, 22575, 22576, 22577, + 22578, 22579, 22580, 22581, 22582, 22583, 22584, 22585, + 22586, 22587, 22588, 22589, 22590, 22591, 22592, 22593, + 22594, 22595, 22596, 22597, 22598, 22599, 22600, 22601, + 22602, 22603, 22604, 22605, 22606, 22607, 22608, 22609, + 22610, 22611, 22612, 22613, 22614, 22615, 22616, 22617, + 22618, 22619, 22620, 22621, 22622, 22623, 22624, 22625, + 22626, 22627, 22628, 22629, 22630, 22631, 22632, 22633, + 22634, 22635, 22636, 22637, 22638, 22639, 22640, 22641, + 22642, 22643, 22644, 22645, 22646, 22647, 22648, 22649, + 22650, 22651, 22652, 22653, 22654, 22655, 22656, 22657, + 22658, 22659, 22660, 22661, 22662, 22663, 22664, 22665, + 22666, 22667, 22668, 22669, 22670, 22671, 22672, 22673, + 22674, 22675, 22676, 22677, 22678, 22679, 22680, 22681, + 22682, 22683, 22684, 22685, 22686, 22687, 22688, 22689, + 22690, 22691, 22692, 22693, 22694, 22695, 22696, 22697, + 22698, 22699, 22700, 22701, 22702, 22703, 22704, 22705, + 22706, 22707, 22708, 22709, 22710, 22711, 22712, 22713, + 22714, 22715, 22716, 22717, 22718, 22719, 22720, 22721, + 22722, 22723, 22724, 22725, 22726, 22727, 22728, 22729, + 22730, 22731, 22732, 22733, 22734, 22735, 22736, 22737, + 22738, 22739, 22740, 22741, 22742, 22743, 22744, 22745, + 22746, 22747, 22748, 22749, 22750, 22751, 22752, 22753, + 22754, 22755, 22756, 22757, 22758, 22759, 22760, 22761, + 22762, 22763, 22764, 22765, 22766, 22767, 22768, 22769, + 22770, 22771, 22772, 22773, 22774, 22775, 22776, 22777, + 22778, 22779, 22780, 22781, 22782, 22783, 22784, 22785, + 22786, 22787, 22788, 22789, 22790, 22791, 22792, 22793, + 22794, 22795, 22796, 22797, 22798, 22799, 22800, 22801, + 22802, 22803, 22804, 22805, 22806, 22807, 22808, 22809, + 22810, 22811, 22812, 22813, 22814, 22815, 22816, 22817, + 22818, 22819, 22820, 22821, 22822, 22823, 22824, 22825, + 22826, 22827, 22828, 22829, 22830, 22831, 22832, 22833, + 22834, 22835, 22836, 22837, 22838, 22839, 22840, 22841, + 22842, 22843, 22844, 22845, 22846, 22847, 22848, 22849, + 22850, 22851, 22852, 22853, 22854, 22855, 22856, 22857, + 22858, 22859, 22860, 22861, 22862, 22863, 22864, 22865, + 22866, 22867, 22868, 22869, 22870, 22871, 22872, 22873, + 22874, 22875, 22876, 22877, 22878, 22879, 22880, 22881, + 22882, 22883, 22884, 22885, 22886, 22887, 22888, 22889, + 22890, 22891, 22892, 22893, 22894, 22895, 22896, 22897, + 22898, 22899, 22900, 22901, 22902, 22903, 22904, 22905, + 22906, 22907, 22908, 22909, 22910, 22911, 22912, 22913, + 22914, 22915, 22916, 22917, 22918, 22919, 22920, 22921, + 22922, 22923, 22924, 22925, 22926, 22927, 22928, 22929, + 22930, 22931, 22932, 22933, 22934, 22935, 22936, 22937, + 22938, 22939, 22940, 22941, 22942, 22943, 22944, 22945, + 22946, 22947, 22948, 22949, 22950, 22951, 22952, 22953, + 22954, 22955, 22956, 22957, 22958, 22959, 22960, 22961, + 22962, 22963, 22964, 22965, 22966, 22967, 22968, 22969, + 22970, 22971, 22972, 22973, 22974, 22975, 22976, 22977, + 22978, 22979, 22980, 22981, 22982, 22983, 22984, 22985, + 22986, 22987, 22988, 22989, 22990, 22991, 22992, 22993, + 22994, 22995, 22996, 22997, 22998, 22999, 23000, 23001, + 23002, 23003, 23004, 23005, 23006, 23007, 23008, 23009, + 23010, 23011, 23012, 23013, 23014, 23015, 23016, 23017, + 23018, 23019, 23020, 23021, 23022, 23023, 23024, 23025, + 23026, 23027, 23028, 23029, 23030, 23031, 23032, 23033, + 23034, 23035, 23036, 23037, 23038, 23039, 23040, 23041, + 23042, 23043, 23044, 23045, 23046, 23047, 23048, 23049, + 23050, 23051, 23052, 23053, 23054, 23055, 23056, 23057, + 23058, 23059, 23060, 23061, 23062, 23063, 23064, 23065, + 23066, 23067, 23068, 23069, 23070, 23071, 23072, 23073, + 23074, 23075, 23076, 23077, 23078, 23079, 23080, 23081, + 23082, 23083, 23084, 23085, 23086, 23087, 23088, 23089, + 23090, 23091, 23092, 23093, 23094, 23095, 23096, 23097, + 23098, 23099, 23100, 23101, 23102, 23103, 23104, 23105, + 23106, 23107, 23108, 23109, 23110, 23111, 23112, 23113, + 23114, 23115, 23116, 23117, 23118, 23119, 23120, 23121, + 23122, 23123, 23124, 23125, 23126, 23127, 23128, 23129, + 23130, 23131, 23132, 23133, 23134, 23135, 23136, 23137, + 23138, 23139, 23140, 23141, 23142, 23143, 23144, 23145, + 23146, 23147, 23148, 23149, 23150, 23151, 23152, 23153, + 23154, 23155, 23156, 23157, 23158, 23159, 23160, 23161, + 23162, 23163, 23164, 23165, 23166, 23167, 23168, 23169, + 23170, 23171, 23172, 23173, 23174, 23175, 23176, 23177, + 23178, 23179, 23180, 23181, 23182, 23183, 23184, 23185, + 23186, 23187, 23188, 23189, 23190, 23191, 23192, 23193, + 23194, 23195, 23196, 23197, 23198, 23199, 23200, 23201, + 23202, 23203, 23204, 23205, 23206, 23207, 23208, 23209, + 23210, 23211, 23212, 23213, 23214, 23215, 23216, 23217, + 23218, 23219, 23220, 23221, 23222, 23223, 23224, 23225, + 23226, 23227, 23228, 23229, 23230, 23231, 23232, 23233, + 23234, 23235, 23236, 23237, 23238, 23239, 23240, 23241, + 23242, 23243, 23244, 23245, 23246, 23247, 23248, 23249, + 23250, 23251, 23252, 23253, 23254, 23255, 23256, 23257, + 23258, 23259, 23260, 23261, 23262, 23263, 23264, 23265, + 23266, 23267, 23268, 23269, 23270, 23271, 23272, 23273, + 23274, 23275, 23276, 23277, 23278, 23279, 23280, 23281, + 23282, 23283, 23284, 23285, 23286, 23287, 23288, 23289, + 23290, 23291, 23292, 23293, 23294, 23295, 23296, 23297, + 23298, 23299, 23300, 23301, 23302, 23303, 23304, 23305, + 23306, 23307, 23308, 23309, 23310, 23311, 23312, 23313, + 23314, 23315, 23316, 23317, 23318, 23319, 23320, 23321, + 23322, 23323, 23324, 23325, 23326, 23327, 23328, 23329, + 23330, 23331, 23332, 23333, 23334, 23335, 23336, 23337, + 23338, 23339, 23340, 23341, 23342, 23343, 23344, 23345, + 23346, 23347, 23348, 23349, 23350, 23351, 23352, 23353, + 23354, 23355, 23356, 23357, 23358, 23359, 23360, 23361, + 23362, 23363, 23364, 23365, 23366, 23367, 23368, 23369, + 23370, 23371, 23372, 23373, 23374, 23375, 23376, 23377, + 23378, 23379, 23380, 23381, 23382, 23383, 23384, 23385, + 23386, 23387, 23388, 23389, 23390, 23391, 23392, 23393, + 23394, 23395, 23396, 23397, 23398, 23399, 23400, 23401, + 23402, 23403, 23404, 23405, 23406, 23407, 23408, 23409, + 23410, 23411, 23412, 23413, 23414, 23415, 23416, 23417, + 23418, 23419, 23420, 23421, 23422, 23423, 23424, 23425, + 23426, 23427, 23428, 23429, 23430, 23431, 23432, 23433, + 23434, 23435, 23436, 23437, 23438, 23439, 23440, 23441, + 23442, 23443, 23444, 23445, 23446, 23447, 23448, 23449, + 23450, 23451, 23452, 23453, 23454, 23455, 23456, 23457, + 23458, 23459, 23460, 23461, 23462, 23463, 23464, 23465, + 23466, 23467, 23468, 23469, 23470, 23471, 23472, 23473, + 23474, 23475, 23476, 23477, 23478, 23479, 23480, 23481, + 23482, 23483, 23484, 23485, 23486, 23487, 23488, 23489, + 23490, 23491, 23492, 23493, 23494, 23495, 23496, 23497, + 23498, 23499, 23500, 23501, 23502, 23503, 23504, 23505, + 23506, 23507, 23508, 23509, 23510, 23511, 23512, 23513, + 23514, 23515, 23516, 23517, 23518, 23519, 23520, 23521, + 23522, 23523, 23524, 23525, 23526, 23527, 23528, 23529, + 23530, 23531, 23532, 23533, 23534, 23535, 23536, 23537, + 23538, 23539, 23540, 23541, 23542, 23543, 23544, 23545, + 23546, 23547, 23548, 23549, 23550, 23551, 23552, 23553, + 23554, 23555, 23556, 23557, 23558, 23559, 23560, 23561, + 23562, 23563, 23564, 23565, 23566, 23567, 23568, 23569, + 23570, 23571, 23572, 23573, 23574, 23575, 23576, 23577, + 23578, 23579, 23580, 23581, 23582, 23583, 23584, 23585, + 23586, 23587, 23588, 23589, 23590, 23591, 23592, 23593, + 23594, 23595, 23596, 23597, 23598, 23599, 23600, 23601, + 23602, 23603, 23604, 23605, 23606, 23607, 23608, 23609, + 23610, 23611, 23612, 23613, 23614, 23615, 23616, 23617, + 23618, 23619, 23620, 23621, 23622, 23623, 23624, 23625, + 23626, 23627, 23628, 23629, 23630, 23631, 23632, 23633, + 23634, 23635, 23636, 23637, 23638, 23639, 23640, 23641, + 23642, 23643, 23644, 23645, 23646, 23647, 23648, 23649, + 23650, 23651, 23652, 23653, 23654, 23655, 23656, 23657, + 23658, 23659, 23660, 23661, 23662, 23663, 23664, 23665, + 23666, 23667, 23668, 23669, 23670, 23671, 23672, 23673, + 23674, 23675, 23676, 23677, 23678, 23679, 23680, 23681, + 23682, 23683, 23684, 23685, 23686, 23687, 23688, 23689, + 23690, 23691, 23692, 23693, 23694, 23695, 23696, 23697, + 23698, 23699, 23700, 23701, 23702, 23703, 23704, 23705, + 23706, 23707, 23708, 23709, 23710, 23711, 23712, 23713, + 23714, 23715, 23716, 23717, 23718, 23719, 23720, 23721, + 23722, 23723, 23724, 23725, 23726, 23727, 23728, 23729, + 23730, 23731, 23732, 23733, 23734, 23735, 23736, 23737, + 23738, 23739, 23740, 23741, 23742, 23743, 23744, 23745, + 23746, 23747, 23748, 23749, 23750, 23751, 23752, 23753, + 23754, 23755, 23756, 23757, 23758, 23759, 23760, 23761, + 23762, 23763, 23764, 23765, 23766, 23767, 23768, 23769, + 23770, 23771, 23772, 23773, 23774, 23775, 23776, 23777, + 23778, 23779, 23780, 23781, 23782, 23783, 23784, 23785, + 23786, 23787, 23788, 23789, 23790, 23791, 23792, 23793, + 23794, 23795, 23796, 23797, 23798, 23799, 23800, 23801, + 23802, 23803, 23804, 23805, 23806, 23807, 23808, 23809, + 23810, 23811, 23812, 23813, 23814, 23815, 23816, 23817, + 23818, 23819, 23820, 23821, 23822, 23823, 23824, 23825, + 23826, 23827, 23828, 23829, 23830, 23831, 23832, 23833, + 23834, 23835, 23836, 23837, 23838, 23839, 23840, 23841, + 23842, 23843, 23844, 23845, 23846, 23847, 23848, 23849, + 23850, 23851, 23852, 23853, 23854, 23855, 23856, 23857, + 23858, 23859, 23860, 23861, 23862, 23863, 23864, 23865, + 23866, 23867, 23868, 23869, 23870, 23871, 23872, 23873, + 23874, 23875, 23876, 23877, 23878, 23879, 23880, 23881, + 23882, 23883, 23884, 23885, 23886, 23887, 23888, 23889, + 23890, 23891, 23892, 23893, 23894, 23895, 23896, 23897, + 23898, 23899, 23900, 23901, 23902, 23903, 23904, 23905, + 23906, 23907, 23908, 23909, 23910, 23911, 23912, 23913, + 23914, 23915, 23916, 23917, 23918, 23919, 23920, 23921, + 23922, 23923, 23924, 23925, 23926, 23927, 23928, 23929, + 23930, 23931, 23932, 23933, 23934, 23935, 23936, 23937, + 23938, 23939, 23940, 23941, 23942, 23943, 23944, 23945, + 23946, 23947, 23948, 23949, 23950, 23951, 23952, 23953, + 23954, 23955, 23956, 23957, 23958, 23959, 23960, 23961, + 23962, 23963, 23964, 23965, 23966, 23967, 23968, 23969, + 23970, 23971, 23972, 23973, 23974, 23975, 23976, 23977, + 23978, 23979, 23980, 23981, 23982, 23983, 23984, 23985, + 23986, 23987, 23988, 23989, 23990, 23991, 23992, 23993, + 23994, 23995, 23996, 23997, 23998, 23999, 24000, 24001, + 24002, 24003, 24004, 24005, 24006, 24007, 24008, 24009, + 24010, 24011, 24012, 24013, 24014, 24015, 24016, 24017, + 24018, 24019, 24020, 24021, 24022, 24023, 24024, 24025, + 24026, 24027, 24028, 24029, 24030, 24031, 24032, 24033, + 24034, 24035, 24036, 24037, 24038, 24039, 24040, 24041, + 24042, 24043, 24044, 24045, 24046, 24047, 24048, 24049, + 24050, 24051, 24052, 24053, 24054, 24055, 24056, 24057, + 24058, 24059, 24060, 24061, 24062, 24063, 24064, 24065, + 24066, 24067, 24068, 24069, 24070, 24071, 24072, 24073, + 24074, 24075, 24076, 24077, 24078, 24079, 24080, 24081, + 24082, 24083, 24084, 24085, 24086, 24087, 24088, 24089, + 24090, 24091, 24092, 24093, 24094, 24095, 24096, 24097, + 24098, 24099, 24100, 24101, 24102, 24103, 24104, 24105, + 24106, 24107, 24108, 24109, 24110, 24111, 24112, 24113, + 24114, 24115, 24116, 24117, 24118, 24119, 24120, 24121, + 24122, 24123, 24124, 24125, 24126, 24127, 24128, 24129, + 24130, 24131, 24132, 24133, 24134, 24135, 24136, 24137, + 24138, 24139, 24140, 24141, 24142, 24143, 24144, 24145, + 24146, 24147, 24148, 24149, 24150, 24151, 24152, 24153, + 24154, 24155, 24156, 24157, 24158, 24159, 24160, 24161, + 24162, 24163, 24164, 24165, 24166, 24167, 24168, 24169, + 24170, 24171, 24172, 24173, 24174, 24175, 24176, 24177, + 24178, 24179, 24180, 24181, 24182, 24183, 24184, 24185, + 24186, 24187, 24188, 24189, 24190, 24191, 24192, 24193, + 24194, 24195, 24196, 24197, 24198, 24199, 24200, 24201, + 24202, 24203, 24204, 24205, 24206, 24207, 24208, 24209, + 24210, 24211, 24212, 24213, 24214, 24215, 24216, 24217, + 24218, 24219, 24220, 24221, 24222, 24223, 24224, 24225, + 24226, 24227, 24228, 24229, 24230, 24231, 24232, 24233, + 24234, 24235, 24236, 24237, 24238, 24239, 24240, 24241, + 24242, 24243, 24244, 24245, 24246, 24247, 24248, 24249, + 24250, 24251, 24252, 24253, 24254, 24255, 24256, 24257, + 24258, 24259, 24260, 24261, 24262, 24263, 24264, 24265, + 24266, 24267, 24268, 24269, 24270, 24271, 24272, 24273, + 24274, 24275, 24276, 24277, 24278, 24279, 24280, 24281, + 24282, 24283, 24284, 24285, 24286, 24287, 24288, 24289, + 24290, 24291, 24292, 24293, 24294, 24295, 24296, 24297, + 24298, 24299, 24300, 24301, 24302, 24303, 24304, 24305, + 24306, 24307, 24308, 24309, 24310, 24311, 24312, 24313, + 24314, 24315, 24316, 24317, 24318, 24319, 24320, 24321, + 24322, 24323, 24324, 24325, 24326, 24327, 24328, 24329, + 24330, 24331, 24332, 24333, 24334, 24335, 24336, 24337, + 24338, 24339, 24340, 24341, 24342, 24343, 24344, 24345, + 24346, 24347, 24348, 24349, 24350, 24351, 24352, 24353, + 24354, 24355, 24356, 24357, 24358, 24359, 24360, 24361, + 24362, 24363, 24364, 24365, 24366, 24367, 24368, 24369, + 24370, 24371, 24372, 24373, 24374, 24375, 24376, 24377, + 24378, 24379, 24380, 24381, 24382, 24383, 24384, 24385, + 24386, 24387, 24388, 24389, 24390, 24391, 24392, 24393, + 24394, 24395, 24396, 24397, 24398, 24399, 24400, 24401, + 24402, 24403, 24404, 24405, 24406, 24407, 24408, 24409, + 24410, 24411, 24412, 24413, 24414, 24415, 24416, 24417, + 24418, 24419, 24420, 24421, 24422, 24423, 24424, 24425, + 24426, 24427, 24428, 24429, 24430, 24431, 24432, 24433, + 24434, 24435, 24436, 24437, 24438, 24439, 24440, 24441, + 24442, 24443, 24444, 24445, 24446, 24447, 24448, 24449, + 24450, 24451, 24452, 24453, 24454, 24455, 24456, 24457, + 24458, 24459, 24460, 24461, 24462, 24463, 24464, 24465, + 24466, 24467, 24468, 24469, 24470, 24471, 24472, 24473, + 24474, 24475, 24476, 24477, 24478, 24479, 24480, 24481, + 24482, 24483, 24484, 24485, 24486, 24487, 24488, 24489, + 24490, 24491, 24492, 24493, 24494, 24495, 24496, 24497, + 24498, 24499, 24500, 24501, 24502, 24503, 24504, 24505, + 24506, 24507, 24508, 24509, 24510, 24511, 24512, 24513, + 24514, 24515, 24516, 24517, 24518, 24519, 24520, 24521, + 24522, 24523, 24524, 24525, 24526, 24527, 24528, 24529, + 24530, 24531, 24532, 24533, 24534, 24535, 24536, 24537, + 24538, 24539, 24540, 24541, 24542, 24543, 24544, 24545, + 24546, 24547, 24548, 24549, 24550, 24551, 24552, 24553, + 24554, 24555, 24556, 24557, 24558, 24559, 24560, 24561, + 24562, 24563, 24564, 24565, 24566, 24567, 24568, 24569, + 24570, 24571, 24572, 24573, 24574, 24575, 24576, 24577, + 24578, 24579, 24580, 24581, 24582, 24583, 24584, 24585, + 24586, 24587, 24588, 24589, 24590, 24591, 24592, 24593, + 24594, 24595, 24596, 24597, 24598, 24599, 24600, 24601, + 24602, 24603, 24604, 24605, 24606, 24607, 24608, 24609, + 24610, 24611, 24612, 24613, 24614, 24615, 24616, 24617, + 24618, 24619, 24620, 24621, 24622, 24623, 24624, 24625, + 24626, 24627, 24628, 24629, 24630, 24631, 24632, 24633, + 24634, 24635, 24636, 24637, 24638, 24639, 24640, 24641, + 24642, 24643, 24644, 24645, 24646, 24647, 24648, 24649, + 24650, 24651, 24652, 24653, 24654, 24655, 24656, 24657, + 24658, 24659, 24660, 24661, 24662, 24663, 24664, 24665, + 24666, 24667, 24668, 24669, 24670, 24671, 24672, 24673, + 24674, 24675, 24676, 24677, 24678, 24679, 24680, 24681, + 24682, 24683, 24684, 24685, 24686, 24687, 24688, 24689, + 24690, 24691, 24692, 24693, 24694, 24695, 24696, 24697, + 24698, 24699, 24700, 24701, 24702, 24703, 24704, 24705, + 24706, 24707, 24708, 24709, 24710, 24711, 24712, 24713, + 24714, 24715, 24716, 24717, 24718, 24719, 24720, 24721, + 24722, 24723, 24724, 24725, 24726, 24727, 24728, 24729, + 24730, 24731, 24732, 24733, 24734, 24735, 24736, 24737, + 24738, 24739, 24740, 24741, 24742, 24743, 24744, 24745, + 24746, 24747, 24748, 24749, 24750, 24751, 24752, 24753, + 24754, 24755, 24756, 24757, 24758, 24759, 24760, 24761, + 24762, 24763, 24764, 24765, 24766, 24767, 24768, 24769, + 24770, 24771, 24772, 24773, 24774, 24775, 24776, 24777, + 24778, 24779, 24780, 24781, 24782, 24783, 24784, 24785, + 24786, 24787, 24788, 24789, 24790, 24791, 24792, 24793, + 24794, 24795, 24796, 24797, 24798, 24799, 24800, 24801, + 24802, 24803, 24804, 24805, 24806, 24807, 24808, 24809, + 24810, 24811, 24812, 24813, 24814, 24815, 24816, 24817, + 24818, 24819, 24820, 24821, 24822, 24823, 24824, 24825, + 24826, 24827, 24828, 24829, 24830, 24831, 24832, 24833, + 24834, 24835, 24836, 24837, 24838, 24839, 24840, 24841, + 24842, 24843, 24844, 24845, 24846, 24847, 24848, 24849, + 24850, 24851, 24852, 24853, 24854, 24855, 24856, 24857, + 24858, 24859, 24860, 24861, 24862, 24863, 24864, 24865, + 24866, 24867, 24868, 24869, 24870, 24871, 24872, 24873, + 24874, 24875, 24876, 24877, 24878, 24879, 24880, 24881, + 24882, 24883, 24884, 24885, 24886, 24887, 24888, 24889, + 24890, 24891, 24892, 24893, 24894, 24895, 24896, 24897, + 24898, 24899, 24900, 24901, 24902, 24903, 24904, 24905, + 24906, 24907, 24908, 24909, 24910, 24911, 24912, 24913, + 24914, 24915, 24916, 24917, 24918, 24919, 24920, 24921, + 24922, 24923, 24924, 24925, 24926, 24927, 24928, 24929, + 24930, 24931, 24932, 24933, 24934, 24935, 24936, 24937, + 24938, 24939, 24940, 24941, 24942, 24943, 24944, 24945, + 24946, 24947, 24948, 24949, 24950, 24951, 24952, 24953, + 24954, 24955, 24956, 24957, 24958, 24959, 24960, 24961, + 24962, 24963, 24964, 24965, 24966, 24967, 24968, 24969, + 24970, 24971, 24972, 24973, 24974, 24975, 24976, 24977, + 24978, 24979, 24980, 24981, 24982, 24983, 24984, 24985, + 24986, 24987, 24988, 24989, 24990, 24991, 24992, 24993, + 24994, 24995, 24996, 24997, 24998, 24999, 25000, 25001, + 25002, 25003, 25004, 25005, 25006, 25007, 25008, 25009, + 25010, 25011, 25012, 25013, 25014, 25015, 25016, 25017, + 25018, 25019, 25020, 25021, 25022, 25023, 25024, 25025, + 25026, 25027, 25028, 25029, 25030, 25031, 25032, 25033, + 25034, 25035, 25036, 25037, 25038, 25039, 25040, 25041, + 25042, 25043, 25044, 25045, 25046, 25047, 25048, 25049, + 25050, 25051, 25052, 25053, 25054, 25055, 25056, 25057, + 25058, 25059, 25060, 25061, 25062, 25063, 25064, 25065, + 25066, 25067, 25068, 25069, 25070, 25071, 25072, 25073, + 25074, 25075, 25076, 25077, 25078, 25079, 25080, 25081, + 25082, 25083, 25084, 25085, 25086, 25087, 25088, 25089, + 25090, 25091, 25092, 25093, 25094, 25095, 25096, 25097, + 25098, 25099, 25100, 25101, 25102, 25103, 25104, 25105, + 25106, 25107, 25108, 25109, 25110, 25111, 25112, 25113, + 25114, 25115, 25116, 25117, 25118, 25119, 25120, 25121, + 25122, 25123, 25124, 25125, 25126, 25127, 25128, 25129, + 25130, 25131, 25132, 25133, 25134, 25135, 25136, 25137, + 25138, 25139, 25140, 25141, 25142, 25143, 25144, 25145, + 25146, 25147, 25148, 25149, 25150, 25151, 25152, 25153, + 25154, 25155, 25156, 25157, 25158, 25159, 25160, 25161, + 25162, 25163, 25164, 25165, 25166, 25167, 25168, 25169, + 25170, 25171, 25172, 25173, 25174, 25175, 25176, 25177, + 25178, 25179, 25180, 25181, 25182, 25183, 25184, 25185, + 25186, 25187, 25188, 25189, 25190, 25191, 25192, 25193, + 25194, 25195, 25196, 25197, 25198, 25199, 25200, 25201, + 25202, 25203, 25204, 25205, 25206, 25207, 25208, 25209, + 25210, 25211, 25212, 25213, 25214, 25215, 25216, 25217, + 25218, 25219, 25220, 25221, 25222, 25223, 25224, 25225, + 25226, 25227, 25228, 25229, 25230, 25231, 25232, 25233, + 25234, 25235, 25236, 25237, 25238, 25239, 25240, 25241, + 25242, 25243, 25244, 25245, 25246, 25247, 25248, 25249, + 25250, 25251, 25252, 25253, 25254, 25255, 25256, 25257, + 25258, 25259, 25260, 25261, 25262, 25263, 25264, 25265, + 25266, 25267, 25268, 25269, 25270, 25271, 25272, 25273, + 25274, 25275, 25276, 25277, 25278, 25279, 25280, 25281, + 25282, 25283, 25284, 25285, 25286, 25287, 25288, 25289, + 25290, 25291, 25292, 25293, 25294, 25295, 25296, 25297, + 25298, 25299, 25300, 25301, 25302, 25303, 25304, 25305, + 25306, 25307, 25308, 25309, 25310, 25311, 25312, 25313, + 25314, 25315, 25316, 25317, 25318, 25319, 25320, 25321, + 25322, 25323, 25324, 25325, 25326, 25327, 25328, 25329, + 25330, 25331, 25332, 25333, 25334, 25335, 25336, 25337, + 25338, 25339, 25340, 25341, 25342, 25343, 25344, 25345, + 25346, 25347, 25348, 25349, 25350, 25351, 25352, 25353, + 25354, 25355, 25356, 25357, 25358, 25359, 25360, 25361, + 25362, 25363, 25364, 25365, 25366, 25367, 25368, 25369, + 25370, 25371, 25372, 25373, 25374, 25375, 25376, 25377, + 25378, 25379, 25380, 25381, 25382, 25383, 25384, 25385, + 25386, 25387, 25388, 25389, 25390, 25391, 25392, 25393, + 25394, 25395, 25396, 25397, 25398, 25399, 25400, 25401, + 25402, 25403, 25404, 25405, 25406, 25407, 25408, 25409, + 25410, 25411, 25412, 25413, 25414, 25415, 25416, 25417, + 25418, 25419, 25420, 25421, 25422, 25423, 25424, 25425, + 25426, 25427, 25428, 25429, 25430, 25431, 25432, 25433, + 25434, 25435, 25436, 25437, 25438, 25439, 25440, 25441, + 25442, 25443, 25444, 25445, 25446, 25447, 25448, 25449, + 25450, 25451, 25452, 25453, 25454, 25455, 25456, 25457, + 25458, 25459, 25460, 25461, 25462, 25463, 25464, 25465, + 25466, 25467, 25468, 25469, 25470, 25471, 25472, 25473, + 25474, 25475, 25476, 25477, 25478, 25479, 25480, 25481, + 25482, 25483, 25484, 25485, 25486, 25487, 25488, 25489, + 25490, 25491, 25492, 25493, 25494, 25495, 25496, 25497, + 25498, 25499, 25500, 25501, 25502, 25503, 25504, 25505, + 25506, 25507, 25508, 25509, 25510, 25511, 25512, 25513, + 25514, 25515, 25516, 25517, 25518, 25519, 25520, 25521, + 25522, 25523, 25524, 25525, 25526, 25527, 25528, 25529, + 25530, 25531, 25532, 25533, 25534, 25535, 25536, 25537, + 25538, 25539, 25540, 25541, 25542, 25543, 25544, 25545, + 25546, 25547, 25548, 25549, 25550, 25551, 25552, 25553, + 25554, 25555, 25556, 25557, 25558, 25559, 25560, 25561, + 25562, 25563, 25564, 25565, 25566, 25567, 25568, 25569, + 25570, 25571, 25572, 25573, 25574, 25575, 25576, 25577, + 25578, 25579, 25580, 25581, 25582, 25583, 25584, 25585, + 25586, 25587, 25588, 25589, 25590, 25591, 25592, 25593, + 25594, 25595, 25596, 25597, 25598, 25599, 25600, 25601, + 25602, 25603, 25604, 25605, 25606, 25607, 25608, 25609, + 25610, 25611, 25612, 25613, 25614, 25615, 25616, 25617, + 25618, 25619, 25620, 25621, 25622, 25623, 25624, 25625, + 25626, 25627, 25628, 25629, 25630, 25631, 25632, 25633, + 25634, 25635, 25636, 25637, 25638, 25639, 25640, 25641, + 25642, 25643, 25644, 25645, 25646, 25647, 25648, 25649, + 25650, 25651, 25652, 25653, 25654, 25655, 25656, 25657, + 25658, 25659, 25660, 25661, 25662, 25663, 25664, 25665, + 25666, 25667, 25668, 25669, 25670, 25671, 25672, 25673, + 25674, 25675, 25676, 25677, 25678, 25679, 25680, 25681, + 25682, 25683, 25684, 25685, 25686, 25687, 25688, 25689, + 25690, 25691, 25692, 25693, 25694, 25695, 25696, 25697, + 25698, 25699, 25700, 25701, 25702, 25703, 25704, 25705, + 25706, 25707, 25708, 25709, 25710, 25711, 25712, 25713, + 25714, 25715, 25716, 25717, 25718, 25719, 25720, 25721, + 25722, 25723, 25724, 25725, 25726, 25727, 25728, 25729, + 25730, 25731, 25732, 25733, 25734, 25735, 25736, 25737, + 25738, 25739, 25740, 25741, 25742, 25743, 25744, 25745, + 25746, 25747, 25748, 25749, 25750, 25751, 25752, 25753, + 25754, 25755, 25756, 25757, 25758, 25759, 25760, 25761, + 25762, 25763, 25764, 25765, 25766, 25767, 25768, 25769, + 25770, 25771, 25772, 25773, 25774, 25775, 25776, 25777, + 25778, 25779, 25780, 25781, 25782, 25783, 25784, 25785, + 25786, 25787, 25788, 25789, 25790, 25791, 25792, 25793, + 25794, 25795, 25796, 25797, 25798, 25799, 25800, 25801, + 25802, 25803, 25804, 25805, 25806, 25807, 25808, 25809, + 25810, 25811, 25812, 25813, 25814, 25815, 25816, 25817, + 25818, 25819, 25820, 25821, 25822, 25823, 25824, 25825, + 25826, 25827, 25828, 25829, 25830, 25831, 25832, 25833, + 25834, 25835, 25836, 25837, 25838, 25839, 25840, 25841, + 25842, 25843, 25844, 25845, 25846, 25847, 25848, 25849, + 25850, 25851, 25852, 25853, 25854, 25855, 25856, 25857, + 25858, 25859, 25860, 25861, 25862, 25863, 25864, 25865, + 25866, 25867, 25868, 25869, 25870, 25871, 25872, 25873, + 25874, 25875, 25876, 25877, 25878, 25879, 25880, 25881, + 25882, 25883, 25884, 25885, 25886, 25887, 25888, 25889, + 25890, 25891, 25892, 25893, 25894, 25895, 25896, 25897, + 25898, 25899, 25900, 25901, 25902, 25903, 25904, 25905, + 25906, 25907, 25908, 25909, 25910, 25911, 25912, 25913, + 25914, 25915, 25916, 25917, 25918, 25919, 25920, 25921, + 25922, 25923, 25924, 25925, 25926, 25927, 25928, 25929, + 25930, 25931, 25932, 25933, 25934, 25935, 25936, 25937, + 25938, 25939, 25940, 25941, 25942, 25943, 25944, 25945, + 25946, 25947, 25948, 25949, 25950, 25951, 25952, 25953, + 25954, 25955, 25956, 25957, 25958, 25959, 25960, 25961, + 25962, 25963, 25964, 25965, 25966, 25967, 25968, 25969, + 25970, 25971, 25972, 25973, 25974, 25975, 25976, 25977, + 25978, 25979, 25980, 25981, 25982, 25983, 25984, 25985, + 25986, 25987, 25988, 25989, 25990, 25991, 25992, 25993, + 25994, 25995, 25996, 25997, 25998, 25999, 26000, 26001, + 26002, 26003, 26004, 26005, 26006, 26007, 26008, 26009, + 26010, 26011, 26012, 26013, 26014, 26015, 26016, 26017, + 26018, 26019, 26020, 26021, 26022, 26023, 26024, 26025, + 26026, 26027, 26028, 26029, 26030, 26031, 26032, 26033, + 26034, 26035, 26036, 26037, 26038, 26039, 26040, 26041, + 26042, 26043, 26044, 26045, 26046, 26047, 26048, 26049, + 26050, 26051, 26052, 26053, 26054, 26055, 26056, 26057, + 26058, 26059, 26060, 26061, 26062, 26063, 26064, 26065, + 26066, 26067, 26068, 26069, 26070, 26071, 26072, 26073, + 26074, 26075, 26076, 26077, 26078, 26079, 26080, 26081, + 26082, 26083, 26084, 26085, 26086, 26087, 26088, 26089, + 26090, 26091, 26092, 26093, 26094, 26095, 26096, 26097, + 26098, 26099, 26100, 26101, 26102, 26103, 26104, 26105, + 26106, 26107, 26108, 26109, 26110, 26111, 26112, 26113, + 26114, 26115, 26116, 26117, 26118, 26119, 26120, 26121, + 26122, 26123, 26124, 26125, 26126, 26127, 26128, 26129, + 26130, 26131, 26132, 26133, 26134, 26135, 26136, 26137, + 26138, 26139, 26140, 26141, 26142, 26143, 26144, 26145, + 26146, 26147, 26148, 26149, 26150, 26151, 26152, 26153, + 26154, 26155, 26156, 26157, 26158, 26159, 26160, 26161, + 26162, 26163, 26164, 26165, 26166, 26167, 26168, 26169, + 26170, 26171, 26172, 26173, 26174, 26175, 26176, 26177, + 26178, 26179, 26180, 26181, 26182, 26183, 26184, 26185, + 26186, 26187, 26188, 26189, 26190, 26191, 26192, 26193, + 26194, 26195, 26196, 26197, 26198, 26199, 26200, 26201, + 26202, 26203, 26204, 26205, 26206, 26207, 26208, 26209, + 26210, 26211, 26212, 26213, 26214, 26215, 26216, 26217, + 26218, 26219, 26220, 26221, 26222, 26223, 26224, 26225, + 26226, 26227, 26228, 26229, 26230, 26231, 26232, 26233, + 26234, 26235, 26236, 26237, 26238, 26239, 26240, 26241, + 26242, 26243, 26244, 26245, 26246, 26247, 26248, 26249, + 26250, 26251, 26252, 26253, 26254, 26255, 26256, 26257, + 26258, 26259, 26260, 26261, 26262, 26263, 26264, 26265, + 26266, 26267, 26268, 26269, 26270, 26271, 26272, 26273, + 26274, 26275, 26276, 26277, 26278, 26279, 26280, 26281, + 26282, 26283, 26284, 26285, 26286, 26287, 26288, 26289, + 26290, 26291, 26292, 26293, 26294, 26295, 26296, 26297, + 26298, 26299, 26300, 26301, 26302, 26303, 26304, 26305, + 26306, 26307, 26308, 26309, 26310, 26311, 26312, 26313, + 26314, 26315, 26316, 26317, 26318, 26319, 26320, 26321, + 26322, 26323, 26324, 26325, 26326, 26327, 26328, 26329, + 26330, 26331, 26332, 26333, 26334, 26335, 26336, 26337, + 26338, 26339, 26340, 26341, 26342, 26343, 26344, 26345, + 26346, 26347, 26348, 26349, 26350, 26351, 26352, 26353, + 26354, 26355, 26356, 26357, 26358, 26359, 26360, 26361, + 26362, 26363, 26364, 26365, 26366, 26367, 26368, 26369, + 26370, 26371, 26372, 26373, 26374, 26375, 26376, 26377, + 26378, 26379, 26380, 26381, 26382, 26383, 26384, 26385, + 26386, 26387, 26388, 26389, 26390, 26391, 26392, 26393, + 26394, 26395, 26396, 26397, 26398, 26399, 26400, 26401, + 26402, 26403, 26404, 26405, 26406, 26407, 26408, 26409, + 26410, 26411, 26412, 26413, 26414, 26415, 26416, 26417, + 26418, 26419, 26420, 26421, 26422, 26423, 26424, 26425, + 26426, 26427, 26428, 26429, 26430, 26431, 26432, 26433, + 26434, 26435, 26436, 26437, 26438, 26439, 26440, 26441, + 26442, 26443, 26444, 26445, 26446, 26447, 26448, 26449, + 26450, 26451, 26452, 26453, 26454, 26455, 26456, 26457, + 26458, 26459, 26460, 26461, 26462, 26463, 26464, 26465, + 26466, 26467, 26468, 26469, 26470, 26471, 26472, 26473, + 26474, 26475, 26476, 26477, 26478, 26479, 26480, 26481, + 26482, 26483, 26484, 26485, 26486, 26487, 26488, 26489, + 26490, 26491, 26492, 26493, 26494, 26495, 26496, 26497, + 26498, 26499, 26500, 26501, 26502, 26503, 26504, 26505, + 26506, 26507, 26508, 26509, 26510, 26511, 26512, 26513, + 26514, 26515, 26516, 26517, 26518, 26519, 26520, 26521, + 26522, 26523, 26524, 26525, 26526, 26527, 26528, 26529, + 26530, 26531, 26532, 26533, 26534, 26535, 26536, 26537, + 26538, 26539, 26540, 26541, 26542, 26543, 26544, 26545, + 26546, 26547, 26548, 26549, 26550, 26551, 26552, 26553, + 26554, 26555, 26556, 26557, 26558, 26559, 26560, 26561, + 26562, 26563, 26564, 26565, 26566, 26567, 26568, 26569, + 26570, 26571, 26572, 26573, 26574, 26575, 26576, 26577, + 26578, 26579, 26580, 26581, 26582, 26583, 26584, 26585, + 26586, 26587, 26588, 26589, 26590, 26591, 26592, 26593, + 26594, 26595, 26596, 26597, 26598, 26599, 26600, 26601, + 26602, 26603, 26604, 26605, 26606, 26607, 26608, 26609, + 26610, 26611, 26612, 26613, 26614, 26615, 26616, 26617, + 26618, 26619, 26620, 26621, 26622, 26623, 26624, 26625, + 26626, 26627, 26628, 26629, 26630, 26631, 26632, 26633, + 26634, 26635, 26636, 26637, 26638, 26639, 26640, 26641, + 26642, 26643, 26644, 26645, 26646, 26647, 26648, 26649, + 26650, 26651, 26652, 26653, 26654, 26655, 26656, 26657, + 26658, 26659, 26660, 26661, 26662, 26663, 26664, 26665, + 26666, 26667, 26668, 26669, 26670, 26671, 26672, 26673, + 26674, 26675, 26676, 26677, 26678, 26679, 26680, 26681, + 26682, 26683, 26684, 26685, 26686, 26687, 26688, 26689, + 26690, 26691, 26692, 26693, 26694, 26695, 26696, 26697, + 26698, 26699, 26700, 26701, 26702, 26703, 26704, 26705, + 26706, 26707, 26708, 26709, 26710, 26711, 26712, 26713, + 26714, 26715, 26716, 26717, 26718, 26719, 26720, 26721, + 26722, 26723, 26724, 26725, 26726, 26727, 26728, 26729, + 26730, 26731, 26732, 26733, 26734, 26735, 26736, 26737, + 26738, 26739, 26740, 26741, 26742, 26743, 26744, 26745, + 26746, 26747, 26748, 26749, 26750, 26751, 26752, 26753, + 26754, 26755, 26756, 26757, 26758, 26759, 26760, 26761, + 26762, 26763, 26764, 26765, 26766, 26767, 26768, 26769, + 26770, 26771, 26772, 26773, 26774, 26775, 26776, 26777, + 26778, 26779, 26780, 26781, 26782, 26783, 26784, 26785, + 26786, 26787, 26788, 26789, 26790, 26791, 26792, 26793, + 26794, 26795, 26796, 26797, 26798, 26799, 26800, 26801, + 26802, 26803, 26804, 26805, 26806, 26807, 26808, 26809, + 26810, 26811, 26812, 26813, 26814, 26815, 26816, 26817, + 26818, 26819, 26820, 26821, 26822, 26823, 26824, 26825, + 26826, 26827, 26828, 26829, 26830, 26831, 26832, 26833, + 26834, 26835, 26836, 26837, 26838, 26839, 26840, 26841, + 26842, 26843, 26844, 26845, 26846, 26847, 26848, 26849, + 26850, 26851, 26852, 26853, 26854, 26855, 26856, 26857, + 26858, 26859, 26860, 26861, 26862, 26863, 26864, 26865, + 26866, 26867, 26868, 26869, 26870, 26871, 26872, 26873, + 26874, 26875, 26876, 26877, 26878, 26879, 26880, 26881, + 26882, 26883, 26884, 26885, 26886, 26887, 26888, 26889, + 26890, 26891, 26892, 26893, 26894, 26895, 26896, 26897, + 26898, 26899, 26900, 26901, 26902, 26903, 26904, 26905, + 26906, 26907, 26908, 26909, 26910, 26911, 26912, 26913, + 26914, 26915, 26916, 26917, 26918, 26919, 26920, 26921, + 26922, 26923, 26924, 26925, 26926, 26927, 26928, 26929, + 26930, 26931, 26932, 26933, 26934, 26935, 26936, 26937, + 26938, 26939, 26940, 26941, 26942, 26943, 26944, 26945, + 26946, 26947, 26948, 26949, 26950, 26951, 26952, 26953, + 26954, 26955, 26956, 26957, 26958, 26959, 26960, 26961, + 26962, 26963, 26964, 26965, 26966, 26967, 26968, 26969, + 26970, 26971, 26972, 26973, 26974, 26975, 26976, 26977, + 26978, 26979, 26980, 26981, 26982, 26983, 26984, 26985, + 26986, 26987, 26988, 26989, 26990, 26991, 26992, 26993, + 26994, 26995, 26996, 26997, 26998, 26999, 27000, 27001, + 27002, 27003, 27004, 27005, 27006, 27007, 27008, 27009, + 27010, 27011, 27012, 27013, 27014, 27015, 27016, 27017, + 27018, 27019, 27020, 27021, 27022, 27023, 27024, 27025, + 27026, 27027, 27028, 27029, 27030, 27031, 27032, 27033, + 27034, 27035, 27036, 27037, 27038, 27039, 27040, 27041, + 27042, 27043, 27044, 27045, 27046, 27047, 27048, 27049, + 27050, 27051, 27052, 27053, 27054, 27055, 27056, 27057, + 27058, 27059, 27060, 27061, 27062, 27063, 27064, 27065, + 27066, 27067, 27068, 27069, 27070, 27071, 27072, 27073, + 27074, 27075, 27076, 27077, 27078, 27079, 27080, 27081, + 27082, 27083, 27084, 27085, 27086, 27087, 27088, 27089, + 27090, 27091, 27092, 27093, 27094, 27095, 27096, 27097, + 27098, 27099, 27100, 27101, 27102, 27103, 27104, 27105, + 27106, 27107, 27108, 27109, 27110, 27111, 27112, 27113, + 27114, 27115, 27116, 27117, 27118, 27119, 27120, 27121, + 27122, 27123, 27124, 27125, 27126, 27127, 27128, 27129, + 27130, 27131, 27132, 27133, 27134, 27135, 27136, 27137, + 27138, 27139, 27140, 27141, 27142, 27143, 27144, 27145, + 27146, 27147, 27148, 27149, 27150, 27151, 27152, 27153, + 27154, 27155, 27156, 27157, 27158, 27159, 27160, 27161, + 27162, 27163, 27164, 27165, 27166, 27167, 27168, 27169, + 27170, 27171, 27172, 27173, 27174, 27175, 27176, 27177, + 27178, 27179, 27180, 27181, 27182, 27183, 27184, 27185, + 27186, 27187, 27188, 27189, 27190, 27191, 27192, 27193, + 27194, 27195, 27196, 27197, 27198, 27199, 27200, 27201, + 27202, 27203, 27204, 27205, 27206, 27207, 27208, 27209, + 27210, 27211, 27212, 27213, 27214, 27215, 27216, 27217, + 27218, 27219, 27220, 27221, 27222, 27223, 27224, 27225, + 27226, 27227, 27228, 27229, 27230, 27231, 27232, 27233, + 27234, 27235, 27236, 27237, 27238, 27239, 27240, 27241, + 27242, 27243, 27244, 27245, 27246, 27247, 27248, 27249, + 27250, 27251, 27252, 27253, 27254, 27255, 27256, 27257, + 27258, 27259, 27260, 27261, 27262, 27263, 27264, 27265, + 27266, 27267, 27268, 27269, 27270, 27271, 27272, 27273, + 27274, 27275, 27276, 27277, 27278, 27279, 27280, 27281, + 27282, 27283, 27284, 27285, 27286, 27287, 27288, 27289, + 27290, 27291, 27292, 27293, 27294, 27295, 27296, 27297, + 27298, 27299, 27300, 27301, 27302, 27303, 27304, 27305, + 27306, 27307, 27308, 27309, 27310, 27311, 27312, 27313, + 27314, 27315, 27316, 27317, 27318, 27319, 27320, 27321, + 27322, 27323, 27324, 27325, 27326, 27327, 27328, 27329, + 27330, 27331, 27332, 27333, 27334, 27335, 27336, 27337, + 27338, 27339, 27340, 27341, 27342, 27343, 27344, 27345, + 27346, 27347, 27348, 27349, 27350, 27351, 27352, 27353, + 27354, 27355, 27356, 27357, 27358, 27359, 27360, 27361, + 27362, 27363, 27364, 27365, 27366, 27367, 27368, 27369, + 27370, 27371, 27372, 27373, 27374, 27375, 27376, 27377, + 27378, 27379, 27380, 27381, 27382, 27383, 27384, 27385, + 27386, 27387, 27388, 27389, 27390, 27391, 27392, 27393, + 27394, 27395, 27396, 27397, 27398, 27399, 27400, 27401, + 27402, 27403, 27404, 27405, 27406, 27407, 27408, 27409, + 27410, 27411, 27412, 27413, 27414, 27415, 27416, 27417, + 27418, 27419, 27420, 27421, 27422, 27423, 27424, 27425, + 27426, 27427, 27428, 27429, 27430, 27431, 27432, 27433, + 27434, 27435, 27436, 27437, 27438, 27439, 27440, 27441, + 27442, 27443, 27444, 27445, 27446, 27447, 27448, 27449, + 27450, 27451, 27452, 27453, 27454, 27455, 27456, 27457, + 27458, 27459, 27460, 27461, 27462, 27463, 27464, 27465, + 27466, 27467, 27468, 27469, 27470, 27471, 27472, 27473, + 27474, 27475, 27476, 27477, 27478, 27479, 27480, 27481, + 27482, 27483, 27484, 27485, 27486, 27487, 27488, 27489, + 27490, 27491, 27492, 27493, 27494, 27495, 27496, 27497, + 27498, 27499, 27500, 27501, 27502, 27503, 27504, 27505, + 27506, 27507, 27508, 27509, 27510, 27511, 27512, 27513, + 27514, 27515, 27516, 27517, 27518, 27519, 27520, 27521, + 27522, 27523, 27524, 27525, 27526, 27527, 27528, 27529, + 27530, 27531, 27532, 27533, 27534, 27535, 27536, 27537, + 27538, 27539, 27540, 27541, 27542, 27543, 27544, 27545, + 27546, 27547, 27548, 27549, 27550, 27551, 27552, 27553, + 27554, 27555, 27556, 27557, 27558, 27559, 27560, 27561, + 27562, 27563, 27564, 27565, 27566, 27567, 27568, 27569, + 27570, 27571, 27572, 27573, 27574, 27575, 27576, 27577, + 27578, 27579, 27580, 27581, 27582, 27583, 27584, 27585, + 27586, 27587, 27588, 27589, 27590, 27591, 27592, 27593, + 27594, 27595, 27596, 27597, 27598, 27599, 27600, 27601, + 27602, 27603, 27604, 27605, 27606, 27607, 27608, 27609, + 27610, 27611, 27612, 27613, 27614, 27615, 27616, 27617, + 27618, 27619, 27620, 27621, 27622, 27623, 27624, 27625, + 27626, 27627, 27628, 27629, 27630, 27631, 27632, 27633, + 27634, 27635, 27636, 27637, 27638, 27639, 27640, 27641, + 27642, 27643, 27644, 27645, 27646, 27647, 27648, 27649, + 27650, 27651, 27652, 27653, 27654, 27655, 27656, 27657, + 27658, 27659, 27660, 27661, 27662, 27663, 27664, 27665, + 27666, 27667, 27668, 27669, 27670, 27671, 27672, 27673, + 27674, 27675, 27676, 27677, 27678, 27679, 27680, 27681, + 27682, 27683, 27684, 27685, 27686, 27687, 27688, 27689, + 27690, 27691, 27692, 27693, 27694, 27695, 27696, 27697, + 27698, 27699, 27700, 27701, 27702, 27703, 27704, 27705, + 27706, 27707, 27708, 27709, 27710, 27711, 27712, 27713, + 27714, 27715, 27716, 27717, 27718, 27719, 27720, 27721, + 27722, 27723, 27724, 27725, 27726, 27727, 27728, 27729, + 27730, 27731, 27732, 27733, 27734, 27735, 27736, 27737, + 27738, 27739, 27740, 27741, 27742, 27743, 27744, 27745, + 27746, 27747, 27748, 27749, 27750, 27751, 27752, 27753, + 27754, 27755, 27756, 27757, 27758, 27759, 27760, 27761, + 27762, 27763, 27764, 27765, 27766, 27767, 27768, 27769, + 27770, 27771, 27772, 27773, 27774, 27775, 27776, 27777, + 27778, 27779, 27780, 27781, 27782, 27783, 27784, 27785, + 27786, 27787, 27788, 27789, 27790, 27791, 27792, 27793, + 27794, 27795, 27796, 27797, 27798, 27799, 27800, 27801, + 27802, 27803, 27804, 27805, 27806, 27807, 27808, 27809, + 27810, 27811, 27812, 27813, 27814, 27815, 27816, 27817, + 27818, 27819, 27820, 27821, 27822, 27823, 27824, 27825, + 27826, 27827, 27828, 27829, 27830, 27831, 27832, 27833, + 27834, 27835, 27836, 27837, 27838, 27839, 27840, 27841, + 27842, 27843, 27844, 27845, 27846, 27847, 27848, 27849, + 27850, 27851, 27852, 27853, 27854, 27855, 27856, 27857, + 27858, 27859, 27860, 27861, 27862, 27863, 27864, 27865, + 27866, 27867, 27868, 27869, 27870, 27871, 27872, 27873, + 27874, 27875, 27876, 27877, 27878, 27879, 27880, 27881, + 27882, 27883, 27884, 27885, 27886, 27887, 27888, 27889, + 27890, 27891, 27892, 27893, 27894, 27895, 27896, 27897, + 27898, 27899, 27900, 27901, 27902, 27903, 27904, 27905, + 27906, 27907, 27908, 27909, 27910, 27911, 27912, 27913, + 27914, 27915, 27916, 27917, 27918, 27919, 27920, 27921, + 27922, 27923, 27924, 27925, 27926, 27927, 27928, 27929, + 27930, 27931, 27932, 27933, 27934, 27935, 27936, 27937, + 27938, 27939, 27940, 27941, 27942, 27943, 27944, 27945, + 27946, 27947, 27948, 27949, 27950, 27951, 27952, 27953, + 27954, 27955, 27956, 27957, 27958, 27959, 27960, 27961, + 27962, 27963, 27964, 27965, 27966, 27967, 27968, 27969, + 27970, 27971, 27972, 27973, 27974, 27975, 27976, 27977, + 27978, 27979, 27980, 27981, 27982, 27983, 27984, 27985, + 27986, 27987, 27988, 27989, 27990, 27991, 27992, 27993, + 27994, 27995, 27996, 27997, 27998, 27999, 28000, 28001, + 28002, 28003, 28004, 28005, 28006, 28007, 28008, 28009, + 28010, 28011, 28012, 28013, 28014, 28015, 28016, 28017, + 28018, 28019, 28020, 28021, 28022, 28023, 28024, 28025, + 28026, 28027, 28028, 28029, 28030, 28031, 28032, 28033, + 28034, 28035, 28036, 28037, 28038, 28039, 28040, 28041, + 28042, 28043, 28044, 28045, 28046, 28047, 28048, 28049, + 28050, 28051, 28052, 28053, 28054, 28055, 28056, 28057, + 28058, 28059, 28060, 28061, 28062, 28063, 28064, 28065, + 28066, 28067, 28068, 28069, 28070, 28071, 28072, 28073, + 28074, 28075, 28076, 28077, 28078, 28079, 28080, 28081, + 28082, 28083, 28084, 28085, 28086, 28087, 28088, 28089, + 28090, 28091, 28092, 28093, 28094, 28095, 28096, 28097, + 28098, 28099, 28100, 28101, 28102, 28103, 28104, 28105, + 28106, 28107, 28108, 28109, 28110, 28111, 28112, 28113, + 28114, 28115, 28116, 28117, 28118, 28119, 28120, 28121, + 28122, 28123, 28124, 28125, 28126, 28127, 28128, 28129, + 28130, 28131, 28132, 28133, 28134, 28135, 28136, 28137, + 28138, 28139, 28140, 28141, 28142, 28143, 28144, 28145, + 28146, 28147, 28148, 28149, 28150, 28151, 28152, 28153, + 28154, 28155, 28156, 28157, 28158, 28159, 28160, 28161, + 28162, 28163, 28164, 28165, 28166, 28167, 28168, 28169, + 28170, 28171, 28172, 28173, 28174, 28175, 28176, 28177, + 28178, 28179, 28180, 28181, 28182, 28183, 28184, 28185, + 28186, 28187, 28188, 28189, 28190, 28191, 28192, 28193, + 28194, 28195, 28196, 28197, 28198, 28199, 28200, 28201, + 28202, 28203, 28204, 28205, 28206, 28207, 28208, 28209, + 28210, 28211, 28212, 28213, 28214, 28215, 28216, 28217, + 28218, 28219, 28220, 28221, 28222, 28223, 28224, 28225, + 28226, 28227, 28228, 28229, 28230, 28231, 28232, 28233, + 28234, 28235, 28236, 28237, 28238, 28239, 28240, 28241, + 28242, 28243, 28244, 28245, 28246, 28247, 28248, 28249, + 28250, 28251, 28252, 28253, 28254, 28255, 28256, 28257, + 28258, 28259, 28260, 28261, 28262, 28263, 28264, 28265, + 28266, 28267, 28268, 28269, 28270, 28271, 28272, 28273, + 28274, 28275, 28276, 28277, 28278, 28279, 28280, 28281, + 28282, 28283, 28284, 28285, 28286, 28287, 28288, 28289, + 28290, 28291, 28292, 28293, 28294, 28295, 28296, 28297, + 28298, 28299, 28300, 28301, 28302, 28303, 28304, 28305, + 28306, 28307, 28308, 28309, 28310, 28311, 28312, 28313, + 28314, 28315, 28316, 28317, 28318, 28319, 28320, 28321, + 28322, 28323, 28324, 28325, 28326, 28327, 28328, 28329, + 28330, 28331, 28332, 28333, 28334, 28335, 28336, 28337, + 28338, 28339, 28340, 28341, 28342, 28343, 28344, 28345, + 28346, 28347, 28348, 28349, 28350, 28351, 28352, 28353, + 28354, 28355, 28356, 28357, 28358, 28359, 28360, 28361, + 28362, 28363, 28364, 28365, 28366, 28367, 28368, 28369, + 28370, 28371, 28372, 28373, 28374, 28375, 28376, 28377, + 28378, 28379, 28380, 28381, 28382, 28383, 28384, 28385, + 28386, 28387, 28388, 28389, 28390, 28391, 28392, 28393, + 28394, 28395, 28396, 28397, 28398, 28399, 28400, 28401, + 28402, 28403, 28404, 28405, 28406, 28407, 28408, 28409, + 28410, 28411, 28412, 28413, 28414, 28415, 28416, 28417, + 28418, 28419, 28420, 28421, 28422, 28423, 28424, 28425, + 28426, 28427, 28428, 28429, 28430, 28431, 28432, 28433, + 28434, 28435, 28436, 28437, 28438, 28439, 28440, 28441, + 28442, 28443, 28444, 28445, 28446, 28447, 28448, 28449, + 28450, 28451, 28452, 28453, 28454, 28455, 28456, 28457, + 28458, 28459, 28460, 28461, 28462, 28463, 28464, 28465, + 28466, 28467, 28468, 28469, 28470, 28471, 28472, 28473, + 28474, 28475, 28476, 28477, 28478, 28479, 28480, 28481, + 28482, 28483, 28484, 28485, 28486, 28487, 28488, 28489, + 28490, 28491, 28492, 28493, 28494, 28495, 28496, 28497, + 28498, 28499, 28500, 28501, 28502, 28503, 28504, 28505, + 28506, 28507, 28508, 28509, 28510, 28511, 28512, 28513, + 28514, 28515, 28516, 28517, 28518, 28519, 28520, 28521, + 28522, 28523, 28524, 28525, 28526, 28527, 28528, 28529, + 28530, 28531, 28532, 28533, 28534, 28535, 28536, 28537, + 28538, 28539, 28540, 28541, 28542, 28543, 28544, 28545, + 28546, 28547, 28548, 28549, 28550, 28551, 28552, 28553, + 28554, 28555, 28556, 28557, 28558, 28559, 28560, 28561, + 28562, 28563, 28564, 28565, 28566, 28567, 28568, 28569, + 28570, 28571, 28572, 28573, 28574, 28575, 28576, 28577, + 28578, 28579, 28580, 28581, 28582, 28583, 28584, 28585, + 28586, 28587, 28588, 28589, 28590, 28591, 28592, 28593, + 28594, 28595, 28596, 28597, 28598, 28599, 28600, 28601, + 28602, 28603, 28604, 28605, 28606, 28607, 28608, 28609, + 28610, 28611, 28612, 28613, 28614, 28615, 28616, 28617, + 28618, 28619, 28620, 28621, 28622, 28623, 28624, 28625, + 28626, 28627, 28628, 28629, 28630, 28631, 28632, 28633, + 28634, 28635, 28636, 28637, 28638, 28639, 28640, 28641, + 28642, 28643, 28644, 28645, 28646, 28647, 28648, 28649, + 28650, 28651, 28652, 28653, 28654, 28655, 28656, 28657, + 28658, 28659, 28660, 28661, 28662, 28663, 28664, 28665, + 28666, 28667, 28668, 28669, 28670, 28671, 28672, 28673, + 28674, 28675, 28676, 28677, 28678, 28679, 28680, 28681, + 28682, 28683, 28684, 28685, 28686, 28687, 28688, 28689, + 28690, 28691, 28692, 28693, 28694, 28695, 28696, 28697, + 28698, 28699, 28700, 28701, 28702, 28703, 28704, 28705, + 28706, 28707, 28708, 28709, 28710, 28711, 28712, 28713, + 28714, 28715, 28716, 28717, 28718, 28719, 28720, 28721, + 28722, 28723, 28724, 28725, 28726, 28727, 28728, 28729, + 28730, 28731, 28732, 28733, 28734, 28735, 28736, 28737, + 28738, 28739, 28740, 28741, 28742, 28743, 28744, 28745, + 28746, 28747, 28748, 28749, 28750, 28751, 28752, 28753, + 28754, 28755, 28756, 28757, 28758, 28759, 28760, 28761, + 28762, 28763, 28764, 28765, 28766, 28767, 28768, 28769, + 28770, 28771, 28772, 28773, 28774, 28775, 28776, 28777, + 28778, 28779, 28780, 28781, 28782, 28783, 28784, 28785, + 28786, 28787, 28788, 28789, 28790, 28791, 28792, 28793, + 28794, 28795, 28796, 28797, 28798, 28799, 28800, 28801, + 28802, 28803, 28804, 28805, 28806, 28807, 28808, 28809, + 28810, 28811, 28812, 28813, 28814, 28815, 28816, 28817, + 28818, 28819, 28820, 28821, 28822, 28823, 28824, 28825, + 28826, 28827, 28828, 28829, 28830, 28831, 28832, 28833, + 28834, 28835, 28836, 28837, 28838, 28839, 28840, 28841, + 28842, 28843, 28844, 28845, 28846, 28847, 28848, 28849, + 28850, 28851, 28852, 28853, 28854, 28855, 28856, 28857, + 28858, 28859, 28860, 28861, 28862, 28863, 28864, 28865, + 28866, 28867, 28868, 28869, 28870, 28871, 28872, 28873, + 28874, 28875, 28876, 28877, 28878, 28879, 28880, 28881, + 28882, 28883, 28884, 28885, 28886, 28887, 28888, 28889, + 28890, 28891, 28892, 28893, 28894, 28895, 28896, 28897, + 28898, 28899, 28900, 28901, 28902, 28903, 28904, 28905, + 28906, 28907, 28908, 28909, 28910, 28911, 28912, 28913, + 28914, 28915, 28916, 28917, 28918, 28919, 28920, 28921, + 28922, 28923, 28924, 28925, 28926, 28927, 28928, 28929, + 28930, 28931, 28932, 28933, 28934, 28935, 28936, 28937, + 28938, 28939, 28940, 28941, 28942, 28943, 28944, 28945, + 28946, 28947, 28948, 28949, 28950, 28951, 28952, 28953, + 28954, 28955, 28956, 28957, 28958, 28959, 28960, 28961, + 28962, 28963, 28964, 28965, 28966, 28967, 28968, 28969, + 28970, 28971, 28972, 28973, 28974, 28975, 28976, 28977, + 28978, 28979, 28980, 28981, 28982, 28983, 28984, 28985, + 28986, 28987, 28988, 28989, 28990, 28991, 28992, 28993, + 28994, 28995, 28996, 28997, 28998, 28999, 29000, 29001, + 29002, 29003, 29004, 29005, 29006, 29007, 29008, 29009, + 29010, 29011, 29012, 29013, 29014, 29015, 29016, 29017, + 29018, 29019, 29020, 29021, 29022, 29023, 29024, 29025, + 29026, 29027, 29028, 29029, 29030, 29031, 29032, 29033, + 29034, 29035, 29036, 29037, 29038, 29039, 29040, 29041, + 29042, 29043, 29044, 29045, 29046, 29047, 29048, 29049, + 29050, 29051, 29052, 29053, 29054, 29055, 29056, 29057, + 29058, 29059, 29060, 29061, 29062, 29063, 29064, 29065, + 29066, 29067, 29068, 29069, 29070, 29071, 29072, 29073, + 29074, 29075, 29076, 29077, 29078, 29079, 29080, 29081, + 29082, 29083, 29084, 29085, 29086, 29087, 29088, 29089, + 29090, 29091, 29092, 29093, 29094, 29095, 29096, 29097, + 29098, 29099, 29100, 29101, 29102, 29103, 29104, 29105, + 29106, 29107, 29108, 29109, 29110, 29111, 29112, 29113, + 29114, 29115, 29116, 29117, 29118, 29119, 29120, 29121, + 29122, 29123, 29124, 29125, 29126, 29127, 29128, 29129, + 29130, 29131, 29132, 29133, 29134, 29135, 29136, 29137, + 29138, 29139, 29140, 29141, 29142, 29143, 29144, 29145, + 29146, 29147, 29148, 29149, 29150, 29151, 29152, 29153, + 29154, 29155, 29156, 29157, 29158, 29159, 29160, 29161, + 29162, 29163, 29164, 29165, 29166, 29167, 29168, 29169, + 29170, 29171, 29172, 29173, 29174, 29175, 29176, 29177, + 29178, 29179, 29180, 29181, 29182, 29183, 29184, 29185, + 29186, 29187, 29188, 29189, 29190, 29191, 29192, 29193, + 29194, 29195, 29196, 29197, 29198, 29199, 29200, 29201, + 29202, 29203, 29204, 29205, 29206, 29207, 29208, 29209, + 29210, 29211, 29212, 29213, 29214, 29215, 29216, 29217, + 29218, 29219, 29220, 29221, 29222, 29223, 29224, 29225, + 29226, 29227, 29228, 29229, 29230, 29231, 29232, 29233, + 29234, 29235, 29236, 29237, 29238, 29239, 29240, 29241, + 29242, 29243, 29244, 29245, 29246, 29247, 29248, 29249, + 29250, 29251, 29252, 29253, 29254, 29255, 29256, 29257, + 29258, 29259, 29260, 29261, 29262, 29263, 29264, 29265, + 29266, 29267, 29268, 29269, 29270, 29271, 29272, 29273, + 29274, 29275, 29276, 29277, 29278, 29279, 29280, 29281, + 29282, 29283, 29284, 29285, 29286, 29287, 29288, 29289, + 29290, 29291, 29292, 29293, 29294, 29295, 29296, 29297, + 29298, 29299, 29300, 29301, 29302, 29303, 29304, 29305, + 29306, 29307, 29308, 29309, 29310, 29311, 29312, 29313, + 29314, 29315, 29316, 29317, 29318, 29319, 29320, 29321, + 29322, 29323, 29324, 29325, 29326, 29327, 29328, 29329, + 29330, 29331, 29332, 29333, 29334, 29335, 29336, 29337, + 29338, 29339, 29340, 29341, 29342, 29343, 29344, 29345, + 29346, 29347, 29348, 29349, 29350, 29351, 29352, 29353, + 29354, 29355, 29356, 29357, 29358, 29359, 29360, 29361, + 29362, 29363, 29364, 29365, 29366, 29367, 29368, 29369, + 29370, 29371, 29372, 29373, 29374, 29375, 29376, 29377, + 29378, 29379, 29380, 29381, 29382, 29383, 29384, 29385, + 29386, 29387, 29388, 29389, 29390, 29391, 29392, 29393, + 29394, 29395, 29396, 29397, 29398, 29399, 29400, 29401, + 29402, 29403, 29404, 29405, 29406, 29407, 29408, 29409, + 29410, 29411, 29412, 29413, 29414, 29415, 29416, 29417, + 29418, 29419, 29420, 29421, 29422, 29423, 29424, 29425, + 29426, 29427, 29428, 29429, 29430, 29431, 29432, 29433, + 29434, 29435, 29436, 29437, 29438, 29439, 29440, 29441, + 29442, 29443, 29444, 29445, 29446, 29447, 29448, 29449, + 29450, 29451, 29452, 29453, 29454, 29455, 29456, 29457, + 29458, 29459, 29460, 29461, 29462, 29463, 29464, 29465, + 29466, 29467, 29468, 29469, 29470, 29471, 29472, 29473, + 29474, 29475, 29476, 29477, 29478, 29479, 29480, 29481, + 29482, 29483, 29484, 29485, 29486, 29487, 29488, 29489, + 29490, 29491, 29492, 29493, 29494, 29495, 29496, 29497, + 29498, 29499, 29500, 29501, 29502, 29503, 29504, 29505, + 29506, 29507, 29508, 29509, 29510, 29511, 29512, 29513, + 29514, 29515, 29516, 29517, 29518, 29519, 29520, 29521, + 29522, 29523, 29524, 29525, 29526, 29527, 29528, 29529, + 29530, 29531, 29532, 29533, 29534, 29535, 29536, 29537, + 29538, 29539, 29540, 29541, 29542, 29543, 29544, 29545, + 29546, 29547, 29548, 29549, 29550, 29551, 29552, 29553, + 29554, 29555, 29556, 29557, 29558, 29559, 29560, 29561, + 29562, 29563, 29564, 29565, 29566, 29567, 29568, 29569, + 29570, 29571, 29572, 29573, 29574, 29575, 29576, 29577, + 29578, 29579, 29580, 29581, 29582, 29583, 29584, 29585, + 29586, 29587, 29588, 29589, 29590, 29591, 29592, 29593, + 29594, 29595, 29596, 29597, 29598, 29599, 29600, 29601, + 29602, 29603, 29604, 29605, 29606, 29607, 29608, 29609, + 29610, 29611, 29612, 29613, 29614, 29615, 29616, 29617, + 29618, 29619, 29620, 29621, 29622, 29623, 29624, 29625, + 29626, 29627, 29628, 29629, 29630, 29631, 29632, 29633, + 29634, 29635, 29636, 29637, 29638, 29639, 29640, 29641, + 29642, 29643, 29644, 29645, 29646, 29647, 29648, 29649, + 29650, 29651, 29652, 29653, 29654, 29655, 29656, 29657, + 29658, 29659, 29660, 29661, 29662, 29663, 29664, 29665, + 29666, 29667, 29668, 29669, 29670, 29671, 29672, 29673, + 29674, 29675, 29676, 29677, 29678, 29679, 29680, 29681, + 29682, 29683, 29684, 29685, 29686, 29687, 29688, 29689, + 29690, 29691, 29692, 29693, 29694, 29695, 29696, 29697, + 29698, 29699, 29700, 29701, 29702, 29703, 29704, 29705, + 29706, 29707, 29708, 29709, 29710, 29711, 29712, 29713, + 29714, 29715, 29716, 29717, 29718, 29719, 29720, 29721, + 29722, 29723, 29724, 29725, 29726, 29727, 29728, 29729, + 29730, 29731, 29732, 29733, 29734, 29735, 29736, 29737, + 29738, 29739, 29740, 29741, 29742, 29743, 29744, 29745, + 29746, 29747, 29748, 29749, 29750, 29751, 29752, 29753, + 29754, 29755, 29756, 29757, 29758, 29759, 29760, 29761, + 29762, 29763, 29764, 29765, 29766, 29767, 29768, 29769, + 29770, 29771, 29772, 29773, 29774, 29775, 29776, 29777, + 29778, 29779, 29780, 29781, 29782, 29783, 29784, 29785, + 29786, 29787, 29788, 29789, 29790, 29791, 29792, 29793, + 29794, 29795, 29796, 29797, 29798, 29799, 29800, 29801, + 29802, 29803, 29804, 29805, 29806, 29807, 29808, 29809, + 29810, 29811, 29812, 29813, 29814, 29815, 29816, 29817, + 29818, 29819, 29820, 29821, 29822, 29823, 29824, 29825, + 29826, 29827, 29828, 29829, 29830, 29831, 29832, 29833, + 29834, 29835, 29836, 29837, 29838, 29839, 29840, 29841, + 29842, 29843, 29844, 29845, 29846, 29847, 29848, 29849, + 29850, 29851, 29852, 29853, 29854, 29855, 29856, 29857, + 29858, 29859, 29860, 29861, 29862, 29863, 29864, 29865, + 29866, 29867, 29868, 29869, 29870, 29871, 29872, 29873, + 29874, 29875, 29876, 29877, 29878, 29879, 29880, 29881, + 29882, 29883, 29884, 29885, 29886, 29887, 29888, 29889, + 29890, 29891, 29892, 29893, 29894, 29895, 29896, 29897, + 29898, 29899, 29900, 29901, 29902, 29903, 29904, 29905, + 29906, 29907, 29908, 29909, 29910, 29911, 29912, 29913, + 29914, 29915, 29916, 29917, 29918, 29919, 29920, 29921, + 29922, 29923, 29924, 29925, 29926, 29927, 29928, 29929, + 29930, 29931, 29932, 29933, 29934, 29935, 29936, 29937, + 29938, 29939, 29940, 29941, 29942, 29943, 29944, 29945, + 29946, 29947, 29948, 29949, 29950, 29951, 29952, 29953, + 29954, 29955, 29956, 29957, 29958, 29959, 29960, 29961, + 29962, 29963, 29964, 29965, 29966, 29967, 29968, 29969, + 29970, 29971, 29972, 29973, 29974, 29975, 29976, 29977, + 29978, 29979, 29980, 29981, 29982, 29983, 29984, 29985, + 29986, 29987, 29988, 29989, 29990, 29991, 29992, 29993, + 29994, 29995, 29996, 29997, 29998, 29999, 30000, 30001, + 30002, 30003, 30004, 30005, 30006, 30007, 30008, 30009, + 30010, 30011, 30012, 30013, 30014, 30015, 30016, 30017, + 30018, 30019, 30020, 30021, 30022, 30023, 30024, 30025, + 30026, 30027, 30028, 30029, 30030, 30031, 30032, 30033, + 30034, 30035, 30036, 30037, 30038, 30039, 30040, 30041, + 30042, 30043, 30044, 30045, 30046, 30047, 30048, 30049, + 30050, 30051, 30052, 30053, 30054, 30055, 30056, 30057, + 30058, 30059, 30060, 30061, 30062, 30063, 30064, 30065, + 30066, 30067, 30068, 30069, 30070, 30071, 30072, 30073, + 30074, 30075, 30076, 30077, 30078, 30079, 30080, 30081, + 30082, 30083, 30084, 30085, 30086, 30087, 30088, 30089, + 30090, 30091, 30092, 30093, 30094, 30095, 30096, 30097, + 30098, 30099, 30100, 30101, 30102, 30103, 30104, 30105, + 30106, 30107, 30108, 30109, 30110, 30111, 30112, 30113, + 30114, 30115, 30116, 30117, 30118, 30119, 30120, 30121, + 30122, 30123, 30124, 30125, 30126, 30127, 30128, 30129, + 30130, 30131, 30132, 30133, 30134, 30135, 30136, 30137, + 30138, 30139, 30140, 30141, 30142, 30143, 30144, 30145, + 30146, 30147, 30148, 30149, 30150, 30151, 30152, 30153, + 30154, 30155, 30156, 30157, 30158, 30159, 30160, 30161, + 30162, 30163, 30164, 30165, 30166, 30167, 30168, 30169, + 30170, 30171, 30172, 30173, 30174, 30175, 30176, 30177, + 30178, 30179, 30180, 30181, 30182, 30183, 30184, 30185, + 30186, 30187, 30188, 30189, 30190, 30191, 30192, 30193, + 30194, 30195, 30196, 30197, 30198, 30199, 30200, 30201, + 30202, 30203, 30204, 30205, 30206, 30207, 30208, 30209, + 30210, 30211, 30212, 30213, 30214, 30215, 30216, 30217, + 30218, 30219, 30220, 30221, 30222, 30223, 30224, 30225, + 30226, 30227, 30228, 30229, 30230, 30231, 30232, 30233, + 30234, 30235, 30236, 30237, 30238, 30239, 30240, 30241, + 30242, 30243, 30244, 30245, 30246, 30247, 30248, 30249, + 30250, 30251, 30252, 30253, 30254, 30255, 30256, 30257, + 30258, 30259, 30260, 30261, 30262, 30263, 30264, 30265, + 30266, 30267, 30268, 30269, 30270, 30271, 30272, 30273, + 30274, 30275, 30276, 30277, 30278, 30279, 30280, 30281, + 30282, 30283, 30284, 30285, 30286, 30287, 30288, 30289, + 30290, 30291, 30292, 30293, 30294, 30295, 30296, 30297, + 30298, 30299, 30300, 30301, 30302, 30303, 30304, 30305, + 30306, 30307, 30308, 30309, 30310, 30311, 30312, 30313, + 30314, 30315, 30316, 30317, 30318, 30319, 30320, 30321, + 30322, 30323, 30324, 30325, 30326, 30327, 30328, 30329, + 30330, 30331, 30332, 30333, 30334, 30335, 30336, 30337, + 30338, 30339, 30340, 30341, 30342, 30343, 30344, 30345, + 30346, 30347, 30348, 30349, 30350, 30351, 30352, 30353, + 30354, 30355, 30356, 30357, 30358, 30359, 30360, 30361, + 30362, 30363, 30364, 30365, 30366, 30367, 30368, 30369, + 30370, 30371, 30372, 30373, 30374, 30375, 30376, 30377, + 30378, 30379, 30380, 30381, 30382, 30383, 30384, 30385, + 30386, 30387, 30388, 30389, 30390, 30391, 30392, 30393, + 30394, 30395, 30396, 30397, 30398, 30399, 30400, 30401, + 30402, 30403, 30404, 30405, 30406, 30407, 30408, 30409, + 30410, 30411, 30412, 30413, 30414, 30415, 30416, 30417, + 30418, 30419, 30420, 30421, 30422, 30423, 30424, 30425, + 30426, 30427, 30428, 30429, 30430, 30431, 30432, 30433, + 30434, 30435, 30436, 30437, 30438, 30439, 30440, 30441, + 30442, 30443, 30444, 30445, 30446, 30447, 30448, 30449, + 30450, 30451, 30452, 30453, 30454, 30455, 30456, 30457, + 30458, 30459, 30460, 30461, 30462, 30463, 30464, 30465, + 30466, 30467, 30468, 30469, 30470, 30471, 30472, 30473, + 30474, 30475, 30476, 30477, 30478, 30479, 30480, 30481, + 30482, 30483, 30484, 30485, 30486, 30487, 30488, 30489, + 30490, 30491, 30492, 30493, 30494, 30495, 30496, 30497, + 30498, 30499, 30500, 30501, 30502, 30503, 30504, 30505, + 30506, 30507, 30508, 30509, 30510, 30511, 30512, 30513, + 30514, 30515, 30516, 30517, 30518, 30519, 30520, 30521, + 30522, 30523, 30524, 30525, 30526, 30527, 30528, 30529, + 30530, 30531, 30532, 30533, 30534, 30535, 30536, 30537, + 30538, 30539, 30540, 30541, 30542, 30543, 30544, 30545, + 30546, 30547, 30548, 30549, 30550, 30551, 30552, 30553, + 30554, 30555, 30556, 30557, 30558, 30559, 30560, 30561, + 30562, 30563, 30564, 30565, 30566, 30567, 30568, 30569, + 30570, 30571, 30572, 30573, 30574, 30575, 30576, 30577, + 30578, 30579, 30580, 30581, 30582, 30583, 30584, 30585, + 30586, 30587, 30588, 30589, 30590, 30591, 30592, 30593, + 30594, 30595, 30596, 30597, 30598, 30599, 30600, 30601, + 30602, 30603, 30604, 30605, 30606, 30607, 30608, 30609, + 30610, 30611, 30612, 30613, 30614, 30615, 30616, 30617, + 30618, 30619, 30620, 30621, 30622, 30623, 30624, 30625, + 30626, 30627, 30628, 30629, 30630, 30631, 30632, 30633, + 30634, 30635, 30636, 30637, 30638, 30639, 30640, 30641, + 30642, 30643, 30644, 30645, 30646, 30647, 30648, 30649, + 30650, 30651, 30652, 30653, 30654, 30655, 30656, 30657, + 30658, 30659, 30660, 30661, 30662, 30663, 30664, 30665, + 30666, 30667, 30668, 30669, 30670, 30671, 30672, 30673, + 30674, 30675, 30676, 30677, 30678, 30679, 30680, 30681, + 30682, 30683, 30684, 30685, 30686, 30687, 30688, 30689, + 30690, 30691, 30692, 30693, 30694, 30695, 30696, 30697, + 30698, 30699, 30700, 30701, 30702, 30703, 30704, 30705, + 30706, 30707, 30708, 30709, 30710, 30711, 30712, 30713, + 30714, 30715, 30716, 30717, 30718, 30719, 30720, 30721, + 30722, 30723, 30724, 30725, 30726, 30727, 30728, 30729, + 30730, 30731, 30732, 30733, 30734, 30735, 30736, 30737, + 30738, 30739, 30740, 30741, 30742, 30743, 30744, 30745, + 30746, 30747, 30748, 30749, 30750, 30751, 30752, 30753, + 30754, 30755, 30756, 30757, 30758, 30759, 30760, 30761, + 30762, 30763, 30764, 30765, 30766, 30767, 30768, 30769, + 30770, 30771, 30772, 30773, 30774, 30775, 30776, 30777, + 30778, 30779, 30780, 30781, 30782, 30783, 30784, 30785, + 30786, 30787, 30788, 30789, 30790, 30791, 30792, 30793, + 30794, 30795, 30796, 30797, 30798, 30799, 30800, 30801, + 30802, 30803, 30804, 30805, 30806, 30807, 30808, 30809, + 30810, 30811, 30812, 30813, 30814, 30815, 30816, 30817, + 30818, 30819, 30820, 30821, 30822, 30823, 30824, 30825, + 30826, 30827, 30828, 30829, 30830, 30831, 30832, 30833, + 30834, 30835, 30836, 30837, 30838, 30839, 30840, 30841, + 30842, 30843, 30844, 30845, 30846, 30847, 30848, 30849, + 30850, 30851, 30852, 30853, 30854, 30855, 30856, 30857, + 30858, 30859, 30860, 30861, 30862, 30863, 30864, 30865, + 30866, 30867, 30868, 30869, 30870, 30871, 30872, 30873, + 30874, 30875, 30876, 30877, 30878, 30879, 30880, 30881, + 30882, 30883, 30884, 30885, 30886, 30887, 30888, 30889, + 30890, 30891, 30892, 30893, 30894, 30895, 30896, 30897, + 30898, 30899, 30900, 30901, 30902, 30903, 30904, 30905, + 30906, 30907, 30908, 30909, 30910, 30911, 30912, 30913, + 30914, 30915, 30916, 30917, 30918, 30919, 30920, 30921, + 30922, 30923, 30924, 30925, 30926, 30927, 30928, 30929, + 30930, 30931, 30932, 30933, 30934, 30935, 30936, 30937, + 30938, 30939, 30940, 30941, 30942, 30943, 30944, 30945, + 30946, 30947, 30948, 30949, 30950, 30951, 30952, 30953, + 30954, 30955, 30956, 30957, 30958, 30959, 30960, 30961, + 30962, 30963, 30964, 30965, 30966, 30967, 30968, 30969, + 30970, 30971, 30972, 30973, 30974, 30975, 30976, 30977, + 30978, 30979, 30980, 30981, 30982, 30983, 30984, 30985, + 30986, 30987, 30988, 30989, 30990, 30991, 30992, 30993, + 30994, 30995, 30996, 30997, 30998, 30999, 31000, 31001, + 31002, 31003, 31004, 31005, 31006, 31007, 31008, 31009, + 31010, 31011, 31012, 31013, 31014, 31015, 31016, 31017, + 31018, 31019, 31020, 31021, 31022, 31023, 31024, 31025, + 31026, 31027, 31028, 31029, 31030, 31031, 31032, 31033, + 31034, 31035, 31036, 31037, 31038, 31039, 31040, 31041, + 31042, 31043, 31044, 31045, 31046, 31047, 31048, 31049, + 31050, 31051, 31052, 31053, 31054, 31055, 31056, 31057, + 31058, 31059, 31060, 31061, 31062, 31063, 31064, 31065, + 31066, 31067, 31068, 31069, 31070, 31071, 31072, 31073, + 31074, 31075, 31076, 31077, 31078, 31079, 31080, 31081, + 31082, 31083, 31084, 31085, 31086, 31087, 31088, 31089, + 31090, 31091, 31092, 31093, 31094, 31095, 31096, 31097, + 31098, 31099, 31100, 31101, 31102, 31103, 31104, 31105, + 31106, 31107, 31108, 31109, 31110, 31111, 31112, 31113, + 31114, 31115, 31116, 31117, 31118, 31119, 31120, 31121, + 31122, 31123, 31124, 31125, 31126, 31127, 31128, 31129, + 31130, 31131, 31132, 31133, 31134, 31135, 31136, 31137, + 31138, 31139, 31140, 31141, 31142, 31143, 31144, 31145, + 31146, 31147, 31148, 31149, 31150, 31151, 31152, 31153, + 31154, 31155, 31156, 31157, 31158, 31159, 31160, 31161, + 31162, 31163, 31164, 31165, 31166, 31167, 31168, 31169, + 31170, 31171, 31172, 31173, 31174, 31175, 31176, 31177, + 31178, 31179, 31180, 31181, 31182, 31183, 31184, 31185, + 31186, 31187, 31188, 31189, 31190, 31191, 31192, 31193, + 31194, 31195, 31196, 31197, 31198, 31199, 31200, 31201, + 31202, 31203, 31204, 31205, 31206, 31207, 31208, 31209, + 31210, 31211, 31212, 31213, 31214, 31215, 31216, 31217, + 31218, 31219, 31220, 31221, 31222, 31223, 31224, 31225, + 31226, 31227, 31228, 31229, 31230, 31231, 31232, 31233, + 31234, 31235, 31236, 31237, 31238, 31239, 31240, 31241, + 31242, 31243, 31244, 31245, 31246, 31247, 31248, 31249, + 31250, 31251, 31252, 31253, 31254, 31255, 31256, 31257, + 31258, 31259, 31260, 31261, 31262, 31263, 31264, 31265, + 31266, 31267, 31268, 31269, 31270, 31271, 31272, 31273, + 31274, 31275, 31276, 31277, 31278, 31279, 31280, 31281, + 31282, 31283, 31284, 31285, 31286, 31287, 31288, 31289, + 31290, 31291, 31292, 31293, 31294, 31295, 31296, 31297, + 31298, 31299, 31300, 31301, 31302, 31303, 31304, 31305, + 31306, 31307, 31308, 31309, 31310, 31311, 31312, 31313, + 31314, 31315, 31316, 31317, 31318, 31319, 31320, 31321, + 31322, 31323, 31324, 31325, 31326, 31327, 31328, 31329, + 31330, 31331, 31332, 31333, 31334, 31335, 31336, 31337, + 31338, 31339, 31340, 31341, 31342, 31343, 31344, 31345, + 31346, 31347, 31348, 31349, 31350, 31351, 31352, 31353, + 31354, 31355, 31356, 31357, 31358, 31359, 31360, 31361, + 31362, 31363, 31364, 31365, 31366, 31367, 31368, 31369, + 31370, 31371, 31372, 31373, 31374, 31375, 31376, 31377, + 31378, 31379, 31380, 31381, 31382, 31383, 31384, 31385, + 31386, 31387, 31388, 31389, 31390, 31391, 31392, 31393, + 31394, 31395, 31396, 31397, 31398, 31399, 31400, 31401, + 31402, 31403, 31404, 31405, 31406, 31407, 31408, 31409, + 31410, 31411, 31412, 31413, 31414, 31415, 31416, 31417, + 31418, 31419, 31420, 31421, 31422, 31423, 31424, 31425, + 31426, 31427, 31428, 31429, 31430, 31431, 31432, 31433, + 31434, 31435, 31436, 31437, 31438, 31439, 31440, 31441, + 31442, 31443, 31444, 31445, 31446, 31447, 31448, 31449, + 31450, 31451, 31452, 31453, 31454, 31455, 31456, 31457, + 31458, 31459, 31460, 31461, 31462, 31463, 31464, 31465, + 31466, 31467, 31468, 31469, 31470, 31471, 31472, 31473, + 31474, 31475, 31476, 31477, 31478, 31479, 31480, 31481, + 31482, 31483, 31484, 31485, 31486, 31487, 31488, 31489, + 31490, 31491, 31492, 31493, 31494, 31495, 31496, 31497, + 31498, 31499, 31500, 31501, 31502, 31503, 31504, 31505, + 31506, 31507, 31508, 31509, 31510, 31511, 31512, 31513, + 31514, 31515, 31516, 31517, 31518, 31519, 31520, 31521, + 31522, 31523, 31524, 31525, 31526, 31527, 31528, 31529, + 31530, 31531, 31532, 31533, 31534, 31535, 31536, 31537, + 31538, 31539, 31540, 31541, 31542, 31543, 31544, 31545, + 31546, 31547, 31548, 31549, 31550, 31551, 31552, 31553, + 31554, 31555, 31556, 31557, 31558, 31559, 31560, 31561, + 31562, 31563, 31564, 31565, 31566, 31567, 31568, 31569, + 31570, 31571, 31572, 31573, 31574, 31575, 31576, 31577, + 31578, 31579, 31580, 31581, 31582, 31583, 31584, 31585, + 31586, 31587, 31588, 31589, 31590, 31591, 31592, 31593, + 31594, 31595, 31596, 31597, 31598, 31599, 31600, 31601, + 31602, 31603, 31604, 31605, 31606, 31607, 31608, 31609, + 31610, 31611, 31612, 31613, 31614, 31615, 31616, 31617, + 31618, 31619, 31620, 31621, 31622, 31623, 31624, 31625, + 31626, 31627, 31628, 31629, 31630, 31631, 31632, 31633, + 31634, 31635, 31636, 31637, 31638, 31639, 31640, 31641, + 31642, 31643, 31644, 31645, 31646, 31647, 31648, 31649, + 31650, 31651, 31652, 31653, 31654, 31655, 31656, 31657, + 31658, 31659, 31660, 31661, 31662, 31663, 31664, 31665, + 31666, 31667, 31668, 31669, 31670, 31671, 31672, 31673, + 31674, 31675, 31676, 31677, 31678, 31679, 31680, 31681, + 31682, 31683, 31684, 31685, 31686, 31687, 31688, 31689, + 31690, 31691, 31692, 31693, 31694, 31695, 31696, 31697, + 31698, 31699, 31700, 31701, 31702, 31703, 31704, 31705, + 31706, 31707, 31708, 31709, 31710, 31711, 31712, 31713, + 31714, 31715, 31716, 31717, 31718, 31719, 31720, 31721, + 31722, 31723, 31724, 31725, 31726, 31727, 31728, 31729, + 31730, 31731, 31732, 31733, 31734, 31735, 31736, 31737, + 31738, 31739, 31740, 31741, 31742, 31743, 31744, 31745, + 31746, 31747, 31748, 31749, 31750, 31751, 31752, 31753, + 31754, 31755, 31756, 31757, 31758, 31759, 31760, 31761, + 31762, 31763, 31764, 31765, 31766, 31767, 31768, 31769, + 31770, 31771, 31772, 31773, 31774, 31775, 31776, 31777, + 31778, 31779, 31780, 31781, 31782, 31783, 31784, 31785, + 31786, 31787, 31788, 31789, 31790, 31791, 31792, 31793, + 31794, 31795, 31796, 31797, 31798, 31799, 31800, 31801, + 31802, 31803, 31804, 31805, 31806, 31807, 31808, 31809, + 31810, 31811, 31812, 31813, 31814, 31815, 31816, 31817, + 31818, 31819, 31820, 31821, 31822, 31823, 31824, 31825, + 31826, 31827, 31828, 31829, 31830, 31831, 31832, 31833, + 31834, 31835, 31836, 31837, 31838, 31839, 31840, 31841, + 31842, 31843, 31844, 31845, 31846, 31847, 31848, 31849, + 31850, 31851, 31852, 31853, 31854, 31855, 31856, 31857, + 31858, 31859, 31860, 31861, 31862, 31863, 31864, 31865, + 31866, 31867, 31868, 31869, 31870, 31871, 31872, 31873, + 31874, 31875, 31876, 31877, 31878, 31879, 31880, 31881, + 31882, 31883, 31884, 31885, 31886, 31887, 31888, 31889, + 31890, 31891, 31892, 31893, 31894, 31895, 31896, 31897, + 31898, 31899, 31900, 31901, 31902, 31903, 31904, 31905, + 31906, 31907, 31908, 31909, 31910, 31911, 31912, 31913, + 31914, 31915, 31916, 31917, 31918, 31919, 31920, 31921, + 31922, 31923, 31924, 31925, 31926, 31927, 31928, 31929, + 31930, 31931, 31932, 31933, 31934, 31935, 31936, 31937, + 31938, 31939, 31940, 31941, 31942, 31943, 31944, 31945, + 31946, 31947, 31948, 31949, 31950, 31951, 31952, 31953, + 31954, 31955, 31956, 31957, 31958, 31959, 31960, 31961, + 31962, 31963, 31964, 31965, 31966, 31967, 31968, 31969, + 31970, 31971, 31972, 31973, 31974, 31975, 31976, 31977, + 31978, 31979, 31980, 31981, 31982, 31983, 31984, 31985, + 31986, 31987, 31988, 31989, 31990, 31991, 31992, 31993, + 31994, 31995, 31996, 31997, 31998, 31999, 32000, 32001, + 32002, 32003, 32004, 32005, 32006, 32007, 32008, 32009, + 32010, 32011, 32012, 32013, 32014, 32015, 32016, 32017, + 32018, 32019, 32020, 32021, 32022, 32023, 32024, 32025, + 32026, 32027, 32028, 32029, 32030, 32031, 32032, 32033, + 32034, 32035, 32036, 32037, 32038, 32039, 32040, 32041, + 32042, 32043, 32044, 32045, 32046, 32047, 32048, 32049, + 32050, 32051, 32052, 32053, 32054, 32055, 32056, 32057, + 32058, 32059, 32060, 32061, 32062, 32063, 32064, 32065, + 32066, 32067, 32068, 32069, 32070, 32071, 32072, 32073, + 32074, 32075, 32076, 32077, 32078, 32079, 32080, 32081, + 32082, 32083, 32084, 32085, 32086, 32087, 32088, 32089, + 32090, 32091, 32092, 32093, 32094, 32095, 32096, 32097, + 32098, 32099, 32100, 32101, 32102, 32103, 32104, 32105, + 32106, 32107, 32108, 32109, 32110, 32111, 32112, 32113, + 32114, 32115, 32116, 32117, 32118, 32119, 32120, 32121, + 32122, 32123, 32124, 32125, 32126, 32127, 32128, 32129, + 32130, 32131, 32132, 32133, 32134, 32135, 32136, 32137, + 32138, 32139, 32140, 32141, 32142, 32143, 32144, 32145, + 32146, 32147, 32148, 32149, 32150, 32151, 32152, 32153, + 32154, 32155, 32156, 32157, 32158, 32159, 32160, 32161, + 32162, 32163, 32164, 32165, 32166, 32167, 32168, 32169, + 32170, 32171, 32172, 32173, 32174, 32175, 32176, 32177, + 32178, 32179, 32180, 32181, 32182, 32183, 32184, 32185, + 32186, 32187, 32188, 32189, 32190, 32191, 32192, 32193, + 32194, 32195, 32196, 32197, 32198, 32199, 32200, 32201, + 32202, 32203, 32204, 32205, 32206, 32207, 32208, 32209, + 32210, 32211, 32212, 32213, 32214, 32215, 32216, 32217, + 32218, 32219, 32220, 32221, 32222, 32223, 32224, 32225, + 32226, 32227, 32228, 32229, 32230, 32231, 32232, 32233, + 32234, 32235, 32236, 32237, 32238, 32239, 32240, 32241, + 32242, 32243, 32244, 32245, 32246, 32247, 32248, 32249, + 32250, 32251, 32252, 32253, 32254, 32255, 32256, 32257, + 32258, 32259, 32260, 32261, 32262, 32263, 32264, 32265, + 32266, 32267, 32268, 32269, 32270, 32271, 32272, 32273, + 32274, 32275, 32276, 32277, 32278, 32279, 32280, 32281, + 32282, 32283, 32284, 32285, 32286, 32287, 32288, 32289, + 32290, 32291, 32292, 32293, 32294, 32295, 32296, 32297, + 32298, 32299, 32300, 32301, 32302, 32303, 32304, 32305, + 32306, 32307, 32308, 32309, 32310, 32311, 32312, 32313, + 32314, 32315, 32316, 32317, 32318, 32319, 32320, 32321, + 32322, 32323, 32324, 32325, 32326, 32327, 32328, 32329, + 32330, 32331, 32332, 32333, 32334, 32335, 32336, 32337, + 32338, 32339, 32340, 32341, 32342, 32343, 32344, 32345, + 32346, 32347, 32348, 32349, 32350, 32351, 32352, 32353, + 32354, 32355, 32356, 32357, 32358, 32359, 32360, 32361, + 32362, 32363, 32364, 32365, 32366, 32367, 32368, 32369, + 32370, 32371, 32372, 32373, 32374, 32375, 32376, 32377, + 32378, 32379, 32380, 32381, 32382, 32383, 32384, 32385, + 32386, 32387, 32388, 32389, 32390, 32391, 32392, 32393, + 32394, 32395, 32396, 32397, 32398, 32399, 32400, 32401, + 32402, 32403, 32404, 32405, 32406, 32407, 32408, 32409, + 32410, 32411, 32412, 32413, 32414, 32415, 32416, 32417, + 32418, 32419, 32420, 32421, 32422, 32423, 32424, 32425, + 32426, 32427, 32428, 32429, 32430, 32431, 32432, 32433, + 32434, 32435, 32436, 32437, 32438, 32439, 32440, 32441, + 32442, 32443, 32444, 32445, 32446, 32447, 32448, 32449, + 32450, 32451, 32452, 32453, 32454, 32455, 32456, 32457, + 32458, 32459, 32460, 32461, 32462, 32463, 32464, 32465, + 32466, 32467, 32468, 32469, 32470, 32471, 32472, 32473, + 32474, 32475, 32476, 32477, 32478, 32479, 32480, 32481, + 32482, 32483, 32484, 32485, 32486, 32487, 32488, 32489, + 32490, 32491, 32492, 32493, 32494, 32495, 32496, 32497, + 32498, 32499, 32500, 32501, 32502, 32503, 32504, 32505, + 32506, 32507, 32508, 32509, 32510, 32511, 32512, 32513, + 32514, 32515, 32516, 32517, 32518, 32519, 32520, 32521, + 32522, 32523, 32524, 32525, 32526, 32527, 32528, 32529, + 32530, 32531, 32532, 32533, 32534, 32535, 32536, 32537, + 32538, 32539, 32540, 32541, 32542, 32543, 32544, 32545, + 32546, 32547, 32548, 32549, 32550, 32551, 32552, 32553, + 32554, 32555, 32556, 32557, 32558, 32559, 32560, 32561, + 32562, 32563, 32564, 32565, 32566, 32567, 32568, 32569, + 32570, 32571, 32572, 32573, 32574, 32575, 32576, 32577, + 32578, 32579, 32580, 32581, 32582, 32583, 32584, 32585, + 32586, 32587, 32588, 32589, 32590, 32591, 32592, 32593, + 32594, 32595, 32596, 32597, 32598, 32599, 32600, 32601, + 32602, 32603, 32604, 32605, 32606, 32607, 32608, 32609, + 32610, 32611, 32612, 32613, 32614, 32615, 32616, 32617, + 32618, 32619, 32620, 32621, 32622, 32623, 32624, 32625, + 32626, 32627, 32628, 32629, 32630, 32631, 32632, 32633, + 32634, 32635, 32636, 32637, 32638, 32639, 32640, 32641, + 32642, 32643, 32644, 32645, 32646, 32647, 32648, 32649, + 32650, 32651, 32652, 32653, 32654, 32655, 32656, 32657, + 32658, 32659, 32660, 32661, 32662, 32663, 32664, 32665, + 32666, 32667, 32668, 32669, 32670, 32671, 32672, 32673, + 32674, 32675, 32676, 32677, 32678, 32679, 32680, 32681, + 32682, 32683, 32684, 32685, 32686, 32687, 32688, 32689, + 32690, 32691, 32692, 32693, 32694, 32695, 32696, 32697, + 32698, 32699, 32700, 32701, 32702, 32703, 32704, 32705, + 32706, 32707, 32708, 32709, 32710, 32711, 32712, 32713, + 32714, 32715, 32716, 32717, 32718, 32719, 32720, 32721, + 32722, 32723, 32724, 32725, 32726, 32727, 32728, 32729, + 32730, 32731, 32732, 32733, 32734, 32735, 32736, 32737, + 32738, 32739, 32740, 32741, 32742, 32743, 32744, 32745, + 32746, 32747, 32748, 32749, 32750, 32751, 32752, 32753, + 32754, 32755, 32756, 32757, 32758, 32759, 32760, 32761, + 32762, 32763, 32764, 32765, 32766, 32767, 32768, 32769, + 32770, 32771, 32772, 32773, 32774, 32775, 32776, 32777, + 32778, 32779, 32780, 32781, 32782, 32783, 32784, 32785, + 32786, 32787, 32788, 32789, 32790, 32791, 32792, 32793, + 32794, 32795, 32796, 32797, 32798, 32799, 32800, 32801, + 32802, 32803, 32804, 32805, 32806, 32807, 32808, 32809, + 32810, 32811, 32812, 32813, 32814, 32815, 32816, 32817, + 32818, 32819, 32820, 32821, 32822, 32823, 32824, 32825, + 32826, 32827, 32828, 32829, 32830, 32831, 32832, 32833, + 32834, 32835, 32836, 32837, 32838, 32839, 32840, 32841, + 32842, 32843, 32844, 32845, 32846, 32847, 32848, 32849, + 32850, 32851, 32852, 32853, 32854, 32855, 32856, 32857, + 32858, 32859, 32860, 32861, 32862, 32863, 32864, 32865, + 32866, 32867, 32868, 32869, 32870, 32871, 32872, 32873, + 32874, 32875, 32876, 32877, 32878, 32879, 32880, 32881, + 32882, 32883, 32884, 32885, 32886, 32887, 32888, 32889, + 32890, 32891, 32892, 32893, 32894, 32895, 32896, 32897, + 32898, 32899, 32900, 32901, 32902, 32903, 32904, 32905, + 32906, 32907, 32908, 32909, 32910, 32911, 32912, 32913, + 32914, 32915, 32916, 32917, 32918, 32919, 32920, 32921, + 32922, 32923, 32924, 32925, 32926, 32927, 32928, 32929, + 32930, 32931, 32932, 32933, 32934, 32935, 32936, 32937, + 32938, 32939, 32940, 32941, 32942, 32943, 32944, 32945, + 32946, 32947, 32948, 32949, 32950, 32951, 32952, 32953, + 32954, 32955, 32956, 32957, 32958, 32959, 32960, 32961, + 32962, 32963, 32964, 32965, 32966, 32967, 32968, 32969, + 32970, 32971, 32972, 32973, 32974, 32975, 32976, 32977, + 32978, 32979, 32980, 32981, 32982, 32983, 32984, 32985, + 32986, 32987, 32988, 32989, 32990, 32991, 32992, 32993, + 32994, 32995, 32996, 32997, 32998, 32999, 33000, 33001, + 33002, 33003, 33004, 33005, 33006, 33007, 33008, 33009, + 33010, 33011, 33012, 33013, 33014, 33015, 33016, 33017, + 33018, 33019, 33020, 33021, 33022, 33023, 33024, 33025, + 33026, 33027, 33028, 33029, 33030, 33031, 33032, 33033, + 33034, 33035, 33036, 33037, 33038, 33039, 33040, 33041, + 33042, 33043, 33044, 33045, 33046, 33047, 33048, 33049, + 33050, 33051, 33052, 33053, 33054, 33055, 33056, 33057, + 33058, 33059, 33060, 33061, 33062, 33063, 33064, 33065, + 33066, 33067, 33068, 33069, 33070, 33071, 33072, 33073, + 33074, 33075, 33076, 33077, 33078, 33079, 33080, 33081, + 33082, 33083, 33084, 33085, 33086, 33087, 33088, 33089, + 33090, 33091, 33092, 33093, 33094, 33095, 33096, 33097, + 33098, 33099, 33100, 33101, 33102, 33103, 33104, 33105, + 33106, 33107, 33108, 33109, 33110, 33111, 33112, 33113, + 33114, 33115, 33116, 33117, 33118, 33119, 33120, 33121, + 33122, 33123, 33124, 33125, 33126, 33127, 33128, 33129, + 33130, 33131, 33132, 33133, 33134, 33135, 33136, 33137, + 33138, 33139, 33140, 33141, 33142, 33143, 33144, 33145, + 33146, 33147, 33148, 33149, 33150, 33151, 33152, 33153, + 33154, 33155, 33156, 33157, 33158, 33159, 33160, 33161, + 33162, 33163, 33164, 33165, 33166, 33167, 33168, 33169, + 33170, 33171, 33172, 33173, 33174, 33175, 33176, 33177, + 33178, 33179, 33180, 33181, 33182, 33183, 33184, 33185, + 33186, 33187, 33188, 33189, 33190, 33191, 33192, 33193, + 33194, 33195, 33196, 33197, 33198, 33199, 33200, 33201, + 33202, 33203, 33204, 33205, 33206, 33207, 33208, 33209, + 33210, 33211, 33212, 33213, 33214, 33215, 33216, 33217, + 33218, 33219, 33220, 33221, 33222, 33223, 33224, 33225, + 33226, 33227, 33228, 33229, 33230, 33231, 33232, 33233, + 33234, 33235, 33236, 33237, 33238, 33239, 33240, 33241, + 33242, 33243, 33244, 33245, 33246, 33247, 33248, 33249, + 33250, 33251, 33252, 33253, 33254, 33255, 33256, 33257, + 33258, 33259, 33260, 33261, 33262, 33263, 33264, 33265, + 33266, 33267, 33268, 33269, 33270, 33271, 33272, 33273, + 33274, 33275, 33276, 33277, 33278, 33279, 33280, 33281, + 33282, 33283, 33284, 33285, 33286, 33287, 33288, 33289, + 33290, 33291, 33292, 33293, 33294, 33295, 33296, 33297, + 33298, 33299, 33300, 33301, 33302, 33303, 33304, 33305, + 33306, 33307, 33308, 33309, 33310, 33311, 33312, 33313, + 33314, 33315, 33316, 33317, 33318, 33319, 33320, 33321, + 33322, 33323, 33324, 33325, 33326, 33327, 33328, 33329, + 33330, 33331, 33332, 33333, 33334, 33335, 33336, 33337, + 33338, 33339, 33340, 33341, 33342, 33343, 33344, 33345, + 33346, 33347, 33348, 33349, 33350, 33351, 33352, 33353, + 33354, 33355, 33356, 33357, 33358, 33359, 33360, 33361, + 33362, 33363, 33364, 33365, 33366, 33367, 33368, 33369, + 33370, 33371, 33372, 33373, 33374, 33375, 33376, 33377, + 33378, 33379, 33380, 33381, 33382, 33383, 33384, 33385, + 33386, 33387, 33388, 33389, 33390, 33391, 33392, 33393, + 33394, 33395, 33396, 33397, 33398, 33399, 33400, 33401, + 33402, 33403, 33404, 33405, 33406, 33407, 33408, 33409, + 33410, 33411, 33412, 33413, 33414, 33415, 33416, 33417, + 33418, 33419, 33420, 33421, 33422, 33423, 33424, 33425, + 33426, 33427, 33428, 33429, 33430, 33431, 33432, 33433, + 33434, 33435, 33436, 33437, 33438, 33439, 33440, 33441, + 33442, 33443, 33444, 33445, 33446, 33447, 33448, 33449, + 33450, 33451, 33452, 33453, 33454, 33455, 33456, 33457, + 33458, 33459, 33460, 33461, 33462, 33463, 33464, 33465, + 33466, 33467, 33468, 33469, 33470, 33471, 33472, 33473, + 33474, 33475, 33476, 33477, 33478, 33479, 33480, 33481, + 33482, 33483, 33484, 33485, 33486, 33487, 33488, 33489, + 33490, 33491, 33492, 33493, 33494, 33495, 33496, 33497, + 33498, 33499, 33500, 33501, 33502, 33503, 33504, 33505, + 33506, 33507, 33508, 33509, 33510, 33511, 33512, 33513, + 33514, 33515, 33516, 33517, 33518, 33519, 33520, 33521, + 33522, 33523, 33524, 33525, 33526, 33527, 33528, 33529, + 33530, 33531, 33532, 33533, 33534, 33535, 33536, 33537, + 33538, 33539, 33540, 33541, 33542, 33543, 33544, 33545, + 33546, 33547, 33548, 33549, 33550, 33551, 33552, 33553, + 33554, 33555, 33556, 33557, 33558, 33559, 33560, 33561, + 33562, 33563, 33564, 33565, 33566, 33567, 33568, 33569, + 33570, 33571, 33572, 33573, 33574, 33575, 33576, 33577, + 33578, 33579, 33580, 33581, 33582, 33583, 33584, 33585, + 33586, 33587, 33588, 33589, 33590, 33591, 33592, 33593, + 33594, 33595, 33596, 33597, 33598, 33599, 33600, 33601, + 33602, 33603, 33604, 33605, 33606, 33607, 33608, 33609, + 33610, 33611, 33612, 33613, 33614, 33615, 33616, 33617, + 33618, 33619, 33620, 33621, 33622, 33623, 33624, 33625, + 33626, 33627, 33628, 33629, 33630, 33631, 33632, 33633, + 33634, 33635, 33636, 33637, 33638, 33639, 33640, 33641, + 33642, 33643, 33644, 33645, 33646, 33647, 33648, 33649, + 33650, 33651, 33652, 33653, 33654, 33655, 33656, 33657, + 33658, 33659, 33660, 33661, 33662, 33663, 33664, 33665, + 33666, 33667, 33668, 33669, 33670, 33671, 33672, 33673, + 33674, 33675, 33676, 33677, 33678, 33679, 33680, 33681, + 33682, 33683, 33684, 33685, 33686, 33687, 33688, 33689, + 33690, 33691, 33692, 33693, 33694, 33695, 33696, 33697, + 33698, 33699, 33700, 33701, 33702, 33703, 33704, 33705, + 33706, 33707, 33708, 33709, 33710, 33711, 33712, 33713, + 33714, 33715, 33716, 33717, 33718, 33719, 33720, 33721, + 33722, 33723, 33724, 33725, 33726, 33727, 33728, 33729, + 33730, 33731, 33732, 33733, 33734, 33735, 33736, 33737, + 33738, 33739, 33740, 33741, 33742, 33743, 33744, 33745, + 33746, 33747, 33748, 33749, 33750, 33751, 33752, 33753, + 33754, 33755, 33756, 33757, 33758, 33759, 33760, 33761, + 33762, 33763, 33764, 33765, 33766, 33767, 33768, 33769, + 33770, 33771, 33772, 33773, 33774, 33775, 33776, 33777, + 33778, 33779, 33780, 33781, 33782, 33783, 33784, 33785, + 33786, 33787, 33788, 33789, 33790, 33791, 33792, 33793, + 33794, 33795, 33796, 33797, 33798, 33799, 33800, 33801, + 33802, 33803, 33804, 33805, 33806, 33807, 33808, 33809, + 33810, 33811, 33812, 33813, 33814, 33815, 33816, 33817, + 33818, 33819, 33820, 33821, 33822, 33823, 33824, 33825, + 33826, 33827, 33828, 33829, 33830, 33831, 33832, 33833, + 33834, 33835, 33836, 33837, 33838, 33839, 33840, 33841, + 33842, 33843, 33844, 33845, 33846, 33847, 33848, 33849, + 33850, 33851, 33852, 33853, 33854, 33855, 33856, 33857, + 33858, 33859, 33860, 33861, 33862, 33863, 33864, 33865, + 33866, 33867, 33868, 33869, 33870, 33871, 33872, 33873, + 33874, 33875, 33876, 33877, 33878, 33879, 33880, 33881, + 33882, 33883, 33884, 33885, 33886, 33887, 33888, 33889, + 33890, 33891, 33892, 33893, 33894, 33895, 33896, 33897, + 33898, 33899, 33900, 33901, 33902, 33903, 33904, 33905, + 33906, 33907, 33908, 33909, 33910, 33911, 33912, 33913, + 33914, 33915, 33916, 33917, 33918, 33919, 33920, 33921, + 33922, 33923, 33924, 33925, 33926, 33927, 33928, 33929, + 33930, 33931, 33932, 33933, 33934, 33935, 33936, 33937, + 33938, 33939, 33940, 33941, 33942, 33943, 33944, 33945, + 33946, 33947, 33948, 33949, 33950, 33951, 33952, 33953, + 33954, 33955, 33956, 33957, 33958, 33959, 33960, 33961, + 33962, 33963, 33964, 33965, 33966, 33967, 33968, 33969, + 33970, 33971, 33972, 33973, 33974, 33975, 33976, 33977, + 33978, 33979, 33980, 33981, 33982, 33983, 33984, 33985, + 33986, 33987, 33988, 33989, 33990, 33991, 33992, 33993, + 33994, 33995, 33996, 33997, 33998, 33999, 34000, 34001, + 34002, 34003, 34004, 34005, 34006, 34007, 34008, 34009, + 34010, 34011, 34012, 34013, 34014, 34015, 34016, 34017, + 34018, 34019, 34020, 34021, 34022, 34023, 34024, 34025, + 34026, 34027, 34028, 34029, 34030, 34031, 34032, 34033, + 34034, 34035, 34036, 34037, 34038, 34039, 34040, 34041, + 34042, 34043, 34044, 34045, 34046, 34047, 34048, 34049, + 34050, 34051, 34052, 34053, 34054, 34055, 34056, 34057, + 34058, 34059, 34060, 34061, 34062, 34063, 34064, 34065, + 34066, 34067, 34068, 34069, 34070, 34071, 34072, 34073, + 34074, 34075, 34076, 34077, 34078, 34079, 34080, 34081, + 34082, 34083, 34084, 34085, 34086, 34087, 34088, 34089, + 34090, 34091, 34092, 34093, 34094, 34095, 34096, 34097, + 34098, 34099, 34100, 34101, 34102, 34103, 34104, 34105, + 34106, 34107, 34108, 34109, 34110, 34111, 34112, 34113, + 34114, 34115, 34116, 34117, 34118, 34119, 34120, 34121, + 34122, 34123, 34124, 34125, 34126, 34127, 34128, 34129, + 34130, 34131, 34132, 34133, 34134, 34135, 34136, 34137, + 34138, 34139, 34140, 34141, 34142, 34143, 34144, 34145, + 34146, 34147, 34148, 34149, 34150, 34151, 34152, 34153, + 34154, 34155, 34156, 34157, 34158, 34159, 34160, 34161, + 34162, 34163, 34164, 34165, 34166, 34167, 34168, 34169, + 34170, 34171, 34172, 34173, 34174, 34175, 34176, 34177, + 34178, 34179, 34180, 34181, 34182, 34183, 34184, 34185, + 34186, 34187, 34188, 34189, 34190, 34191, 34192, 34193, + 34194, 34195, 34196, 34197, 34198, 34199, 34200, 34201, + 34202, 34203, 34204, 34205, 34206, 34207, 34208, 34209, + 34210, 34211, 34212, 34213, 34214, 34215, 34216, 34217, + 34218, 34219, 34220, 34221, 34222, 34223, 34224, 34225, + 34226, 34227, 34228, 34229, 34230, 34231, 34232, 34233, + 34234, 34235, 34236, 34237, 34238, 34239, 34240, 34241, + 34242, 34243, 34244, 34245, 34246, 34247, 34248, 34249, + 34250, 34251, 34252, 34253, 34254, 34255, 34256, 34257, + 34258, 34259, 34260, 34261, 34262, 34263, 34264, 34265, + 34266, 34267, 34268, 34269, 34270, 34271, 34272, 34273, + 34274, 34275, 34276, 34277, 34278, 34279, 34280, 34281, + 34282, 34283, 34284, 34285, 34286, 34287, 34288, 34289, + 34290, 34291, 34292, 34293, 34294, 34295, 34296, 34297, + 34298, 34299, 34300, 34301, 34302, 34303, 34304, 34305, + 34306, 34307, 34308, 34309, 34310, 34311, 34312, 34313, + 34314, 34315, 34316, 34317, 34318, 34319, 34320, 34321, + 34322, 34323, 34324, 34325, 34326, 34327, 34328, 34329, + 34330, 34331, 34332, 34333, 34334, 34335, 34336, 34337, + 34338, 34339, 34340, 34341, 34342, 34343, 34344, 34345, + 34346, 34347, 34348, 34349, 34350, 34351, 34352, 34353, + 34354, 34355, 34356, 34357, 34358, 34359, 34360, 34361, + 34362, 34363, 34364, 34365, 34366, 34367, 34368, 34369, + 34370, 34371, 34372, 34373, 34374, 34375, 34376, 34377, + 34378, 34379, 34380, 34381, 34382, 34383, 34384, 34385, + 34386, 34387, 34388, 34389, 34390, 34391, 34392, 34393, + 34394, 34395, 34396, 34397, 34398, 34399, 34400, 34401, + 34402, 34403, 34404, 34405, 34406, 34407, 34408, 34409, + 34410, 34411, 34412, 34413, 34414, 34415, 34416, 34417, + 34418, 34419, 34420, 34421, 34422, 34423, 34424, 34425, + 34426, 34427, 34428, 34429, 34430, 34431, 34432, 34433, + 34434, 34435, 34436, 34437, 34438, 34439, 34440, 34441, + 34442, 34443, 34444, 34445, 34446, 34447, 34448, 34449, + 34450, 34451, 34452, 34453, 34454, 34455, 34456, 34457, + 34458, 34459, 34460, 34461, 34462, 34463, 34464, 34465, + 34466, 34467, 34468, 34469, 34470, 34471, 34472, 34473, + 34474, 34475, 34476, 34477, 34478, 34479, 34480, 34481, + 34482, 34483, 34484, 34485, 34486, 34487, 34488, 34489, + 34490, 34491, 34492, 34493, 34494, 34495, 34496, 34497, + 34498, 34499, 34500, 34501, 34502, 34503, 34504, 34505, + 34506, 34507, 34508, 34509, 34510, 34511, 34512, 34513, + 34514, 34515, 34516, 34517, 34518, 34519, 34520, 34521, + 34522, 34523, 34524, 34525, 34526, 34527, 34528, 34529, + 34530, 34531, 34532, 34533, 34534, 34535, 34536, 34537, + 34538, 34539, 34540, 34541, 34542, 34543, 34544, 34545, + 34546, 34547, 34548, 34549, 34550, 34551, 34552, 34553, + 34554, 34555, 34556, 34557, 34558, 34559, 34560, 34561, + 34562, 34563, 34564, 34565, 34566, 34567, 34568, 34569, + 34570, 34571, 34572, 34573, 34574, 34575, 34576, 34577, + 34578, 34579, 34580, 34581, 34582, 34583, 34584, 34585, + 34586, 34587, 34588, 34589, 34590, 34591, 34592, 34593, + 34594, 34595, 34596, 34597, 34598, 34599, 34600, 34601, + 34602, 34603, 34604, 34605, 34606, 34607, 34608, 34609, + 34610, 34611, 34612, 34613, 34614, 34615, 34616, 34617, + 34618, 34619, 34620, 34621, 34622, 34623, 34624, 34625, + 34626, 34627, 34628, 34629, 34630, 34631, 34632, 34633, + 34634, 34635, 34636, 34637, 34638, 34639, 34640, 34641, + 34642, 34643, 34644, 34645, 34646, 34647, 34648, 34649, + 34650, 34651, 34652, 34653, 34654, 34655, 34656, 34657, + 34658, 34659, 34660, 34661, 34662, 34663, 34664, 34665, + 34666, 34667, 34668, 34669, 34670, 34671, 34672, 34673, + 34674, 34675, 34676, 34677, 34678, 34679, 34680, 34681, + 34682, 34683, 34684, 34685, 34686, 34687, 34688, 34689, + 34690, 34691, 34692, 34693, 34694, 34695, 34696, 34697, + 34698, 34699, 34700, 34701, 34702, 34703, 34704, 34705, + 34706, 34707, 34708, 34709, 34710, 34711, 34712, 34713, + 34714, 34715, 34716, 34717, 34718, 34719, 34720, 34721, + 34722, 34723, 34724, 34725, 34726, 34727, 34728, 34729, + 34730, 34731, 34732, 34733, 34734, 34735, 34736, 34737, + 34738, 34739, 34740, 34741, 34742, 34743, 34744, 34745, + 34746, 34747, 34748, 34749, 34750, 34751, 34752, 34753, + 34754, 34755, 34756, 34757, 34758, 34759, 34760, 34761, + 34762, 34763, 34764, 34765, 34766, 34767, 34768, 34769, + 34770, 34771, 34772, 34773, 34774, 34775, 34776, 34777, + 34778, 34779, 34780, 34781, 34782, 34783, 34784, 34785, + 34786, 34787, 34788, 34789, 34790, 34791, 34792, 34793, + 34794, 34795, 34796, 34797, 34798, 34799, 34800, 34801, + 34802, 34803, 34804, 34805, 34806, 34807, 34808, 34809, + 34810, 34811, 34812, 34813, 34814, 34815, 34816, 34817, + 34818, 34819, 34820, 34821, 34822, 34823, 34824, 34825, + 34826, 34827, 34828, 34829, 34830, 34831, 34832, 34833, + 34834, 34835, 34836, 34837, 34838, 34839, 34840, 34841, + 34842, 34843, 34844, 34845, 34846, 34847, 34848, 34849, + 34850, 34851, 34852, 34853, 34854, 34855, 34856, 34857, + 34858, 34859, 34860, 34861, 34862, 34863, 34864, 34865, + 34866, 34867, 34868, 34869, 34870, 34871, 34872, 34873, + 34874, 34875, 34876, 34877, 34878, 34879, 34880, 34881, + 34882, 34883, 34884, 34885, 34886, 34887, 34888, 34889, + 34890, 34891, 34892, 34893, 34894, 34895, 34896, 34897, + 34898, 34899, 34900, 34901, 34902, 34903, 34904, 34905, + 34906, 34907, 34908, 34909, 34910, 34911, 34912, 34913, + 34914, 34915, 34916, 34917, 34918, 34919, 34920, 34921, + 34922, 34923, 34924, 34925, 34926, 34927, 34928, 34929, + 34930, 34931, 34932, 34933, 34934, 34935, 34936, 34937, + 34938, 34939, 34940, 34941, 34942, 34943, 34944, 34945, + 34946, 34947, 34948, 34949, 34950, 34951, 34952, 34953, + 34954, 34955, 34956, 34957, 34958, 34959, 34960, 34961, + 34962, 34963, 34964, 34965, 34966, 34967, 34968, 34969, + 34970, 34971, 34972, 34973, 34974, 34975, 34976, 34977, + 34978, 34979, 34980, 34981, 34982, 34983, 34984, 34985, + 34986, 34987, 34988, 34989, 34990, 34991, 34992, 34993, + 34994, 34995, 34996, 34997, 34998, 34999, 35000, 35001, + 35002, 35003, 35004, 35005, 35006, 35007, 35008, 35009, + 35010, 35011, 35012, 35013, 35014, 35015, 35016, 35017, + 35018, 35019, 35020, 35021, 35022, 35023, 35024, 35025, + 35026, 35027, 35028, 35029, 35030, 35031, 35032, 35033, + 35034, 35035, 35036, 35037, 35038, 35039, 35040, 35041, + 35042, 35043, 35044, 35045, 35046, 35047, 35048, 35049, + 35050, 35051, 35052, 35053, 35054, 35055, 35056, 35057, + 35058, 35059, 35060, 35061, 35062, 35063, 35064, 35065, + 35066, 35067, 35068, 35069, 35070, 35071, 35072, 35073, + 35074, 35075, 35076, 35077, 35078, 35079, 35080, 35081, + 35082, 35083, 35084, 35085, 35086, 35087, 35088, 35089, + 35090, 35091, 35092, 35093, 35094, 35095, 35096, 35097, + 35098, 35099, 35100, 35101, 35102, 35103, 35104, 35105, + 35106, 35107, 35108, 35109, 35110, 35111, 35112, 35113, + 35114, 35115, 35116, 35117, 35118, 35119, 35120, 35121, + 35122, 35123, 35124, 35125, 35126, 35127, 35128, 35129, + 35130, 35131, 35132, 35133, 35134, 35135, 35136, 35137, + 35138, 35139, 35140, 35141, 35142, 35143, 35144, 35145, + 35146, 35147, 35148, 35149, 35150, 35151, 35152, 35153, + 35154, 35155, 35156, 35157, 35158, 35159, 35160, 35161, + 35162, 35163, 35164, 35165, 35166, 35167, 35168, 35169, + 35170, 35171, 35172, 35173, 35174, 35175, 35176, 35177, + 35178, 35179, 35180, 35181, 35182, 35183, 35184, 35185, + 35186, 35187, 35188, 35189, 35190, 35191, 35192, 35193, + 35194, 35195, 35196, 35197, 35198, 35199, 35200, 35201, + 35202, 35203, 35204, 35205, 35206, 35207, 35208, 35209, + 35210, 35211, 35212, 35213, 35214, 35215, 35216, 35217, + 35218, 35219, 35220, 35221, 35222, 35223, 35224, 35225, + 35226, 35227, 35228, 35229, 35230, 35231, 35232, 35233, + 35234, 35235, 35236, 35237, 35238, 35239, 35240, 35241, + 35242, 35243, 35244, 35245, 35246, 35247, 35248, 35249, + 35250, 35251, 35252, 35253, 35254, 35255, 35256, 35257, + 35258, 35259, 35260, 35261, 35262, 35263, 35264, 35265, + 35266, 35267, 35268, 35269, 35270, 35271, 35272, 35273, + 35274, 35275, 35276, 35277, 35278, 35279, 35280, 35281, + 35282, 35283, 35284, 35285, 35286, 35287, 35288, 35289, + 35290, 35291, 35292, 35293, 35294, 35295, 35296, 35297, + 35298, 35299, 35300, 35301, 35302, 35303, 35304, 35305, + 35306, 35307, 35308, 35309, 35310, 35311, 35312, 35313, + 35314, 35315, 35316, 35317, 35318, 35319, 35320, 35321, + 35322, 35323, 35324, 35325, 35326, 35327, 35328, 35329, + 35330, 35331, 35332, 35333, 35334, 35335, 35336, 35337, + 35338, 35339, 35340, 35341, 35342, 35343, 35344, 35345, + 35346, 35347, 35348, 35349, 35350, 35351, 35352, 35353, + 35354, 35355, 35356, 35357, 35358, 35359, 35360, 35361, + 35362, 35363, 35364, 35365, 35366, 35367, 35368, 35369, + 35370, 35371, 35372, 35373, 35374, 35375, 35376, 35377, + 35378, 35379, 35380, 35381, 35382, 35383, 35384, 35385, + 35386, 35387, 35388, 35389, 35390, 35391, 35392, 35393, + 35394, 35395, 35396, 35397, 35398, 35399, 35400, 35401, + 35402, 35403, 35404, 35405, 35406, 35407, 35408, 35409, + 35410, 35411, 35412, 35413, 35414, 35415, 35416, 35417, + 35418, 35419, 35420, 35421, 35422, 35423, 35424, 35425, + 35426, 35427, 35428, 35429, 35430, 35431, 35432, 35433, + 35434, 35435, 35436, 35437, 35438, 35439, 35440, 35441, + 35442, 35443, 35444, 35445, 35446, 35447, 35448, 35449, + 35450, 35451, 35452, 35453, 35454, 35455, 35456, 35457, + 35458, 35459, 35460, 35461, 35462, 35463, 35464, 35465, + 35466, 35467, 35468, 35469, 35470, 35471, 35472, 35473, + 35474, 35475, 35476, 35477, 35478, 35479, 35480, 35481, + 35482, 35483, 35484, 35485, 35486, 35487, 35488, 35489, + 35490, 35491, 35492, 35493, 35494, 35495, 35496, 35497, + 35498, 35499, 35500, 35501, 35502, 35503, 35504, 35505, + 35506, 35507, 35508, 35509, 35510, 35511, 35512, 35513, + 35514, 35515, 35516, 35517, 35518, 35519, 35520, 35521, + 35522, 35523, 35524, 35525, 35526, 35527, 35528, 35529, + 35530, 35531, 35532, 35533, 35534, 35535, 35536, 35537, + 35538, 35539, 35540, 35541, 35542, 35543, 35544, 35545, + 35546, 35547, 35548, 35549, 35550, 35551, 35552, 35553, + 35554, 35555, 35556, 35557, 35558, 35559, 35560, 35561, + 35562, 35563, 35564, 35565, 35566, 35567, 35568, 35569, + 35570, 35571, 35572, 35573, 35574, 35575, 35576, 35577, + 35578, 35579, 35580, 35581, 35582, 35583, 35584, 35585, + 35586, 35587, 35588, 35589, 35590, 35591, 35592, 35593, + 35594, 35595, 35596, 35597, 35598, 35599, 35600, 35601, + 35602, 35603, 35604, 35605, 35606, 35607, 35608, 35609, + 35610, 35611, 35612, 35613, 35614, 35615, 35616, 35617, + 35618, 35619, 35620, 35621, 35622, 35623, 35624, 35625, + 35626, 35627, 35628, 35629, 35630, 35631, 35632, 35633, + 35634, 35635, 35636, 35637, 35638, 35639, 35640, 35641, + 35642, 35643, 35644, 35645, 35646, 35647, 35648, 35649, + 35650, 35651, 35652, 35653, 35654, 35655, 35656, 35657, + 35658, 35659, 35660, 35661, 35662, 35663, 35664, 35665, + 35666, 35667, 35668, 35669, 35670, 35671, 35672, 35673, + 35674, 35675, 35676, 35677, 35678, 35679, 35680, 35681, + 35682, 35683, 35684, 35685, 35686, 35687, 35688, 35689, + 35690, 35691, 35692, 35693, 35694, 35695, 35696, 35697, + 35698, 35699, 35700, 35701, 35702, 35703, 35704, 35705, + 35706, 35707, 35708, 35709, 35710, 35711, 35712, 35713, + 35714, 35715, 35716, 35717, 35718, 35719, 35720, 35721, + 35722, 35723, 35724, 35725, 35726, 35727, 35728, 35729, + 35730, 35731, 35732, 35733, 35734, 35735, 35736, 35737, + 35738, 35739, 35740, 35741, 35742, 35743, 35744, 35745, + 35746, 35747, 35748, 35749, 35750, 35751, 35752, 35753, + 35754, 35755, 35756, 35757, 35758, 35759, 35760, 35761, + 35762, 35763, 35764, 35765, 35766, 35767, 35768, 35769, + 35770, 35771, 35772, 35773, 35774, 35775, 35776, 35777, + 35778, 35779, 35780, 35781, 35782, 35783, 35784, 35785, + 35786, 35787, 35788, 35789, 35790, 35791, 35792, 35793, + 35794, 35795, 35796, 35797, 35798, 35799, 35800, 35801, + 35802, 35803, 35804, 35805, 35806, 35807, 35808, 35809, + 35810, 35811, 35812, 35813, 35814, 35815, 35816, 35817, + 35818, 35819, 35820, 35821, 35822, 35823, 35824, 35825, + 35826, 35827, 35828, 35829, 35830, 35831, 35832, 35833, + 35834, 35835, 35836, 35837, 35838, 35839, 35840, 35841, + 35842, 35843, 35844, 35845, 35846, 35847, 35848, 35849, + 35850, 35851, 35852, 35853, 35854, 35855, 35856, 35857, + 35858, 35859, 35860, 35861, 35862, 35863, 35864, 35865, + 35866, 35867, 35868, 35869, 35870, 35871, 35872, 35873, + 35874, 35875, 35876, 35877, 35878, 35879, 35880, 35881, + 35882, 35883, 35884, 35885, 35886, 35887, 35888, 35889, + 35890, 35891, 35892, 35893, 35894, 35895, 35896, 35897, + 35898, 35899, 35900, 35901, 35902, 35903, 35904, 35905, + 35906, 35907, 35908, 35909, 35910, 35911, 35912, 35913, + 35914, 35915, 35916, 35917, 35918, 35919, 35920, 35921, + 35922, 35923, 35924, 35925, 35926, 35927, 35928, 35929, + 35930, 35931, 35932, 35933, 35934, 35935, 35936, 35937, + 35938, 35939, 35940, 35941, 35942, 35943, 35944, 35945, + 35946, 35947, 35948, 35949, 35950, 35951, 35952, 35953, + 35954, 35955, 35956, 35957, 35958, 35959, 35960, 35961, + 35962, 35963, 35964, 35965, 35966, 35967, 35968, 35969, + 35970, 35971, 35972, 35973, 35974, 35975, 35976, 35977, + 35978, 35979, 35980, 35981, 35982, 35983, 35984, 35985, + 35986, 35987, 35988, 35989, 35990, 35991, 35992, 35993, + 35994, 35995, 35996, 35997, 35998, 35999, 36000, 36001, + 36002, 36003, 36004, 36005, 36006, 36007, 36008, 36009, + 36010, 36011, 36012, 36013, 36014, 36015, 36016, 36017, + 36018, 36019, 36020, 36021, 36022, 36023, 36024, 36025, + 36026, 36027, 36028, 36029, 36030, 36031, 36032, 36033, + 36034, 36035, 36036, 36037, 36038, 36039, 36040, 36041, + 36042, 36043, 36044, 36045, 36046, 36047, 36048, 36049, + 36050, 36051, 36052, 36053, 36054, 36055, 36056, 36057, + 36058, 36059, 36060, 36061, 36062, 36063, 36064, 36065, + 36066, 36067, 36068, 36069, 36070, 36071, 36072, 36073, + 36074, 36075, 36076, 36077, 36078, 36079, 36080, 36081, + 36082, 36083, 36084, 36085, 36086, 36087, 36088, 36089, + 36090, 36091, 36092, 36093, 36094, 36095, 36096, 36097, + 36098, 36099, 36100, 36101, 36102, 36103, 36104, 36105, + 36106, 36107, 36108, 36109, 36110, 36111, 36112, 36113, + 36114, 36115, 36116, 36117, 36118, 36119, 36120, 36121, + 36122, 36123, 36124, 36125, 36126, 36127, 36128, 36129, + 36130, 36131, 36132, 36133, 36134, 36135, 36136, 36137, + 36138, 36139, 36140, 36141, 36142, 36143, 36144, 36145, + 36146, 36147, 36148, 36149, 36150, 36151, 36152, 36153, + 36154, 36155, 36156, 36157, 36158, 36159, 36160, 36161, + 36162, 36163, 36164, 36165, 36166, 36167, 36168, 36169, + 36170, 36171, 36172, 36173, 36174, 36175, 36176, 36177, + 36178, 36179, 36180, 36181, 36182, 36183, 36184, 36185, + 36186, 36187, 36188, 36189, 36190, 36191, 36192, 36193, + 36194, 36195, 36196, 36197, 36198, 36199, 36200, 36201, + 36202, 36203, 36204, 36205, 36206, 36207, 36208, 36209, + 36210, 36211, 36212, 36213, 36214, 36215, 36216, 36217, + 36218, 36219, 36220, 36221, 36222, 36223, 36224, 36225, + 36226, 36227, 36228, 36229, 36230, 36231, 36232, 36233, + 36234, 36235, 36236, 36237, 36238, 36239, 36240, 36241, + 36242, 36243, 36244, 36245, 36246, 36247, 36248, 36249, + 36250, 36251, 36252, 36253, 36254, 36255, 36256, 36257, + 36258, 36259, 36260, 36261, 36262, 36263, 36264, 36265, + 36266, 36267, 36268, 36269, 36270, 36271, 36272, 36273, + 36274, 36275, 36276, 36277, 36278, 36279, 36280, 36281, + 36282, 36283, 36284, 36285, 36286, 36287, 36288, 36289, + 36290, 36291, 36292, 36293, 36294, 36295, 36296, 36297, + 36298, 36299, 36300, 36301, 36302, 36303, 36304, 36305, + 36306, 36307, 36308, 36309, 36310, 36311, 36312, 36313, + 36314, 36315, 36316, 36317, 36318, 36319, 36320, 36321, + 36322, 36323, 36324, 36325, 36326, 36327, 36328, 36329, + 36330, 36331, 36332, 36333, 36334, 36335, 36336, 36337, + 36338, 36339, 36340, 36341, 36342, 36343, 36344, 36345, + 36346, 36347, 36348, 36349, 36350, 36351, 36352, 36353, + 36354, 36355, 36356, 36357, 36358, 36359, 36360, 36361, + 36362, 36363, 36364, 36365, 36366, 36367, 36368, 36369, + 36370, 36371, 36372, 36373, 36374, 36375, 36376, 36377, + 36378, 36379, 36380, 36381, 36382, 36383, 36384, 36385, + 36386, 36387, 36388, 36389, 36390, 36391, 36392, 36393, + 36394, 36395, 36396, 36397, 36398, 36399, 36400, 36401, + 36402, 36403, 36404, 36405, 36406, 36407, 36408, 36409, + 36410, 36411, 36412, 36413, 36414, 36415, 36416, 36417, + 36418, 36419, 36420, 36421, 36422, 36423, 36424, 36425, + 36426, 36427, 36428, 36429, 36430, 36431, 36432, 36433, + 36434, 36435, 36436, 36437, 36438, 36439, 36440, 36441, + 36442, 36443, 36444, 36445, 36446, 36447, 36448, 36449, + 36450, 36451, 36452, 36453, 36454, 36455, 36456, 36457, + 36458, 36459, 36460, 36461, 36462, 36463, 36464, 36465, + 36466, 36467, 36468, 36469, 36470, 36471, 36472, 36473, + 36474, 36475, 36476, 36477, 36478, 36479, 36480, 36481, + 36482, 36483, 36484, 36485, 36486, 36487, 36488, 36489, + 36490, 36491, 36492, 36493, 36494, 36495, 36496, 36497, + 36498, 36499, 36500, 36501, 36502, 36503, 36504, 36505, + 36506, 36507, 36508, 36509, 36510, 36511, 36512, 36513, + 36514, 36515, 36516, 36517, 36518, 36519, 36520, 36521, + 36522, 36523, 36524, 36525, 36526, 36527, 36528, 36529, + 36530, 36531, 36532, 36533, 36534, 36535, 36536, 36537, + 36538, 36539, 36540, 36541, 36542, 36543, 36544, 36545, + 36546, 36547, 36548, 36549, 36550, 36551, 36552, 36553, + 36554, 36555, 36556, 36557, 36558, 36559, 36560, 36561, + 36562, 36563, 36564, 36565, 36566, 36567, 36568, 36569, + 36570, 36571, 36572, 36573, 36574, 36575, 36576, 36577, + 36578, 36579, 36580, 36581, 36582, 36583, 36584, 36585, + 36586, 36587, 36588, 36589, 36590, 36591, 36592, 36593, + 36594, 36595, 36596, 36597, 36598, 36599, 36600, 36601, + 36602, 36603, 36604, 36605, 36606, 36607, 36608, 36609, + 36610, 36611, 36612, 36613, 36614, 36615, 36616, 36617, + 36618, 36619, 36620, 36621, 36622, 36623, 36624, 36625, + 36626, 36627, 36628, 36629, 36630, 36631, 36632, 36633, + 36634, 36635, 36636, 36637, 36638, 36639, 36640, 36641, + 36642, 36643, 36644, 36645, 36646, 36647, 36648, 36649, + 36650, 36651, 36652, 36653, 36654, 36655, 36656, 36657, + 36658, 36659, 36660, 36661, 36662, 36663, 36664, 36665, + 36666, 36667, 36668, 36669, 36670, 36671, 36672, 36673, + 36674, 36675, 36676, 36677, 36678, 36679, 36680, 36681, + 36682, 36683, 36684, 36685, 36686, 36687, 36688, 36689, + 36690, 36691, 36692, 36693, 36694, 36695, 36696, 36697, + 36698, 36699, 36700, 36701, 36702, 36703, 36704, 36705, + 36706, 36707, 36708, 36709, 36710, 36711, 36712, 36713, + 36714, 36715, 36716, 36717, 36718, 36719, 36720, 36721, + 36722, 36723, 36724, 36725, 36726, 36727, 36728, 36729, + 36730, 36731, 36732, 36733, 36734, 36735, 36736, 36737, + 36738, 36739, 36740, 36741, 36742, 36743, 36744, 36745, + 36746, 36747, 36748, 36749, 36750, 36751, 36752, 36753, + 36754, 36755, 36756, 36757, 36758, 36759, 36760, 36761, + 36762, 36763, 36764, 36765, 36766, 36767, 36768, 36769, + 36770, 36771, 36772, 36773, 36774, 36775, 36776, 36777, + 36778, 36779, 36780, 36781, 36782, 36783, 36784, 36785, + 36786, 36787, 36788, 36789, 36790, 36791, 36792, 36793, + 36794, 36795, 36796, 36797, 36798, 36799, 36800, 36801, + 36802, 36803, 36804, 36805, 36806, 36807, 36808, 36809, + 36810, 36811, 36812, 36813, 36814, 36815, 36816, 36817, + 36818, 36819, 36820, 36821, 36822, 36823, 36824, 36825, + 36826, 36827, 36828, 36829, 36830, 36831, 36832, 36833, + 36834, 36835, 36836, 36837, 36838, 36839, 36840, 36841, + 36842, 36843, 36844, 36845, 36846, 36847, 36848, 36849, + 36850, 36851, 36852, 36853, 36854, 36855, 36856, 36857, + 36858, 36859, 36860, 36861, 36862, 36863, 36864, 36865, + 36866, 36867, 36868, 36869, 36870, 36871, 36872, 36873, + 36874, 36875, 36876, 36877, 36878, 36879, 36880, 36881, + 36882, 36883, 36884, 36885, 36886, 36887, 36888, 36889, + 36890, 36891, 36892, 36893, 36894, 36895, 36896, 36897, + 36898, 36899, 36900, 36901, 36902, 36903, 36904, 36905, + 36906, 36907, 36908, 36909, 36910, 36911, 36912, 36913, + 36914, 36915, 36916, 36917, 36918, 36919, 36920, 36921, + 36922, 36923, 36924, 36925, 36926, 36927, 36928, 36929, + 36930, 36931, 36932, 36933, 36934, 36935, 36936, 36937, + 36938, 36939, 36940, 36941, 36942, 36943, 36944, 36945, + 36946, 36947, 36948, 36949, 36950, 36951, 36952, 36953, + 36954, 36955, 36956, 36957, 36958, 36959, 36960, 36961, + 36962, 36963, 36964, 36965, 36966, 36967, 36968, 36969, + 36970, 36971, 36972, 36973, 36974, 36975, 36976, 36977, + 36978, 36979, 36980, 36981, 36982, 36983, 36984, 36985, + 36986, 36987, 36988, 36989, 36990, 36991, 36992, 36993, + 36994, 36995, 36996, 36997, 36998, 36999, 37000, 37001, + 37002, 37003, 37004, 37005, 37006, 37007, 37008, 37009, + 37010, 37011, 37012, 37013, 37014, 37015, 37016, 37017, + 37018, 37019, 37020, 37021, 37022, 37023, 37024, 37025, + 37026, 37027, 37028, 37029, 37030, 37031, 37032, 37033, + 37034, 37035, 37036, 37037, 37038, 37039, 37040, 37041, + 37042, 37043, 37044, 37045, 37046, 37047, 37048, 37049, + 37050, 37051, 37052, 37053, 37054, 37055, 37056, 37057, + 37058, 37059, 37060, 37061, 37062, 37063, 37064, 37065, + 37066, 37067, 37068, 37069, 37070, 37071, 37072, 37073, + 37074, 37075, 37076, 37077, 37078, 37079, 37080, 37081, + 37082, 37083, 37084, 37085, 37086, 37087, 37088, 37089, + 37090, 37091, 37092, 37093, 37094, 37095, 37096, 37097, + 37098, 37099, 37100, 37101, 37102, 37103, 37104, 37105, + 37106, 37107, 37108, 37109, 37110, 37111, 37112, 37113, + 37114, 37115, 37116, 37117, 37118, 37119, 37120, 37121, + 37122, 37123, 37124, 37125, 37126, 37127, 37128, 37129, + 37130, 37131, 37132, 37133, 37134, 37135, 37136, 37137, + 37138, 37139, 37140, 37141, 37142, 37143, 37144, 37145, + 37146, 37147, 37148, 37149, 37150, 37151, 37152, 37153, + 37154, 37155, 37156, 37157, 37158, 37159, 37160, 37161, + 37162, 37163, 37164, 37165, 37166, 37167, 37168, 37169, + 37170, 37171, 37172, 37173, 37174, 37175, 37176, 37177, + 37178, 37179, 37180, 37181, 37182, 37183, 37184, 37185, + 37186, 37187, 37188, 37189, 37190, 37191, 37192, 37193, + 37194, 37195, 37196, 37197, 37198, 37199, 37200, 37201, + 37202, 37203, 37204, 37205, 37206, 37207, 37208, 37209, + 37210, 37211, 37212, 37213, 37214, 37215, 37216, 37217, + 37218, 37219, 37220, 37221, 37222, 37223, 37224, 37225, + 37226, 37227, 37228, 37229, 37230, 37231, 37232, 37233, + 37234, 37235, 37236, 37237, 37238, 37239, 37240, 37241, + 37242, 37243, 37244, 37245, 37246, 37247, 37248, 37249, + 37250, 37251, 37252, 37253, 37254, 37255, 37256, 37257, + 37258, 37259, 37260, 37261, 37262, 37263, 37264, 37265, + 37266, 37267, 37268, 37269, 37270, 37271, 37272, 37273, + 37274, 37275, 37276, 37277, 37278, 37279, 37280, 37281, + 37282, 37283, 37284, 37285, 37286, 37287, 37288, 37289, + 37290, 37291, 37292, 37293, 37294, 37295, 37296, 37297, + 37298, 37299, 37300, 37301, 37302, 37303, 37304, 37305, + 37306, 37307, 37308, 37309, 37310, 37311, 37312, 37313, + 37314, 37315, 37316, 37317, 37318, 37319, 37320, 37321, + 37322, 37323, 37324, 37325, 37326, 37327, 37328, 37329, + 37330, 37331, 37332, 37333, 37334, 37335, 37336, 37337, + 37338, 37339, 37340, 37341, 37342, 37343, 37344, 37345, + 37346, 37347, 37348, 37349, 37350, 37351, 37352, 37353, + 37354, 37355, 37356, 37357, 37358, 37359, 37360, 37361, + 37362, 37363, 37364, 37365, 37366, 37367, 37368, 37369, + 37370, 37371, 37372, 37373, 37374, 37375, 37376, 37377, + 37378, 37379, 37380, 37381, 37382, 37383, 37384, 37385, + 37386, 37387, 37388, 37389, 37390, 37391, 37392, 37393, + 37394, 37395, 37396, 37397, 37398, 37399, 37400, 37401, + 37402, 37403, 37404, 37405, 37406, 37407, 37408, 37409, + 37410, 37411, 37412, 37413, 37414, 37415, 37416, 37417, + 37418, 37419, 37420, 37421, 37422, 37423, 37424, 37425, + 37426, 37427, 37428, 37429, 37430, 37431, 37432, 37433, + 37434, 37435, 37436, 37437, 37438, 37439, 37440, 37441, + 37442, 37443, 37444, 37445, 37446, 37447, 37448, 37449, + 37450, 37451, 37452, 37453, 37454, 37455, 37456, 37457, + 37458, 37459, 37460, 37461, 37462, 37463, 37464, 37465, + 37466, 37467, 37468, 37469, 37470, 37471, 37472, 37473, + 37474, 37475, 37476, 37477, 37478, 37479, 37480, 37481, + 37482, 37483, 37484, 37485, 37486, 37487, 37488, 37489, + 37490, 37491, 37492, 37493, 37494, 37495, 37496, 37497, + 37498, 37499, 37500, 37501, 37502, 37503, 37504, 37505, + 37506, 37507, 37508, 37509, 37510, 37511, 37512, 37513, + 37514, 37515, 37516, 37517, 37518, 37519, 37520, 37521, + 37522, 37523, 37524, 37525, 37526, 37527, 37528, 37529, + 37530, 37531, 37532, 37533, 37534, 37535, 37536, 37537, + 37538, 37539, 37540, 37541, 37542, 37543, 37544, 37545, + 37546, 37547, 37548, 37549, 37550, 37551, 37552, 37553, + 37554, 37555, 37556, 37557, 37558, 37559, 37560, 37561, + 37562, 37563, 37564, 37565, 37566, 37567, 37568, 37569, + 37570, 37571, 37572, 37573, 37574, 37575, 37576, 37577, + 37578, 37579, 37580, 37581, 37582, 37583, 37584, 37585, + 37586, 37587, 37588, 37589, 37590, 37591, 37592, 37593, + 37594, 37595, 37596, 37597, 37598, 37599, 37600, 37601, + 37602, 37603, 37604, 37605, 37606, 37607, 37608, 37609, + 37610, 37611, 37612, 37613, 37614, 37615, 37616, 37617, + 37618, 37619, 37620, 37621, 37622, 37623, 37624, 37625, + 37626, 37627, 37628, 37629, 37630, 37631, 37632, 37633, + 37634, 37635, 37636, 37637, 37638, 37639, 37640, 37641, + 37642, 37643, 37644, 37645, 37646, 37647, 37648, 37649, + 37650, 37651, 37652, 37653, 37654, 37655, 37656, 37657, + 37658, 37659, 37660, 37661, 37662, 37663, 37664, 37665, + 37666, 37667, 37668, 37669, 37670, 37671, 37672, 37673, + 37674, 37675, 37676, 37677, 37678, 37679, 37680, 37681, + 37682, 37683, 37684, 37685, 37686, 37687, 37688, 37689, + 37690, 37691, 37692, 37693, 37694, 37695, 37696, 37697, + 37698, 37699, 37700, 37701, 37702, 37703, 37704, 37705, + 37706, 37707, 37708, 37709, 37710, 37711, 37712, 37713, + 37714, 37715, 37716, 37717, 37718, 37719, 37720, 37721, + 37722, 37723, 37724, 37725, 37726, 37727, 37728, 37729, + 37730, 37731, 37732, 37733, 37734, 37735, 37736, 37737, + 37738, 37739, 37740, 37741, 37742, 37743, 37744, 37745, + 37746, 37747, 37748, 37749, 37750, 37751, 37752, 37753, + 37754, 37755, 37756, 37757, 37758, 37759, 37760, 37761, + 37762, 37763, 37764, 37765, 37766, 37767, 37768, 37769, + 37770, 37771, 37772, 37773, 37774, 37775, 37776, 37777, + 37778, 37779, 37780, 37781, 37782, 37783, 37784, 37785, + 37786, 37787, 37788, 37789, 37790, 37791, 37792, 37793, + 37794, 37795, 37796, 37797, 37798, 37799, 37800, 37801, + 37802, 37803, 37804, 37805, 37806, 37807, 37808, 37809, + 37810, 37811, 37812, 37813, 37814, 37815, 37816, 37817, + 37818, 37819, 37820, 37821, 37822, 37823, 37824, 37825, + 37826, 37827, 37828, 37829, 37830, 37831, 37832, 37833, + 37834, 37835, 37836, 37837, 37838, 37839, 37840, 37841, + 37842, 37843, 37844, 37845, 37846, 37847, 37848, 37849, + 37850, 37851, 37852, 37853, 37854, 37855, 37856, 37857, + 37858, 37859, 37860, 37861, 37862, 37863, 37864, 37865, + 37866, 37867, 37868, 37869, 37870, 37871, 37872, 37873, + 37874, 37875, 37876, 37877, 37878, 37879, 37880, 37881, + 37882, 37883, 37884, 37885, 37886, 37887, 37888, 37889, + 37890, 37891, 37892, 37893, 37894, 37895, 37896, 37897, + 37898, 37899, 37900, 37901, 37902, 37903, 37904, 37905, + 37906, 37907, 37908, 37909, 37910, 37911, 37912, 37913, + 37914, 37915, 37916, 37917, 37918, 37919, 37920, 37921, + 37922, 37923, 37924, 37925, 37926, 37927, 37928, 37929, + 37930, 37931, 37932, 37933, 37934, 37935, 37936, 37937, + 37938, 37939, 37940, 37941, 37942, 37943, 37944, 37945, + 37946, 37947, 37948, 37949, 37950, 37951, 37952, 37953, + 37954, 37955, 37956, 37957, 37958, 37959, 37960, 37961, + 37962, 37963, 37964, 37965, 37966, 37967, 37968, 37969, + 37970, 37971, 37972, 37973, 37974, 37975, 37976, 37977, + 37978, 37979, 37980, 37981, 37982, 37983, 37984, 37985, + 37986, 37987, 37988, 37989, 37990, 37991, 37992, 37993, + 37994, 37995, 37996, 37997, 37998, 37999, 38000, 38001, + 38002, 38003, 38004, 38005, 38006, 38007, 38008, 38009, + 38010, 38011, 38012, 38013, 38014, 38015, 38016, 38017, + 38018, 38019, 38020, 38021, 38022, 38023, 38024, 38025, + 38026, 38027, 38028, 38029, 38030, 38031, 38032, 38033, + 38034, 38035, 38036, 38037, 38038, 38039, 38040, 38041, + 38042, 38043, 38044, 38045, 38046, 38047, 38048, 38049, + 38050, 38051, 38052, 38053, 38054, 38055, 38056, 38057, + 38058, 38059, 38060, 38061, 38062, 38063, 38064, 38065, + 38066, 38067, 38068, 38069, 38070, 38071, 38072, 38073, + 38074, 38075, 38076, 38077, 38078, 38079, 38080, 38081, + 38082, 38083, 38084, 38085, 38086, 38087, 38088, 38089, + 38090, 38091, 38092, 38093, 38094, 38095, 38096, 38097, + 38098, 38099, 38100, 38101, 38102, 38103, 38104, 38105, + 38106, 38107, 38108, 38109, 38110, 38111, 38112, 38113, + 38114, 38115, 38116, 38117, 38118, 38119, 38120, 38121, + 38122, 38123, 38124, 38125, 38126, 38127, 38128, 38129, + 38130, 38131, 38132, 38133, 38134, 38135, 38136, 38137, + 38138, 38139, 38140, 38141, 38142, 38143, 38144, 38145, + 38146, 38147, 38148, 38149, 38150, 38151, 38152, 38153, + 38154, 38155, 38156, 38157, 38158, 38159, 38160, 38161, + 38162, 38163, 38164, 38165, 38166, 38167, 38168, 38169, + 38170, 38171, 38172, 38173, 38174, 38175, 38176, 38177, + 38178, 38179, 38180, 38181, 38182, 38183, 38184, 38185, + 38186, 38187, 38188, 38189, 38190, 38191, 38192, 38193, + 38194, 38195, 38196, 38197, 38198, 38199, 38200, 38201, + 38202, 38203, 38204, 38205, 38206, 38207, 38208, 38209, + 38210, 38211, 38212, 38213, 38214, 38215, 38216, 38217, + 38218, 38219, 38220, 38221, 38222, 38223, 38224, 38225, + 38226, 38227, 38228, 38229, 38230, 38231, 38232, 38233, + 38234, 38235, 38236, 38237, 38238, 38239, 38240, 38241, + 38242, 38243, 38244, 38245, 38246, 38247, 38248, 38249, + 38250, 38251, 38252, 38253, 38254, 38255, 38256, 38257, + 38258, 38259, 38260, 38261, 38262, 38263, 38264, 38265, + 38266, 38267, 38268, 38269, 38270, 38271, 38272, 38273, + 38274, 38275, 38276, 38277, 38278, 38279, 38280, 38281, + 38282, 38283, 38284, 38285, 38286, 38287, 38288, 38289, + 38290, 38291, 38292, 38293, 38294, 38295, 38296, 38297, + 38298, 38299, 38300, 38301, 38302, 38303, 38304, 38305, + 38306, 38307, 38308, 38309, 38310, 38311, 38312, 38313, + 38314, 38315, 38316, 38317, 38318, 38319, 38320, 38321, + 38322, 38323, 38324, 38325, 38326, 38327, 38328, 38329, + 38330, 38331, 38332, 38333, 38334, 38335, 38336, 38337, + 38338, 38339, 38340, 38341, 38342, 38343, 38344, 38345, + 38346, 38347, 38348, 38349, 38350, 38351, 38352, 38353, + 38354, 38355, 38356, 38357, 38358, 38359, 38360, 38361, + 38362, 38363, 38364, 38365, 38366, 38367, 38368, 38369, + 38370, 38371, 38372, 38373, 38374, 38375, 38376, 38377, + 38378, 38379, 38380, 38381, 38382, 38383, 38384, 38385, + 38386, 38387, 38388, 38389, 38390, 38391, 38392, 38393, + 38394, 38395, 38396, 38397, 38398, 38399, 38400, 38401, + 38402, 38403, 38404, 38405, 38406, 38407, 38408, 38409, + 38410, 38411, 38412, 38413, 38414, 38415, 38416, 38417, + 38418, 38419, 38420, 38421, 38422, 38423, 38424, 38425, + 38426, 38427, 38428, 38429, 38430, 38431, 38432, 38433, + 38434, 38435, 38436, 38437, 38438, 38439, 38440, 38441, + 38442, 38443, 38444, 38445, 38446, 38447, 38448, 38449, + 38450, 38451, 38452, 38453, 38454, 38455, 38456, 38457, + 38458, 38459, 38460, 38461, 38462, 38463, 38464, 38465, + 38466, 38467, 38468, 38469, 38470, 38471, 38472, 38473, + 38474, 38475, 38476, 38477, 38478, 38479, 38480, 38481, + 38482, 38483, 38484, 38485, 38486, 38487, 38488, 38489, + 38490, 38491, 38492, 38493, 38494, 38495, 38496, 38497, + 38498, 38499, 38500, 38501, 38502, 38503, 38504, 38505, + 38506, 38507, 38508, 38509, 38510, 38511, 38512, 38513, + 38514, 38515, 38516, 38517, 38518, 38519, 38520, 38521, + 38522, 38523, 38524, 38525, 38526, 38527, 38528, 38529, + 38530, 38531, 38532, 38533, 38534, 38535, 38536, 38537, + 38538, 38539, 38540, 38541, 38542, 38543, 38544, 38545, + 38546, 38547, 38548, 38549, 38550, 38551, 38552, 38553, + 38554, 38555, 38556, 38557, 38558, 38559, 38560, 38561, + 38562, 38563, 38564, 38565, 38566, 38567, 38568, 38569, + 38570, 38571, 38572, 38573, 38574, 38575, 38576, 38577, + 38578, 38579, 38580, 38581, 38582, 38583, 38584, 38585, + 38586, 38587, 38588, 38589, 38590, 38591, 38592, 38593, + 38594, 38595, 38596, 38597, 38598, 38599, 38600, 38601, + 38602, 38603, 38604, 38605, 38606, 38607, 38608, 38609, + 38610, 38611, 38612, 38613, 38614, 38615, 38616, 38617, + 38618, 38619, 38620, 38621, 38622, 38623, 38624, 38625, + 38626, 38627, 38628, 38629, 38630, 38631, 38632, 38633, + 38634, 38635, 38636, 38637, 38638, 38639, 38640, 38641, + 38642, 38643, 38644, 38645, 38646, 38647, 38648, 38649, + 38650, 38651, 38652, 38653, 38654, 38655, 38656, 38657, + 38658, 38659, 38660, 38661, 38662, 38663, 38664, 38665, + 38666, 38667, 38668, 38669, 38670, 38671, 38672, 38673, + 38674, 38675, 38676, 38677, 38678, 38679, 38680, 38681, + 38682, 38683, 38684, 38685, 38686, 38687, 38688, 38689, + 38690, 38691, 38692, 38693, 38694, 38695, 38696, 38697, + 38698, 38699, 38700, 38701, 38702, 38703, 38704, 38705, + 38706, 38707, 38708, 38709, 38710, 38711, 38712, 38713, + 38714, 38715, 38716, 38717, 38718, 38719, 38720, 38721, + 38722, 38723, 38724, 38725, 38726, 38727, 38728, 38729, + 38730, 38731, 38732, 38733, 38734, 38735, 38736, 38737, + 38738, 38739, 38740, 38741, 38742, 38743, 38744, 38745, + 38746, 38747, 38748, 38749, 38750, 38751, 38752, 38753, + 38754, 38755, 38756, 38757, 38758, 38759, 38760, 38761, + 38762, 38763, 38764, 38765, 38766, 38767, 38768, 38769, + 38770, 38771, 38772, 38773, 38774, 38775, 38776, 38777, + 38778, 38779, 38780, 38781, 38782, 38783, 38784, 38785, + 38786, 38787, 38788, 38789, 38790, 38791, 38792, 38793, + 38794, 38795, 38796, 38797, 38798, 38799, 38800, 38801, + 38802, 38803, 38804, 38805, 38806, 38807, 38808, 38809, + 38810, 38811, 38812, 38813, 38814, 38815, 38816, 38817, + 38818, 38819, 38820, 38821, 38822, 38823, 38824, 38825, + 38826, 38827, 38828, 38829, 38830, 38831, 38832, 38833, + 38834, 38835, 38836, 38837, 38838, 38839, 38840, 38841, + 38842, 38843, 38844, 38845, 38846, 38847, 38848, 38849, + 38850, 38851, 38852, 38853, 38854, 38855, 38856, 38857, + 38858, 38859, 38860, 38861, 38862, 38863, 38864, 38865, + 38866, 38867, 38868, 38869, 38870, 38871, 38872, 38873, + 38874, 38875, 38876, 38877, 38878, 38879, 38880, 38881, + 38882, 38883, 38884, 38885, 38886, 38887, 38888, 38889, + 38890, 38891, 38892, 38893, 38894, 38895, 38896, 38897, + 38898, 38899, 38900, 38901, 38902, 38903, 38904, 38905, + 38906, 38907, 38908, 38909, 38910, 38911, 38912, 38913, + 38914, 38915, 38916, 38917, 38918, 38919, 38920, 38921, + 38922, 38923, 38924, 38925, 38926, 38927, 38928, 38929, + 38930, 38931, 38932, 38933, 38934, 38935, 38936, 38937, + 38938, 38939, 38940, 38941, 38942, 38943, 38944, 38945, + 38946, 38947, 38948, 38949, 38950, 38951, 38952, 38953, + 38954, 38955, 38956, 38957, 38958, 38959, 38960, 38961, + 38962, 38963, 38964, 38965, 38966, 38967, 38968, 38969, + 38970, 38971, 38972, 38973, 38974, 38975, 38976, 38977, + 38978, 38979, 38980, 38981, 38982, 38983, 38984, 38985, + 38986, 38987, 38988, 38989, 38990, 38991, 38992, 38993, + 38994, 38995, 38996, 38997, 38998, 38999, 39000, 39001, + 39002, 39003, 39004, 39005, 39006, 39007, 39008, 39009, + 39010, 39011, 39012, 39013, 39014, 39015, 39016, 39017, + 39018, 39019, 39020, 39021, 39022, 39023, 39024, 39025, + 39026, 39027, 39028, 39029, 39030, 39031, 39032, 39033, + 39034, 39035, 39036, 39037, 39038, 39039, 39040, 39041, + 39042, 39043, 39044, 39045, 39046, 39047, 39048, 39049, + 39050, 39051, 39052, 39053, 39054, 39055, 39056, 39057, + 39058, 39059, 39060, 39061, 39062, 39063, 39064, 39065, + 39066, 39067, 39068, 39069, 39070, 39071, 39072, 39073, + 39074, 39075, 39076, 39077, 39078, 39079, 39080, 39081, + 39082, 39083, 39084, 39085, 39086, 39087, 39088, 39089, + 39090, 39091, 39092, 39093, 39094, 39095, 39096, 39097, + 39098, 39099, 39100, 39101, 39102, 39103, 39104, 39105, + 39106, 39107, 39108, 39109, 39110, 39111, 39112, 39113, + 39114, 39115, 39116, 39117, 39118, 39119, 39120, 39121, + 39122, 39123, 39124, 39125, 39126, 39127, 39128, 39129, + 39130, 39131, 39132, 39133, 39134, 39135, 39136, 39137, + 39138, 39139, 39140, 39141, 39142, 39143, 39144, 39145, + 39146, 39147, 39148, 39149, 39150, 39151, 39152, 39153, + 39154, 39155, 39156, 39157, 39158, 39159, 39160, 39161, + 39162, 39163, 39164, 39165, 39166, 39167, 39168, 39169, + 39170, 39171, 39172, 39173, 39174, 39175, 39176, 39177, + 39178, 39179, 39180, 39181, 39182, 39183, 39184, 39185, + 39186, 39187, 39188, 39189, 39190, 39191, 39192, 39193, + 39194, 39195, 39196, 39197, 39198, 39199, 39200, 39201, + 39202, 39203, 39204, 39205, 39206, 39207, 39208, 39209, + 39210, 39211, 39212, 39213, 39214, 39215, 39216, 39217, + 39218, 39219, 39220, 39221, 39222, 39223, 39224, 39225, + 39226, 39227, 39228, 39229, 39230, 39231, 39232, 39233, + 39234, 39235, 39236, 39237, 39238, 39239, 39240, 39241, + 39242, 39243, 39244, 39245, 39246, 39247, 39248, 39249, + 39250, 39251, 39252, 39253, 39254, 39255, 39256, 39257, + 39258, 39259, 39260, 39261, 39262, 39263, 39264, 39265, + 39266, 39267, 39268, 39269, 39270, 39271, 39272, 39273, + 39274, 39275, 39276, 39277, 39278, 39279, 39280, 39281, + 39282, 39283, 39284, 39285, 39286, 39287, 39288, 39289, + 39290, 39291, 39292, 39293, 39294, 39295, 39296, 39297, + 39298, 39299, 39300, 39301, 39302, 39303, 39304, 39305, + 39306, 39307, 39308, 39309, 39310, 39311, 39312, 39313, + 39314, 39315, 39316, 39317, 39318, 39319, 39320, 39321, + 39322, 39323, 39324, 39325, 39326, 39327, 39328, 39329, + 39330, 39331, 39332, 39333, 39334, 39335, 39336, 39337, + 39338, 39339, 39340, 39341, 39342, 39343, 39344, 39345, + 39346, 39347, 39348, 39349, 39350, 39351, 39352, 39353, + 39354, 39355, 39356, 39357, 39358, 39359, 39360, 39361, + 39362, 39363, 39364, 39365, 39366, 39367, 39368, 39369, + 39370, 39371, 39372, 39373, 39374, 39375, 39376, 39377, + 39378, 39379, 39380, 39381, 39382, 39383, 39384, 39385, + 39386, 39387, 39388, 39389, 39390, 39391, 39392, 39393, + 39394, 39395, 39396, 39397, 39398, 39399, 39400, 39401, + 39402, 39403, 39404, 39405, 39406, 39407, 39408, 39409, + 39410, 39411, 39412, 39413, 39414, 39415, 39416, 39417, + 39418, 39419, 39420, 39421, 39422, 39423, 39424, 39425, + 39426, 39427, 39428, 39429, 39430, 39431, 39432, 39433, + 39434, 39435, 39436, 39437, 39438, 39439, 39440, 39441, + 39442, 39443, 39444, 39445, 39446, 39447, 39448, 39449, + 39450, 39451, 39452, 39453, 39454, 39455, 39456, 39457, + 39458, 39459, 39460, 39461, 39462, 39463, 39464, 39465, + 39466, 39467, 39468, 39469, 39470, 39471, 39472, 39473, + 39474, 39475, 39476, 39477, 39478, 39479, 39480, 39481, + 39482, 39483, 39484, 39485, 39486, 39487, 39488, 39489, + 39490, 39491, 39492, 39493, 39494, 39495, 39496, 39497, + 39498, 39499, 39500, 39501, 39502, 39503, 39504, 39505, + 39506, 39507, 39508, 39509, 39510, 39511, 39512, 39513, + 39514, 39515, 39516, 39517, 39518, 39519, 39520, 39521, + 39522, 39523, 39524, 39525, 39526, 39527, 39528, 39529, + 39530, 39531, 39532, 39533, 39534, 39535, 39536, 39537, + 39538, 39539, 39540, 39541, 39542, 39543, 39544, 39545, + 39546, 39547, 39548, 39549, 39550, 39551, 39552, 39553, + 39554, 39555, 39556, 39557, 39558, 39559, 39560, 39561, + 39562, 39563, 39564, 39565, 39566, 39567, 39568, 39569, + 39570, 39571, 39572, 39573, 39574, 39575, 39576, 39577, + 39578, 39579, 39580, 39581, 39582, 39583, 39584, 39585, + 39586, 39587, 39588, 39589, 39590, 39591, 39592, 39593, + 39594, 39595, 39596, 39597, 39598, 39599, 39600, 39601, + 39602, 39603, 39604, 39605, 39606, 39607, 39608, 39609, + 39610, 39611, 39612, 39613, 39614, 39615, 39616, 39617, + 39618, 39619, 39620, 39621, 39622, 39623, 39624, 39625, + 39626, 39627, 39628, 39629, 39630, 39631, 39632, 39633, + 39634, 39635, 39636, 39637, 39638, 39639, 39640, 39641, + 39642, 39643, 39644, 39645, 39646, 39647, 39648, 39649, + 39650, 39651, 39652, 39653, 39654, 39655, 39656, 39657, + 39658, 39659, 39660, 39661, 39662, 39663, 39664, 39665, + 39666, 39667, 39668, 39669, 39670, 39671, 39672, 39673, + 39674, 39675, 39676, 39677, 39678, 39679, 39680, 39681, + 39682, 39683, 39684, 39685, 39686, 39687, 39688, 39689, + 39690, 39691, 39692, 39693, 39694, 39695, 39696, 39697, + 39698, 39699, 39700, 39701, 39702, 39703, 39704, 39705, + 39706, 39707, 39708, 39709, 39710, 39711, 39712, 39713, + 39714, 39715, 39716, 39717, 39718, 39719, 39720, 39721, + 39722, 39723, 39724, 39725, 39726, 39727, 39728, 39729, + 39730, 39731, 39732, 39733, 39734, 39735, 39736, 39737, + 39738, 39739, 39740, 39741, 39742, 39743, 39744, 39745, + 39746, 39747, 39748, 39749, 39750, 39751, 39752, 39753, + 39754, 39755, 39756, 39757, 39758, 39759, 39760, 39761, + 39762, 39763, 39764, 39765, 39766, 39767, 39768, 39769, + 39770, 39771, 39772, 39773, 39774, 39775, 39776, 39777, + 39778, 39779, 39780, 39781, 39782, 39783, 39784, 39785, + 39786, 39787, 39788, 39789, 39790, 39791, 39792, 39793, + 39794, 39795, 39796, 39797, 39798, 39799, 39800, 39801, + 39802, 39803, 39804, 39805, 39806, 39807, 39808, 39809, + 39810, 39811, 39812, 39813, 39814, 39815, 39816, 39817, + 39818, 39819, 39820, 39821, 39822, 39823, 39824, 39825, + 39826, 39827, 39828, 39829, 39830, 39831, 39832, 39833, + 39834, 39835, 39836, 39837, 39838, 39839, 39840, 39841, + 39842, 39843, 39844, 39845, 39846, 39847, 39848, 39849, + 39850, 39851, 39852, 39853, 39854, 39855, 39856, 39857, + 39858, 39859, 39860, 39861, 39862, 39863, 39864, 39865, + 39866, 39867, 39868, 39869, 39870, 39871, 39872, 39873, + 39874, 39875, 39876, 39877, 39878, 39879, 39880, 39881, + 39882, 39883, 39884, 39885, 39886, 39887, 39888, 39889, + 39890, 39891, 39892, 39893, 39894, 39895, 39896, 39897, + 39898, 39899, 39900, 39901, 39902, 39903, 39904, 39905, + 39906, 39907, 39908, 39909, 39910, 39911, 39912, 39913, + 39914, 39915, 39916, 39917, 39918, 39919, 39920, 39921, + 39922, 39923, 39924, 39925, 39926, 39927, 39928, 39929, + 39930, 39931, 39932, 39933, 39934, 39935, 39936, 39937, + 39938, 39939, 39940, 39941, 39942, 39943, 39944, 39945, + 39946, 39947, 39948, 39949, 39950, 39951, 39952, 39953, + 39954, 39955, 39956, 39957, 39958, 39959, 39960, 39961, + 39962, 39963, 39964, 39965, 39966, 39967, 39968, 39969, + 39970, 39971, 39972, 39973, 39974, 39975, 39976, 39977, + 39978, 39979, 39980, 39981, 39982, 39983, 39984, 39985, + 39986, 39987, 39988, 39989, 39990, 39991, 39992, 39993, + 39994, 39995, 39996, 39997, 39998, 39999, 40000, 40001, + 40002, 40003, 40004, 40005, 40006, 40007, 40008, 40009, + 40010, 40011, 40012, 40013, 40014, 40015, 40016, 40017, + 40018, 40019, 40020, 40021, 40022, 40023, 40024, 40025, + 40026, 40027, 40028, 40029, 40030, 40031, 40032, 40033, + 40034, 40035, 40036, 40037, 40038, 40039, 40040, 40041, + 40042, 40043, 40044, 40045, 40046, 40047, 40048, 40049, + 40050, 40051, 40052, 40053, 40054, 40055, 40056, 40057, + 40058, 40059, 40060, 40061, 40062, 40063, 40064, 40065, + 40066, 40067, 40068, 40069, 40070, 40071, 40072, 40073, + 40074, 40075, 40076, 40077, 40078, 40079, 40080, 40081, + 40082, 40083, 40084, 40085, 40086, 40087, 40088, 40089, + 40090, 40091, 40092, 40093, 40094, 40095, 40096, 40097, + 40098, 40099, 40100, 40101, 40102, 40103, 40104, 40105, + 40106, 40107, 40108, 40109, 40110, 40111, 40112, 40113, + 40114, 40115, 40116, 40117, 40118, 40119, 40120, 40121, + 40122, 40123, 40124, 40125, 40126, 40127, 40128, 40129, + 40130, 40131, 40132, 40133, 40134, 40135, 40136, 40137, + 40138, 40139, 40140, 40141, 40142, 40143, 40144, 40145, + 40146, 40147, 40148, 40149, 40150, 40151, 40152, 40153, + 40154, 40155, 40156, 40157, 40158, 40159, 40160, 40161, + 40162, 40163, 40164, 40165, 40166, 40167, 40168, 40169, + 40170, 40171, 40172, 40173, 40174, 40175, 40176, 40177, + 40178, 40179, 40180, 40181, 40182, 40183, 40184, 40185, + 40186, 40187, 40188, 40189, 40190, 40191, 40192, 40193, + 40194, 40195, 40196, 40197, 40198, 40199, 40200, 40201, + 40202, 40203, 40204, 40205, 40206, 40207, 40208, 40209, + 40210, 40211, 40212, 40213, 40214, 40215, 40216, 40217, + 40218, 40219, 40220, 40221, 40222, 40223, 40224, 40225, + 40226, 40227, 40228, 40229, 40230, 40231, 40232, 40233, + 40234, 40235, 40236, 40237, 40238, 40239, 40240, 40241, + 40242, 40243, 40244, 40245, 40246, 40247, 40248, 40249, + 40250, 40251, 40252, 40253, 40254, 40255, 40256, 40257, + 40258, 40259, 40260, 40261, 40262, 40263, 40264, 40265, + 40266, 40267, 40268, 40269, 40270, 40271, 40272, 40273, + 40274, 40275, 40276, 40277, 40278, 40279, 40280, 40281, + 40282, 40283, 40284, 40285, 40286, 40287, 40288, 40289, + 40290, 40291, 40292, 40293, 40294, 40295, 40296, 40297, + 40298, 40299, 40300, 40301, 40302, 40303, 40304, 40305, + 40306, 40307, 40308, 40309, 40310, 40311, 40312, 40313, + 40314, 40315, 40316, 40317, 40318, 40319, 40320, 40321, + 40322, 40323, 40324, 40325, 40326, 40327, 40328, 40329, + 40330, 40331, 40332, 40333, 40334, 40335, 40336, 40337, + 40338, 40339, 40340, 40341, 40342, 40343, 40344, 40345, + 40346, 40347, 40348, 40349, 40350, 40351, 40352, 40353, + 40354, 40355, 40356, 40357, 40358, 40359, 40360, 40361, + 40362, 40363, 40364, 40365, 40366, 40367, 40368, 40369, + 40370, 40371, 40372, 40373, 40374, 40375, 40376, 40377, + 40378, 40379, 40380, 40381, 40382, 40383, 40384, 40385, + 40386, 40387, 40388, 40389, 40390, 40391, 40392, 40393, + 40394, 40395, 40396, 40397, 40398, 40399, 40400, 40401, + 40402, 40403, 40404, 40405, 40406, 40407, 40408, 40409, + 40410, 40411, 40412, 40413, 40414, 40415, 40416, 40417, + 40418, 40419, 40420, 40421, 40422, 40423, 40424, 40425, + 40426, 40427, 40428, 40429, 40430, 40431, 40432, 40433, + 40434, 40435, 40436, 40437, 40438, 40439, 40440, 40441, + 40442, 40443, 40444, 40445, 40446, 40447, 40448, 40449, + 40450, 40451, 40452, 40453, 40454, 40455, 40456, 40457, + 40458, 40459, 40460, 40461, 40462, 40463, 40464, 40465, + 40466, 40467, 40468, 40469, 40470, 40471, 40472, 40473, + 40474, 40475, 40476, 40477, 40478, 40479, 40480, 40481, + 40482, 40483, 40484, 40485, 40486, 40487, 40488, 40489, + 40490, 40491, 40492, 40493, 40494, 40495, 40496, 40497, + 40498, 40499, 40500, 40501, 40502, 40503, 40504, 40505, + 40506, 40507, 40508, 40509, 40510, 40511, 40512, 40513, + 40514, 40515, 40516, 40517, 40518, 40519, 40520, 40521, + 40522, 40523, 40524, 40525, 40526, 40527, 40528, 40529, + 40530, 40531, 40532, 40533, 40534, 40535, 40536, 40537, + 40538, 40539, 40540, 40541, 40542, 40543, 40544, 40545, + 40546, 40547, 40548, 40549, 40550, 40551, 40552, 40553, + 40554, 40555, 40556, 40557, 40558, 40559, 40560, 40561, + 40562, 40563, 40564, 40565, 40566, 40567, 40568, 40569, + 40570, 40571, 40572, 40573, 40574, 40575, 40576, 40577, + 40578, 40579, 40580, 40581, 40582, 40583, 40584, 40585, + 40586, 40587, 40588, 40589, 40590, 40591, 40592, 40593, + 40594, 40595, 40596, 40597, 40598, 40599, 40600, 40601, + 40602, 40603, 40604, 40605, 40606, 40607, 40608, 40609, + 40610, 40611, 40612, 40613, 40614, 40615, 40616, 40617, + 40618, 40619, 40620, 40621, 40622, 40623, 40624, 40625, + 40626, 40627, 40628, 40629, 40630, 40631, 40632, 40633, + 40634, 40635, 40636, 40637, 40638, 40639, 40640, 40641, + 40642, 40643, 40644, 40645, 40646, 40647, 40648, 40649, + 40650, 40651, 40652, 40653, 40654, 40655, 40656, 40657, + 40658, 40659, 40660, 40661, 40662, 40663, 40664, 40665, + 40666, 40667, 40668, 40669, 40670, 40671, 40672, 40673, + 40674, 40675, 40676, 40677, 40678, 40679, 40680, 40681, + 40682, 40683, 40684, 40685, 40686, 40687, 40688, 40689, + 40690, 40691, 40692, 40693, 40694, 40695, 40696, 40697, + 40698, 40699, 40700, 40701, 40702, 40703, 40704, 40705, + 40706, 40707, 40708, 40709, 40710, 40711, 40712, 40713, + 40714, 40715, 40716, 40717, 40718, 40719, 40720, 40721, + 40722, 40723, 40724, 40725, 40726, 40727, 40728, 40729, + 40730, 40731, 40732, 40733, 40734, 40735, 40736, 40737, + 40738, 40739, 40740, 40741, 40742, 40743, 40744, 40745, + 40746, 40747, 40748, 40749, 40750, 40751, 40752, 40753, + 40754, 40755, 40756, 40757, 40758, 40759, 40760, 40761, + 40762, 40763, 40764, 40765, 40766, 40767, 40768, 40769, + 40770, 40771, 40772, 40773, 40774, 40775, 40776, 40777, + 40778, 40779, 40780, 40781, 40782, 40783, 40784, 40785, + 40786, 40787, 40788, 40789, 40790, 40791, 40792, 40793, + 40794, 40795, 40796, 40797, 40798, 40799, 40800, 40801, + 40802, 40803, 40804, 40805, 40806, 40807, 40808, 40809, + 40810, 40811, 40812, 40813, 40814, 40815, 40816, 40817, + 40818, 40819, 40820, 40821, 40822, 40823, 40824, 40825, + 40826, 40827, 40828, 40829, 40830, 40831, 40832, 40833, + 40834, 40835, 40836, 40837, 40838, 40839, 40840, 40841, + 40842, 40843, 40844, 40845, 40846, 40847, 40848, 40849, + 40850, 40851, 40852, 40853, 40854, 40855, 40856, 40857, + 40858, 40859, 40860, 40861, 40862, 40863, 40864, 40865, + 40866, 40867, 40868, 40869, 40870, 40871, 40872, 40873, + 40874, 40875, 40876, 40877, 40878, 40879, 40880, 40881, + 40882, 40883, 40884, 40885, 40886, 40887, 40888, 40889, + 40890, 40891, 40892, 40893, 40894, 40895, 40896, 40897, + 40898, 40899, 40900, 40901, 40902, 40903, 40904, 40905, + 40906, 40907, 40908, 40960, 40961, 40962, 40963, 40964, + 40965, 40966, 40967, 40968, 40969, 40970, 40971, 40972, + 40973, 40974, 40975, 40976, 40977, 40978, 40979, 40980, + 40981, 40982, 40983, 40984, 40985, 40986, 40987, 40988, + 40989, 40990, 40991, 40992, 40993, 40994, 40995, 40996, + 40997, 40998, 40999, 41000, 41001, 41002, 41003, 41004, + 41005, 41006, 41007, 41008, 41009, 41010, 41011, 41012, + 41013, 41014, 41015, 41016, 41017, 41018, 41019, 41020, + 41021, 41022, 41023, 41024, 41025, 41026, 41027, 41028, + 41029, 41030, 41031, 41032, 41033, 41034, 41035, 41036, + 41037, 41038, 41039, 41040, 41041, 41042, 41043, 41044, + 41045, 41046, 41047, 41048, 41049, 41050, 41051, 41052, + 41053, 41054, 41055, 41056, 41057, 41058, 41059, 41060, + 41061, 41062, 41063, 41064, 41065, 41066, 41067, 41068, + 41069, 41070, 41071, 41072, 41073, 41074, 41075, 41076, + 41077, 41078, 41079, 41080, 41081, 41082, 41083, 41084, + 41085, 41086, 41087, 41088, 41089, 41090, 41091, 41092, + 41093, 41094, 41095, 41096, 41097, 41098, 41099, 41100, + 41101, 41102, 41103, 41104, 41105, 41106, 41107, 41108, + 41109, 41110, 41111, 41112, 41113, 41114, 41115, 41116, + 41117, 41118, 41119, 41120, 41121, 41122, 41123, 41124, + 41125, 41126, 41127, 41128, 41129, 41130, 41131, 41132, + 41133, 41134, 41135, 41136, 41137, 41138, 41139, 41140, + 41141, 41142, 41143, 41144, 41145, 41146, 41147, 41148, + 41149, 41150, 41151, 41152, 41153, 41154, 41155, 41156, + 41157, 41158, 41159, 41160, 41161, 41162, 41163, 41164, + 41165, 41166, 41167, 41168, 41169, 41170, 41171, 41172, + 41173, 41174, 41175, 41176, 41177, 41178, 41179, 41180, + 41181, 41182, 41183, 41184, 41185, 41186, 41187, 41188, + 41189, 41190, 41191, 41192, 41193, 41194, 41195, 41196, + 41197, 41198, 41199, 41200, 41201, 41202, 41203, 41204, + 41205, 41206, 41207, 41208, 41209, 41210, 41211, 41212, + 41213, 41214, 41215, 41216, 41217, 41218, 41219, 41220, + 41221, 41222, 41223, 41224, 41225, 41226, 41227, 41228, + 41229, 41230, 41231, 41232, 41233, 41234, 41235, 41236, + 41237, 41238, 41239, 41240, 41241, 41242, 41243, 41244, + 41245, 41246, 41247, 41248, 41249, 41250, 41251, 41252, + 41253, 41254, 41255, 41256, 41257, 41258, 41259, 41260, + 41261, 41262, 41263, 41264, 41265, 41266, 41267, 41268, + 41269, 41270, 41271, 41272, 41273, 41274, 41275, 41276, + 41277, 41278, 41279, 41280, 41281, 41282, 41283, 41284, + 41285, 41286, 41287, 41288, 41289, 41290, 41291, 41292, + 41293, 41294, 41295, 41296, 41297, 41298, 41299, 41300, + 41301, 41302, 41303, 41304, 41305, 41306, 41307, 41308, + 41309, 41310, 41311, 41312, 41313, 41314, 41315, 41316, + 41317, 41318, 41319, 41320, 41321, 41322, 41323, 41324, + 41325, 41326, 41327, 41328, 41329, 41330, 41331, 41332, + 41333, 41334, 41335, 41336, 41337, 41338, 41339, 41340, + 41341, 41342, 41343, 41344, 41345, 41346, 41347, 41348, + 41349, 41350, 41351, 41352, 41353, 41354, 41355, 41356, + 41357, 41358, 41359, 41360, 41361, 41362, 41363, 41364, + 41365, 41366, 41367, 41368, 41369, 41370, 41371, 41372, + 41373, 41374, 41375, 41376, 41377, 41378, 41379, 41380, + 41381, 41382, 41383, 41384, 41385, 41386, 41387, 41388, + 41389, 41390, 41391, 41392, 41393, 41394, 41395, 41396, + 41397, 41398, 41399, 41400, 41401, 41402, 41403, 41404, + 41405, 41406, 41407, 41408, 41409, 41410, 41411, 41412, + 41413, 41414, 41415, 41416, 41417, 41418, 41419, 41420, + 41421, 41422, 41423, 41424, 41425, 41426, 41427, 41428, + 41429, 41430, 41431, 41432, 41433, 41434, 41435, 41436, + 41437, 41438, 41439, 41440, 41441, 41442, 41443, 41444, + 41445, 41446, 41447, 41448, 41449, 41450, 41451, 41452, + 41453, 41454, 41455, 41456, 41457, 41458, 41459, 41460, + 41461, 41462, 41463, 41464, 41465, 41466, 41467, 41468, + 41469, 41470, 41471, 41472, 41473, 41474, 41475, 41476, + 41477, 41478, 41479, 41480, 41481, 41482, 41483, 41484, + 41485, 41486, 41487, 41488, 41489, 41490, 41491, 41492, + 41493, 41494, 41495, 41496, 41497, 41498, 41499, 41500, + 41501, 41502, 41503, 41504, 41505, 41506, 41507, 41508, + 41509, 41510, 41511, 41512, 41513, 41514, 41515, 41516, + 41517, 41518, 41519, 41520, 41521, 41522, 41523, 41524, + 41525, 41526, 41527, 41528, 41529, 41530, 41531, 41532, + 41533, 41534, 41535, 41536, 41537, 41538, 41539, 41540, + 41541, 41542, 41543, 41544, 41545, 41546, 41547, 41548, + 41549, 41550, 41551, 41552, 41553, 41554, 41555, 41556, + 41557, 41558, 41559, 41560, 41561, 41562, 41563, 41564, + 41565, 41566, 41567, 41568, 41569, 41570, 41571, 41572, + 41573, 41574, 41575, 41576, 41577, 41578, 41579, 41580, + 41581, 41582, 41583, 41584, 41585, 41586, 41587, 41588, + 41589, 41590, 41591, 41592, 41593, 41594, 41595, 41596, + 41597, 41598, 41599, 41600, 41601, 41602, 41603, 41604, + 41605, 41606, 41607, 41608, 41609, 41610, 41611, 41612, + 41613, 41614, 41615, 41616, 41617, 41618, 41619, 41620, + 41621, 41622, 41623, 41624, 41625, 41626, 41627, 41628, + 41629, 41630, 41631, 41632, 41633, 41634, 41635, 41636, + 41637, 41638, 41639, 41640, 41641, 41642, 41643, 41644, + 41645, 41646, 41647, 41648, 41649, 41650, 41651, 41652, + 41653, 41654, 41655, 41656, 41657, 41658, 41659, 41660, + 41661, 41662, 41663, 41664, 41665, 41666, 41667, 41668, + 41669, 41670, 41671, 41672, 41673, 41674, 41675, 41676, + 41677, 41678, 41679, 41680, 41681, 41682, 41683, 41684, + 41685, 41686, 41687, 41688, 41689, 41690, 41691, 41692, + 41693, 41694, 41695, 41696, 41697, 41698, 41699, 41700, + 41701, 41702, 41703, 41704, 41705, 41706, 41707, 41708, + 41709, 41710, 41711, 41712, 41713, 41714, 41715, 41716, + 41717, 41718, 41719, 41720, 41721, 41722, 41723, 41724, + 41725, 41726, 41727, 41728, 41729, 41730, 41731, 41732, + 41733, 41734, 41735, 41736, 41737, 41738, 41739, 41740, + 41741, 41742, 41743, 41744, 41745, 41746, 41747, 41748, + 41749, 41750, 41751, 41752, 41753, 41754, 41755, 41756, + 41757, 41758, 41759, 41760, 41761, 41762, 41763, 41764, + 41765, 41766, 41767, 41768, 41769, 41770, 41771, 41772, + 41773, 41774, 41775, 41776, 41777, 41778, 41779, 41780, + 41781, 41782, 41783, 41784, 41785, 41786, 41787, 41788, + 41789, 41790, 41791, 41792, 41793, 41794, 41795, 41796, + 41797, 41798, 41799, 41800, 41801, 41802, 41803, 41804, + 41805, 41806, 41807, 41808, 41809, 41810, 41811, 41812, + 41813, 41814, 41815, 41816, 41817, 41818, 41819, 41820, + 41821, 41822, 41823, 41824, 41825, 41826, 41827, 41828, + 41829, 41830, 41831, 41832, 41833, 41834, 41835, 41836, + 41837, 41838, 41839, 41840, 41841, 41842, 41843, 41844, + 41845, 41846, 41847, 41848, 41849, 41850, 41851, 41852, + 41853, 41854, 41855, 41856, 41857, 41858, 41859, 41860, + 41861, 41862, 41863, 41864, 41865, 41866, 41867, 41868, + 41869, 41870, 41871, 41872, 41873, 41874, 41875, 41876, + 41877, 41878, 41879, 41880, 41881, 41882, 41883, 41884, + 41885, 41886, 41887, 41888, 41889, 41890, 41891, 41892, + 41893, 41894, 41895, 41896, 41897, 41898, 41899, 41900, + 41901, 41902, 41903, 41904, 41905, 41906, 41907, 41908, + 41909, 41910, 41911, 41912, 41913, 41914, 41915, 41916, + 41917, 41918, 41919, 41920, 41921, 41922, 41923, 41924, + 41925, 41926, 41927, 41928, 41929, 41930, 41931, 41932, + 41933, 41934, 41935, 41936, 41937, 41938, 41939, 41940, + 41941, 41942, 41943, 41944, 41945, 41946, 41947, 41948, + 41949, 41950, 41951, 41952, 41953, 41954, 41955, 41956, + 41957, 41958, 41959, 41960, 41961, 41962, 41963, 41964, + 41965, 41966, 41967, 41968, 41969, 41970, 41971, 41972, + 41973, 41974, 41975, 41976, 41977, 41978, 41979, 41980, + 41981, 41982, 41983, 41984, 41985, 41986, 41987, 41988, + 41989, 41990, 41991, 41992, 41993, 41994, 41995, 41996, + 41997, 41998, 41999, 42000, 42001, 42002, 42003, 42004, + 42005, 42006, 42007, 42008, 42009, 42010, 42011, 42012, + 42013, 42014, 42015, 42016, 42017, 42018, 42019, 42020, + 42021, 42022, 42023, 42024, 42025, 42026, 42027, 42028, + 42029, 42030, 42031, 42032, 42033, 42034, 42035, 42036, + 42037, 42038, 42039, 42040, 42041, 42042, 42043, 42044, + 42045, 42046, 42047, 42048, 42049, 42050, 42051, 42052, + 42053, 42054, 42055, 42056, 42057, 42058, 42059, 42060, + 42061, 42062, 42063, 42064, 42065, 42066, 42067, 42068, + 42069, 42070, 42071, 42072, 42073, 42074, 42075, 42076, + 42077, 42078, 42079, 42080, 42081, 42082, 42083, 42084, + 42085, 42086, 42087, 42088, 42089, 42090, 42091, 42092, + 42093, 42094, 42095, 42096, 42097, 42098, 42099, 42100, + 42101, 42102, 42103, 42104, 42105, 42106, 42107, 42108, + 42109, 42110, 42111, 42112, 42113, 42114, 42115, 42116, + 42117, 42118, 42119, 42120, 42121, 42122, 42123, 42124, + 42192, 42193, 42194, 42195, 42196, 42197, 42198, 42199, + 42200, 42201, 42202, 42203, 42204, 42205, 42206, 42207, + 42208, 42209, 42210, 42211, 42212, 42213, 42214, 42215, + 42216, 42217, 42218, 42219, 42220, 42221, 42222, 42223, + 42224, 42225, 42226, 42227, 42228, 42229, 42230, 42231, + 42232, 42233, 42234, 42235, 42236, 42237, 42240, 42241, + 42242, 42243, 42244, 42245, 42246, 42247, 42248, 42249, + 42250, 42251, 42252, 42253, 42254, 42255, 42256, 42257, + 42258, 42259, 42260, 42261, 42262, 42263, 42264, 42265, + 42266, 42267, 42268, 42269, 42270, 42271, 42272, 42273, + 42274, 42275, 42276, 42277, 42278, 42279, 42280, 42281, + 42282, 42283, 42284, 42285, 42286, 42287, 42288, 42289, + 42290, 42291, 42292, 42293, 42294, 42295, 42296, 42297, + 42298, 42299, 42300, 42301, 42302, 42303, 42304, 42305, + 42306, 42307, 42308, 42309, 42310, 42311, 42312, 42313, + 42314, 42315, 42316, 42317, 42318, 42319, 42320, 42321, + 42322, 42323, 42324, 42325, 42326, 42327, 42328, 42329, + 42330, 42331, 42332, 42333, 42334, 42335, 42336, 42337, + 42338, 42339, 42340, 42341, 42342, 42343, 42344, 42345, + 42346, 42347, 42348, 42349, 42350, 42351, 42352, 42353, + 42354, 42355, 42356, 42357, 42358, 42359, 42360, 42361, + 42362, 42363, 42364, 42365, 42366, 42367, 42368, 42369, + 42370, 42371, 42372, 42373, 42374, 42375, 42376, 42377, + 42378, 42379, 42380, 42381, 42382, 42383, 42384, 42385, + 42386, 42387, 42388, 42389, 42390, 42391, 42392, 42393, + 42394, 42395, 42396, 42397, 42398, 42399, 42400, 42401, + 42402, 42403, 42404, 42405, 42406, 42407, 42408, 42409, + 42410, 42411, 42412, 42413, 42414, 42415, 42416, 42417, + 42418, 42419, 42420, 42421, 42422, 42423, 42424, 42425, + 42426, 42427, 42428, 42429, 42430, 42431, 42432, 42433, + 42434, 42435, 42436, 42437, 42438, 42439, 42440, 42441, + 42442, 42443, 42444, 42445, 42446, 42447, 42448, 42449, + 42450, 42451, 42452, 42453, 42454, 42455, 42456, 42457, + 42458, 42459, 42460, 42461, 42462, 42463, 42464, 42465, + 42466, 42467, 42468, 42469, 42470, 42471, 42472, 42473, + 42474, 42475, 42476, 42477, 42478, 42479, 42480, 42481, + 42482, 42483, 42484, 42485, 42486, 42487, 42488, 42489, + 42490, 42491, 42492, 42493, 42494, 42495, 42496, 42497, + 42498, 42499, 42500, 42501, 42502, 42503, 42504, 42505, + 42506, 42507, 42508, 42512, 42513, 42514, 42515, 42516, + 42517, 42518, 42519, 42520, 42521, 42522, 42523, 42524, + 42525, 42526, 42527, 42538, 42539, 42560, 42561, 42562, + 42563, 42564, 42565, 42566, 42567, 42568, 42569, 42570, + 42571, 42572, 42573, 42574, 42575, 42576, 42577, 42578, + 42579, 42580, 42581, 42582, 42583, 42584, 42585, 42586, + 42587, 42588, 42589, 42590, 42591, 42592, 42593, 42594, + 42595, 42596, 42597, 42598, 42599, 42600, 42601, 42602, + 42603, 42604, 42605, 42606, 42623, 42624, 42625, 42626, + 42627, 42628, 42629, 42630, 42631, 42632, 42633, 42634, + 42635, 42636, 42637, 42638, 42639, 42640, 42641, 42642, + 42643, 42644, 42645, 42646, 42647, 42656, 42657, 42658, + 42659, 42660, 42661, 42662, 42663, 42664, 42665, 42666, + 42667, 42668, 42669, 42670, 42671, 42672, 42673, 42674, + 42675, 42676, 42677, 42678, 42679, 42680, 42681, 42682, + 42683, 42684, 42685, 42686, 42687, 42688, 42689, 42690, + 42691, 42692, 42693, 42694, 42695, 42696, 42697, 42698, + 42699, 42700, 42701, 42702, 42703, 42704, 42705, 42706, + 42707, 42708, 42709, 42710, 42711, 42712, 42713, 42714, + 42715, 42716, 42717, 42718, 42719, 42720, 42721, 42722, + 42723, 42724, 42725, 42726, 42727, 42728, 42729, 42730, + 42731, 42732, 42733, 42734, 42735, 42775, 42776, 42777, + 42778, 42779, 42780, 42781, 42782, 42783, 42786, 42787, + 42788, 42789, 42790, 42791, 42792, 42793, 42794, 42795, + 42796, 42797, 42798, 42799, 42800, 42801, 42802, 42803, + 42804, 42805, 42806, 42807, 42808, 42809, 42810, 42811, + 42812, 42813, 42814, 42815, 42816, 42817, 42818, 42819, + 42820, 42821, 42822, 42823, 42824, 42825, 42826, 42827, + 42828, 42829, 42830, 42831, 42832, 42833, 42834, 42835, + 42836, 42837, 42838, 42839, 42840, 42841, 42842, 42843, + 42844, 42845, 42846, 42847, 42848, 42849, 42850, 42851, + 42852, 42853, 42854, 42855, 42856, 42857, 42858, 42859, + 42860, 42861, 42862, 42863, 42864, 42865, 42866, 42867, + 42868, 42869, 42870, 42871, 42872, 42873, 42874, 42875, + 42876, 42877, 42878, 42879, 42880, 42881, 42882, 42883, + 42884, 42885, 42886, 42887, 42888, 42891, 42892, 42893, + 42894, 42896, 42897, 42898, 42899, 42912, 42913, 42914, + 42915, 42916, 42917, 42918, 42919, 42920, 42921, 42922, + 43000, 43001, 43002, 43003, 43004, 43005, 43006, 43007, + 43008, 43009, 43011, 43012, 43013, 43015, 43016, 43017, + 43018, 43020, 43021, 43022, 43023, 43024, 43025, 43026, + 43027, 43028, 43029, 43030, 43031, 43032, 43033, 43034, + 43035, 43036, 43037, 43038, 43039, 43040, 43041, 43042, + 43072, 43073, 43074, 43075, 43076, 43077, 43078, 43079, + 43080, 43081, 43082, 43083, 43084, 43085, 43086, 43087, + 43088, 43089, 43090, 43091, 43092, 43093, 43094, 43095, + 43096, 43097, 43098, 43099, 43100, 43101, 43102, 43103, + 43104, 43105, 43106, 43107, 43108, 43109, 43110, 43111, + 43112, 43113, 43114, 43115, 43116, 43117, 43118, 43119, + 43120, 43121, 43122, 43123, 43138, 43139, 43140, 43141, + 43142, 43143, 43144, 43145, 43146, 43147, 43148, 43149, + 43150, 43151, 43152, 43153, 43154, 43155, 43156, 43157, + 43158, 43159, 43160, 43161, 43162, 43163, 43164, 43165, + 43166, 43167, 43168, 43169, 43170, 43171, 43172, 43173, + 43174, 43175, 43176, 43177, 43178, 43179, 43180, 43181, + 43182, 43183, 43184, 43185, 43186, 43187, 43250, 43251, + 43252, 43253, 43254, 43255, 43259, 43274, 43275, 43276, + 43277, 43278, 43279, 43280, 43281, 43282, 43283, 43284, + 43285, 43286, 43287, 43288, 43289, 43290, 43291, 43292, + 43293, 43294, 43295, 43296, 43297, 43298, 43299, 43300, + 43301, 43312, 43313, 43314, 43315, 43316, 43317, 43318, + 43319, 43320, 43321, 43322, 43323, 43324, 43325, 43326, + 43327, 43328, 43329, 43330, 43331, 43332, 43333, 43334, + 43360, 43361, 43362, 43363, 43364, 43365, 43366, 43367, + 43368, 43369, 43370, 43371, 43372, 43373, 43374, 43375, + 43376, 43377, 43378, 43379, 43380, 43381, 43382, 43383, + 43384, 43385, 43386, 43387, 43388, 43396, 43397, 43398, + 43399, 43400, 43401, 43402, 43403, 43404, 43405, 43406, + 43407, 43408, 43409, 43410, 43411, 43412, 43413, 43414, + 43415, 43416, 43417, 43418, 43419, 43420, 43421, 43422, + 43423, 43424, 43425, 43426, 43427, 43428, 43429, 43430, + 43431, 43432, 43433, 43434, 43435, 43436, 43437, 43438, + 43439, 43440, 43441, 43442, 43471, 43520, 43521, 43522, + 43523, 43524, 43525, 43526, 43527, 43528, 43529, 43530, + 43531, 43532, 43533, 43534, 43535, 43536, 43537, 43538, + 43539, 43540, 43541, 43542, 43543, 43544, 43545, 43546, + 43547, 43548, 43549, 43550, 43551, 43552, 43553, 43554, + 43555, 43556, 43557, 43558, 43559, 43560, 43584, 43585, + 43586, 43588, 43589, 43590, 43591, 43592, 43593, 43594, + 43595, 43616, 43617, 43618, 43619, 43620, 43621, 43622, + 43623, 43624, 43625, 43626, 43627, 43628, 43629, 43630, + 43631, 43632, 43633, 43634, 43635, 43636, 43637, 43638, + 43642, 43648, 43649, 43650, 43651, 43652, 43653, 43654, + 43655, 43656, 43657, 43658, 43659, 43660, 43661, 43662, + 43663, 43664, 43665, 43666, 43667, 43668, 43669, 43670, + 43671, 43672, 43673, 43674, 43675, 43676, 43677, 43678, + 43679, 43680, 43681, 43682, 43683, 43684, 43685, 43686, + 43687, 43688, 43689, 43690, 43691, 43692, 43693, 43694, + 43695, 43697, 43701, 43702, 43705, 43706, 43707, 43708, + 43709, 43712, 43714, 43739, 43740, 43741, 43744, 43745, + 43746, 43747, 43748, 43749, 43750, 43751, 43752, 43753, + 43754, 43762, 43763, 43764, 43777, 43778, 43779, 43780, + 43781, 43782, 43785, 43786, 43787, 43788, 43789, 43790, + 43793, 43794, 43795, 43796, 43797, 43798, 43808, 43809, + 43810, 43811, 43812, 43813, 43814, 43816, 43817, 43818, + 43819, 43820, 43821, 43822, 43968, 43969, 43970, 43971, + 43972, 43973, 43974, 43975, 43976, 43977, 43978, 43979, + 43980, 43981, 43982, 43983, 43984, 43985, 43986, 43987, + 43988, 43989, 43990, 43991, 43992, 43993, 43994, 43995, + 43996, 43997, 43998, 43999, 44000, 44001, 44002, 44032, + 44033, 44034, 44035, 44036, 44037, 44038, 44039, 44040, + 44041, 44042, 44043, 44044, 44045, 44046, 44047, 44048, + 44049, 44050, 44051, 44052, 44053, 44054, 44055, 44056, + 44057, 44058, 44059, 44060, 44061, 44062, 44063, 44064, + 44065, 44066, 44067, 44068, 44069, 44070, 44071, 44072, + 44073, 44074, 44075, 44076, 44077, 44078, 44079, 44080, + 44081, 44082, 44083, 44084, 44085, 44086, 44087, 44088, + 44089, 44090, 44091, 44092, 44093, 44094, 44095, 44096, + 44097, 44098, 44099, 44100, 44101, 44102, 44103, 44104, + 44105, 44106, 44107, 44108, 44109, 44110, 44111, 44112, + 44113, 44114, 44115, 44116, 44117, 44118, 44119, 44120, + 44121, 44122, 44123, 44124, 44125, 44126, 44127, 44128, + 44129, 44130, 44131, 44132, 44133, 44134, 44135, 44136, + 44137, 44138, 44139, 44140, 44141, 44142, 44143, 44144, + 44145, 44146, 44147, 44148, 44149, 44150, 44151, 44152, + 44153, 44154, 44155, 44156, 44157, 44158, 44159, 44160, + 44161, 44162, 44163, 44164, 44165, 44166, 44167, 44168, + 44169, 44170, 44171, 44172, 44173, 44174, 44175, 44176, + 44177, 44178, 44179, 44180, 44181, 44182, 44183, 44184, + 44185, 44186, 44187, 44188, 44189, 44190, 44191, 44192, + 44193, 44194, 44195, 44196, 44197, 44198, 44199, 44200, + 44201, 44202, 44203, 44204, 44205, 44206, 44207, 44208, + 44209, 44210, 44211, 44212, 44213, 44214, 44215, 44216, + 44217, 44218, 44219, 44220, 44221, 44222, 44223, 44224, + 44225, 44226, 44227, 44228, 44229, 44230, 44231, 44232, + 44233, 44234, 44235, 44236, 44237, 44238, 44239, 44240, + 44241, 44242, 44243, 44244, 44245, 44246, 44247, 44248, + 44249, 44250, 44251, 44252, 44253, 44254, 44255, 44256, + 44257, 44258, 44259, 44260, 44261, 44262, 44263, 44264, + 44265, 44266, 44267, 44268, 44269, 44270, 44271, 44272, + 44273, 44274, 44275, 44276, 44277, 44278, 44279, 44280, + 44281, 44282, 44283, 44284, 44285, 44286, 44287, 44288, + 44289, 44290, 44291, 44292, 44293, 44294, 44295, 44296, + 44297, 44298, 44299, 44300, 44301, 44302, 44303, 44304, + 44305, 44306, 44307, 44308, 44309, 44310, 44311, 44312, + 44313, 44314, 44315, 44316, 44317, 44318, 44319, 44320, + 44321, 44322, 44323, 44324, 44325, 44326, 44327, 44328, + 44329, 44330, 44331, 44332, 44333, 44334, 44335, 44336, + 44337, 44338, 44339, 44340, 44341, 44342, 44343, 44344, + 44345, 44346, 44347, 44348, 44349, 44350, 44351, 44352, + 44353, 44354, 44355, 44356, 44357, 44358, 44359, 44360, + 44361, 44362, 44363, 44364, 44365, 44366, 44367, 44368, + 44369, 44370, 44371, 44372, 44373, 44374, 44375, 44376, + 44377, 44378, 44379, 44380, 44381, 44382, 44383, 44384, + 44385, 44386, 44387, 44388, 44389, 44390, 44391, 44392, + 44393, 44394, 44395, 44396, 44397, 44398, 44399, 44400, + 44401, 44402, 44403, 44404, 44405, 44406, 44407, 44408, + 44409, 44410, 44411, 44412, 44413, 44414, 44415, 44416, + 44417, 44418, 44419, 44420, 44421, 44422, 44423, 44424, + 44425, 44426, 44427, 44428, 44429, 44430, 44431, 44432, + 44433, 44434, 44435, 44436, 44437, 44438, 44439, 44440, + 44441, 44442, 44443, 44444, 44445, 44446, 44447, 44448, + 44449, 44450, 44451, 44452, 44453, 44454, 44455, 44456, + 44457, 44458, 44459, 44460, 44461, 44462, 44463, 44464, + 44465, 44466, 44467, 44468, 44469, 44470, 44471, 44472, + 44473, 44474, 44475, 44476, 44477, 44478, 44479, 44480, + 44481, 44482, 44483, 44484, 44485, 44486, 44487, 44488, + 44489, 44490, 44491, 44492, 44493, 44494, 44495, 44496, + 44497, 44498, 44499, 44500, 44501, 44502, 44503, 44504, + 44505, 44506, 44507, 44508, 44509, 44510, 44511, 44512, + 44513, 44514, 44515, 44516, 44517, 44518, 44519, 44520, + 44521, 44522, 44523, 44524, 44525, 44526, 44527, 44528, + 44529, 44530, 44531, 44532, 44533, 44534, 44535, 44536, + 44537, 44538, 44539, 44540, 44541, 44542, 44543, 44544, + 44545, 44546, 44547, 44548, 44549, 44550, 44551, 44552, + 44553, 44554, 44555, 44556, 44557, 44558, 44559, 44560, + 44561, 44562, 44563, 44564, 44565, 44566, 44567, 44568, + 44569, 44570, 44571, 44572, 44573, 44574, 44575, 44576, + 44577, 44578, 44579, 44580, 44581, 44582, 44583, 44584, + 44585, 44586, 44587, 44588, 44589, 44590, 44591, 44592, + 44593, 44594, 44595, 44596, 44597, 44598, 44599, 44600, + 44601, 44602, 44603, 44604, 44605, 44606, 44607, 44608, + 44609, 44610, 44611, 44612, 44613, 44614, 44615, 44616, + 44617, 44618, 44619, 44620, 44621, 44622, 44623, 44624, + 44625, 44626, 44627, 44628, 44629, 44630, 44631, 44632, + 44633, 44634, 44635, 44636, 44637, 44638, 44639, 44640, + 44641, 44642, 44643, 44644, 44645, 44646, 44647, 44648, + 44649, 44650, 44651, 44652, 44653, 44654, 44655, 44656, + 44657, 44658, 44659, 44660, 44661, 44662, 44663, 44664, + 44665, 44666, 44667, 44668, 44669, 44670, 44671, 44672, + 44673, 44674, 44675, 44676, 44677, 44678, 44679, 44680, + 44681, 44682, 44683, 44684, 44685, 44686, 44687, 44688, + 44689, 44690, 44691, 44692, 44693, 44694, 44695, 44696, + 44697, 44698, 44699, 44700, 44701, 44702, 44703, 44704, + 44705, 44706, 44707, 44708, 44709, 44710, 44711, 44712, + 44713, 44714, 44715, 44716, 44717, 44718, 44719, 44720, + 44721, 44722, 44723, 44724, 44725, 44726, 44727, 44728, + 44729, 44730, 44731, 44732, 44733, 44734, 44735, 44736, + 44737, 44738, 44739, 44740, 44741, 44742, 44743, 44744, + 44745, 44746, 44747, 44748, 44749, 44750, 44751, 44752, + 44753, 44754, 44755, 44756, 44757, 44758, 44759, 44760, + 44761, 44762, 44763, 44764, 44765, 44766, 44767, 44768, + 44769, 44770, 44771, 44772, 44773, 44774, 44775, 44776, + 44777, 44778, 44779, 44780, 44781, 44782, 44783, 44784, + 44785, 44786, 44787, 44788, 44789, 44790, 44791, 44792, + 44793, 44794, 44795, 44796, 44797, 44798, 44799, 44800, + 44801, 44802, 44803, 44804, 44805, 44806, 44807, 44808, + 44809, 44810, 44811, 44812, 44813, 44814, 44815, 44816, + 44817, 44818, 44819, 44820, 44821, 44822, 44823, 44824, + 44825, 44826, 44827, 44828, 44829, 44830, 44831, 44832, + 44833, 44834, 44835, 44836, 44837, 44838, 44839, 44840, + 44841, 44842, 44843, 44844, 44845, 44846, 44847, 44848, + 44849, 44850, 44851, 44852, 44853, 44854, 44855, 44856, + 44857, 44858, 44859, 44860, 44861, 44862, 44863, 44864, + 44865, 44866, 44867, 44868, 44869, 44870, 44871, 44872, + 44873, 44874, 44875, 44876, 44877, 44878, 44879, 44880, + 44881, 44882, 44883, 44884, 44885, 44886, 44887, 44888, + 44889, 44890, 44891, 44892, 44893, 44894, 44895, 44896, + 44897, 44898, 44899, 44900, 44901, 44902, 44903, 44904, + 44905, 44906, 44907, 44908, 44909, 44910, 44911, 44912, + 44913, 44914, 44915, 44916, 44917, 44918, 44919, 44920, + 44921, 44922, 44923, 44924, 44925, 44926, 44927, 44928, + 44929, 44930, 44931, 44932, 44933, 44934, 44935, 44936, + 44937, 44938, 44939, 44940, 44941, 44942, 44943, 44944, + 44945, 44946, 44947, 44948, 44949, 44950, 44951, 44952, + 44953, 44954, 44955, 44956, 44957, 44958, 44959, 44960, + 44961, 44962, 44963, 44964, 44965, 44966, 44967, 44968, + 44969, 44970, 44971, 44972, 44973, 44974, 44975, 44976, + 44977, 44978, 44979, 44980, 44981, 44982, 44983, 44984, + 44985, 44986, 44987, 44988, 44989, 44990, 44991, 44992, + 44993, 44994, 44995, 44996, 44997, 44998, 44999, 45000, + 45001, 45002, 45003, 45004, 45005, 45006, 45007, 45008, + 45009, 45010, 45011, 45012, 45013, 45014, 45015, 45016, + 45017, 45018, 45019, 45020, 45021, 45022, 45023, 45024, + 45025, 45026, 45027, 45028, 45029, 45030, 45031, 45032, + 45033, 45034, 45035, 45036, 45037, 45038, 45039, 45040, + 45041, 45042, 45043, 45044, 45045, 45046, 45047, 45048, + 45049, 45050, 45051, 45052, 45053, 45054, 45055, 45056, + 45057, 45058, 45059, 45060, 45061, 45062, 45063, 45064, + 45065, 45066, 45067, 45068, 45069, 45070, 45071, 45072, + 45073, 45074, 45075, 45076, 45077, 45078, 45079, 45080, + 45081, 45082, 45083, 45084, 45085, 45086, 45087, 45088, + 45089, 45090, 45091, 45092, 45093, 45094, 45095, 45096, + 45097, 45098, 45099, 45100, 45101, 45102, 45103, 45104, + 45105, 45106, 45107, 45108, 45109, 45110, 45111, 45112, + 45113, 45114, 45115, 45116, 45117, 45118, 45119, 45120, + 45121, 45122, 45123, 45124, 45125, 45126, 45127, 45128, + 45129, 45130, 45131, 45132, 45133, 45134, 45135, 45136, + 45137, 45138, 45139, 45140, 45141, 45142, 45143, 45144, + 45145, 45146, 45147, 45148, 45149, 45150, 45151, 45152, + 45153, 45154, 45155, 45156, 45157, 45158, 45159, 45160, + 45161, 45162, 45163, 45164, 45165, 45166, 45167, 45168, + 45169, 45170, 45171, 45172, 45173, 45174, 45175, 45176, + 45177, 45178, 45179, 45180, 45181, 45182, 45183, 45184, + 45185, 45186, 45187, 45188, 45189, 45190, 45191, 45192, + 45193, 45194, 45195, 45196, 45197, 45198, 45199, 45200, + 45201, 45202, 45203, 45204, 45205, 45206, 45207, 45208, + 45209, 45210, 45211, 45212, 45213, 45214, 45215, 45216, + 45217, 45218, 45219, 45220, 45221, 45222, 45223, 45224, + 45225, 45226, 45227, 45228, 45229, 45230, 45231, 45232, + 45233, 45234, 45235, 45236, 45237, 45238, 45239, 45240, + 45241, 45242, 45243, 45244, 45245, 45246, 45247, 45248, + 45249, 45250, 45251, 45252, 45253, 45254, 45255, 45256, + 45257, 45258, 45259, 45260, 45261, 45262, 45263, 45264, + 45265, 45266, 45267, 45268, 45269, 45270, 45271, 45272, + 45273, 45274, 45275, 45276, 45277, 45278, 45279, 45280, + 45281, 45282, 45283, 45284, 45285, 45286, 45287, 45288, + 45289, 45290, 45291, 45292, 45293, 45294, 45295, 45296, + 45297, 45298, 45299, 45300, 45301, 45302, 45303, 45304, + 45305, 45306, 45307, 45308, 45309, 45310, 45311, 45312, + 45313, 45314, 45315, 45316, 45317, 45318, 45319, 45320, + 45321, 45322, 45323, 45324, 45325, 45326, 45327, 45328, + 45329, 45330, 45331, 45332, 45333, 45334, 45335, 45336, + 45337, 45338, 45339, 45340, 45341, 45342, 45343, 45344, + 45345, 45346, 45347, 45348, 45349, 45350, 45351, 45352, + 45353, 45354, 45355, 45356, 45357, 45358, 45359, 45360, + 45361, 45362, 45363, 45364, 45365, 45366, 45367, 45368, + 45369, 45370, 45371, 45372, 45373, 45374, 45375, 45376, + 45377, 45378, 45379, 45380, 45381, 45382, 45383, 45384, + 45385, 45386, 45387, 45388, 45389, 45390, 45391, 45392, + 45393, 45394, 45395, 45396, 45397, 45398, 45399, 45400, + 45401, 45402, 45403, 45404, 45405, 45406, 45407, 45408, + 45409, 45410, 45411, 45412, 45413, 45414, 45415, 45416, + 45417, 45418, 45419, 45420, 45421, 45422, 45423, 45424, + 45425, 45426, 45427, 45428, 45429, 45430, 45431, 45432, + 45433, 45434, 45435, 45436, 45437, 45438, 45439, 45440, + 45441, 45442, 45443, 45444, 45445, 45446, 45447, 45448, + 45449, 45450, 45451, 45452, 45453, 45454, 45455, 45456, + 45457, 45458, 45459, 45460, 45461, 45462, 45463, 45464, + 45465, 45466, 45467, 45468, 45469, 45470, 45471, 45472, + 45473, 45474, 45475, 45476, 45477, 45478, 45479, 45480, + 45481, 45482, 45483, 45484, 45485, 45486, 45487, 45488, + 45489, 45490, 45491, 45492, 45493, 45494, 45495, 45496, + 45497, 45498, 45499, 45500, 45501, 45502, 45503, 45504, + 45505, 45506, 45507, 45508, 45509, 45510, 45511, 45512, + 45513, 45514, 45515, 45516, 45517, 45518, 45519, 45520, + 45521, 45522, 45523, 45524, 45525, 45526, 45527, 45528, + 45529, 45530, 45531, 45532, 45533, 45534, 45535, 45536, + 45537, 45538, 45539, 45540, 45541, 45542, 45543, 45544, + 45545, 45546, 45547, 45548, 45549, 45550, 45551, 45552, + 45553, 45554, 45555, 45556, 45557, 45558, 45559, 45560, + 45561, 45562, 45563, 45564, 45565, 45566, 45567, 45568, + 45569, 45570, 45571, 45572, 45573, 45574, 45575, 45576, + 45577, 45578, 45579, 45580, 45581, 45582, 45583, 45584, + 45585, 45586, 45587, 45588, 45589, 45590, 45591, 45592, + 45593, 45594, 45595, 45596, 45597, 45598, 45599, 45600, + 45601, 45602, 45603, 45604, 45605, 45606, 45607, 45608, + 45609, 45610, 45611, 45612, 45613, 45614, 45615, 45616, + 45617, 45618, 45619, 45620, 45621, 45622, 45623, 45624, + 45625, 45626, 45627, 45628, 45629, 45630, 45631, 45632, + 45633, 45634, 45635, 45636, 45637, 45638, 45639, 45640, + 45641, 45642, 45643, 45644, 45645, 45646, 45647, 45648, + 45649, 45650, 45651, 45652, 45653, 45654, 45655, 45656, + 45657, 45658, 45659, 45660, 45661, 45662, 45663, 45664, + 45665, 45666, 45667, 45668, 45669, 45670, 45671, 45672, + 45673, 45674, 45675, 45676, 45677, 45678, 45679, 45680, + 45681, 45682, 45683, 45684, 45685, 45686, 45687, 45688, + 45689, 45690, 45691, 45692, 45693, 45694, 45695, 45696, + 45697, 45698, 45699, 45700, 45701, 45702, 45703, 45704, + 45705, 45706, 45707, 45708, 45709, 45710, 45711, 45712, + 45713, 45714, 45715, 45716, 45717, 45718, 45719, 45720, + 45721, 45722, 45723, 45724, 45725, 45726, 45727, 45728, + 45729, 45730, 45731, 45732, 45733, 45734, 45735, 45736, + 45737, 45738, 45739, 45740, 45741, 45742, 45743, 45744, + 45745, 45746, 45747, 45748, 45749, 45750, 45751, 45752, + 45753, 45754, 45755, 45756, 45757, 45758, 45759, 45760, + 45761, 45762, 45763, 45764, 45765, 45766, 45767, 45768, + 45769, 45770, 45771, 45772, 45773, 45774, 45775, 45776, + 45777, 45778, 45779, 45780, 45781, 45782, 45783, 45784, + 45785, 45786, 45787, 45788, 45789, 45790, 45791, 45792, + 45793, 45794, 45795, 45796, 45797, 45798, 45799, 45800, + 45801, 45802, 45803, 45804, 45805, 45806, 45807, 45808, + 45809, 45810, 45811, 45812, 45813, 45814, 45815, 45816, + 45817, 45818, 45819, 45820, 45821, 45822, 45823, 45824, + 45825, 45826, 45827, 45828, 45829, 45830, 45831, 45832, + 45833, 45834, 45835, 45836, 45837, 45838, 45839, 45840, + 45841, 45842, 45843, 45844, 45845, 45846, 45847, 45848, + 45849, 45850, 45851, 45852, 45853, 45854, 45855, 45856, + 45857, 45858, 45859, 45860, 45861, 45862, 45863, 45864, + 45865, 45866, 45867, 45868, 45869, 45870, 45871, 45872, + 45873, 45874, 45875, 45876, 45877, 45878, 45879, 45880, + 45881, 45882, 45883, 45884, 45885, 45886, 45887, 45888, + 45889, 45890, 45891, 45892, 45893, 45894, 45895, 45896, + 45897, 45898, 45899, 45900, 45901, 45902, 45903, 45904, + 45905, 45906, 45907, 45908, 45909, 45910, 45911, 45912, + 45913, 45914, 45915, 45916, 45917, 45918, 45919, 45920, + 45921, 45922, 45923, 45924, 45925, 45926, 45927, 45928, + 45929, 45930, 45931, 45932, 45933, 45934, 45935, 45936, + 45937, 45938, 45939, 45940, 45941, 45942, 45943, 45944, + 45945, 45946, 45947, 45948, 45949, 45950, 45951, 45952, + 45953, 45954, 45955, 45956, 45957, 45958, 45959, 45960, + 45961, 45962, 45963, 45964, 45965, 45966, 45967, 45968, + 45969, 45970, 45971, 45972, 45973, 45974, 45975, 45976, + 45977, 45978, 45979, 45980, 45981, 45982, 45983, 45984, + 45985, 45986, 45987, 45988, 45989, 45990, 45991, 45992, + 45993, 45994, 45995, 45996, 45997, 45998, 45999, 46000, + 46001, 46002, 46003, 46004, 46005, 46006, 46007, 46008, + 46009, 46010, 46011, 46012, 46013, 46014, 46015, 46016, + 46017, 46018, 46019, 46020, 46021, 46022, 46023, 46024, + 46025, 46026, 46027, 46028, 46029, 46030, 46031, 46032, + 46033, 46034, 46035, 46036, 46037, 46038, 46039, 46040, + 46041, 46042, 46043, 46044, 46045, 46046, 46047, 46048, + 46049, 46050, 46051, 46052, 46053, 46054, 46055, 46056, + 46057, 46058, 46059, 46060, 46061, 46062, 46063, 46064, + 46065, 46066, 46067, 46068, 46069, 46070, 46071, 46072, + 46073, 46074, 46075, 46076, 46077, 46078, 46079, 46080, + 46081, 46082, 46083, 46084, 46085, 46086, 46087, 46088, + 46089, 46090, 46091, 46092, 46093, 46094, 46095, 46096, + 46097, 46098, 46099, 46100, 46101, 46102, 46103, 46104, + 46105, 46106, 46107, 46108, 46109, 46110, 46111, 46112, + 46113, 46114, 46115, 46116, 46117, 46118, 46119, 46120, + 46121, 46122, 46123, 46124, 46125, 46126, 46127, 46128, + 46129, 46130, 46131, 46132, 46133, 46134, 46135, 46136, + 46137, 46138, 46139, 46140, 46141, 46142, 46143, 46144, + 46145, 46146, 46147, 46148, 46149, 46150, 46151, 46152, + 46153, 46154, 46155, 46156, 46157, 46158, 46159, 46160, + 46161, 46162, 46163, 46164, 46165, 46166, 46167, 46168, + 46169, 46170, 46171, 46172, 46173, 46174, 46175, 46176, + 46177, 46178, 46179, 46180, 46181, 46182, 46183, 46184, + 46185, 46186, 46187, 46188, 46189, 46190, 46191, 46192, + 46193, 46194, 46195, 46196, 46197, 46198, 46199, 46200, + 46201, 46202, 46203, 46204, 46205, 46206, 46207, 46208, + 46209, 46210, 46211, 46212, 46213, 46214, 46215, 46216, + 46217, 46218, 46219, 46220, 46221, 46222, 46223, 46224, + 46225, 46226, 46227, 46228, 46229, 46230, 46231, 46232, + 46233, 46234, 46235, 46236, 46237, 46238, 46239, 46240, + 46241, 46242, 46243, 46244, 46245, 46246, 46247, 46248, + 46249, 46250, 46251, 46252, 46253, 46254, 46255, 46256, + 46257, 46258, 46259, 46260, 46261, 46262, 46263, 46264, + 46265, 46266, 46267, 46268, 46269, 46270, 46271, 46272, + 46273, 46274, 46275, 46276, 46277, 46278, 46279, 46280, + 46281, 46282, 46283, 46284, 46285, 46286, 46287, 46288, + 46289, 46290, 46291, 46292, 46293, 46294, 46295, 46296, + 46297, 46298, 46299, 46300, 46301, 46302, 46303, 46304, + 46305, 46306, 46307, 46308, 46309, 46310, 46311, 46312, + 46313, 46314, 46315, 46316, 46317, 46318, 46319, 46320, + 46321, 46322, 46323, 46324, 46325, 46326, 46327, 46328, + 46329, 46330, 46331, 46332, 46333, 46334, 46335, 46336, + 46337, 46338, 46339, 46340, 46341, 46342, 46343, 46344, + 46345, 46346, 46347, 46348, 46349, 46350, 46351, 46352, + 46353, 46354, 46355, 46356, 46357, 46358, 46359, 46360, + 46361, 46362, 46363, 46364, 46365, 46366, 46367, 46368, + 46369, 46370, 46371, 46372, 46373, 46374, 46375, 46376, + 46377, 46378, 46379, 46380, 46381, 46382, 46383, 46384, + 46385, 46386, 46387, 46388, 46389, 46390, 46391, 46392, + 46393, 46394, 46395, 46396, 46397, 46398, 46399, 46400, + 46401, 46402, 46403, 46404, 46405, 46406, 46407, 46408, + 46409, 46410, 46411, 46412, 46413, 46414, 46415, 46416, + 46417, 46418, 46419, 46420, 46421, 46422, 46423, 46424, + 46425, 46426, 46427, 46428, 46429, 46430, 46431, 46432, + 46433, 46434, 46435, 46436, 46437, 46438, 46439, 46440, + 46441, 46442, 46443, 46444, 46445, 46446, 46447, 46448, + 46449, 46450, 46451, 46452, 46453, 46454, 46455, 46456, + 46457, 46458, 46459, 46460, 46461, 46462, 46463, 46464, + 46465, 46466, 46467, 46468, 46469, 46470, 46471, 46472, + 46473, 46474, 46475, 46476, 46477, 46478, 46479, 46480, + 46481, 46482, 46483, 46484, 46485, 46486, 46487, 46488, + 46489, 46490, 46491, 46492, 46493, 46494, 46495, 46496, + 46497, 46498, 46499, 46500, 46501, 46502, 46503, 46504, + 46505, 46506, 46507, 46508, 46509, 46510, 46511, 46512, + 46513, 46514, 46515, 46516, 46517, 46518, 46519, 46520, + 46521, 46522, 46523, 46524, 46525, 46526, 46527, 46528, + 46529, 46530, 46531, 46532, 46533, 46534, 46535, 46536, + 46537, 46538, 46539, 46540, 46541, 46542, 46543, 46544, + 46545, 46546, 46547, 46548, 46549, 46550, 46551, 46552, + 46553, 46554, 46555, 46556, 46557, 46558, 46559, 46560, + 46561, 46562, 46563, 46564, 46565, 46566, 46567, 46568, + 46569, 46570, 46571, 46572, 46573, 46574, 46575, 46576, + 46577, 46578, 46579, 46580, 46581, 46582, 46583, 46584, + 46585, 46586, 46587, 46588, 46589, 46590, 46591, 46592, + 46593, 46594, 46595, 46596, 46597, 46598, 46599, 46600, + 46601, 46602, 46603, 46604, 46605, 46606, 46607, 46608, + 46609, 46610, 46611, 46612, 46613, 46614, 46615, 46616, + 46617, 46618, 46619, 46620, 46621, 46622, 46623, 46624, + 46625, 46626, 46627, 46628, 46629, 46630, 46631, 46632, + 46633, 46634, 46635, 46636, 46637, 46638, 46639, 46640, + 46641, 46642, 46643, 46644, 46645, 46646, 46647, 46648, + 46649, 46650, 46651, 46652, 46653, 46654, 46655, 46656, + 46657, 46658, 46659, 46660, 46661, 46662, 46663, 46664, + 46665, 46666, 46667, 46668, 46669, 46670, 46671, 46672, + 46673, 46674, 46675, 46676, 46677, 46678, 46679, 46680, + 46681, 46682, 46683, 46684, 46685, 46686, 46687, 46688, + 46689, 46690, 46691, 46692, 46693, 46694, 46695, 46696, + 46697, 46698, 46699, 46700, 46701, 46702, 46703, 46704, + 46705, 46706, 46707, 46708, 46709, 46710, 46711, 46712, + 46713, 46714, 46715, 46716, 46717, 46718, 46719, 46720, + 46721, 46722, 46723, 46724, 46725, 46726, 46727, 46728, + 46729, 46730, 46731, 46732, 46733, 46734, 46735, 46736, + 46737, 46738, 46739, 46740, 46741, 46742, 46743, 46744, + 46745, 46746, 46747, 46748, 46749, 46750, 46751, 46752, + 46753, 46754, 46755, 46756, 46757, 46758, 46759, 46760, + 46761, 46762, 46763, 46764, 46765, 46766, 46767, 46768, + 46769, 46770, 46771, 46772, 46773, 46774, 46775, 46776, + 46777, 46778, 46779, 46780, 46781, 46782, 46783, 46784, + 46785, 46786, 46787, 46788, 46789, 46790, 46791, 46792, + 46793, 46794, 46795, 46796, 46797, 46798, 46799, 46800, + 46801, 46802, 46803, 46804, 46805, 46806, 46807, 46808, + 46809, 46810, 46811, 46812, 46813, 46814, 46815, 46816, + 46817, 46818, 46819, 46820, 46821, 46822, 46823, 46824, + 46825, 46826, 46827, 46828, 46829, 46830, 46831, 46832, + 46833, 46834, 46835, 46836, 46837, 46838, 46839, 46840, + 46841, 46842, 46843, 46844, 46845, 46846, 46847, 46848, + 46849, 46850, 46851, 46852, 46853, 46854, 46855, 46856, + 46857, 46858, 46859, 46860, 46861, 46862, 46863, 46864, + 46865, 46866, 46867, 46868, 46869, 46870, 46871, 46872, + 46873, 46874, 46875, 46876, 46877, 46878, 46879, 46880, + 46881, 46882, 46883, 46884, 46885, 46886, 46887, 46888, + 46889, 46890, 46891, 46892, 46893, 46894, 46895, 46896, + 46897, 46898, 46899, 46900, 46901, 46902, 46903, 46904, + 46905, 46906, 46907, 46908, 46909, 46910, 46911, 46912, + 46913, 46914, 46915, 46916, 46917, 46918, 46919, 46920, + 46921, 46922, 46923, 46924, 46925, 46926, 46927, 46928, + 46929, 46930, 46931, 46932, 46933, 46934, 46935, 46936, + 46937, 46938, 46939, 46940, 46941, 46942, 46943, 46944, + 46945, 46946, 46947, 46948, 46949, 46950, 46951, 46952, + 46953, 46954, 46955, 46956, 46957, 46958, 46959, 46960, + 46961, 46962, 46963, 46964, 46965, 46966, 46967, 46968, + 46969, 46970, 46971, 46972, 46973, 46974, 46975, 46976, + 46977, 46978, 46979, 46980, 46981, 46982, 46983, 46984, + 46985, 46986, 46987, 46988, 46989, 46990, 46991, 46992, + 46993, 46994, 46995, 46996, 46997, 46998, 46999, 47000, + 47001, 47002, 47003, 47004, 47005, 47006, 47007, 47008, + 47009, 47010, 47011, 47012, 47013, 47014, 47015, 47016, + 47017, 47018, 47019, 47020, 47021, 47022, 47023, 47024, + 47025, 47026, 47027, 47028, 47029, 47030, 47031, 47032, + 47033, 47034, 47035, 47036, 47037, 47038, 47039, 47040, + 47041, 47042, 47043, 47044, 47045, 47046, 47047, 47048, + 47049, 47050, 47051, 47052, 47053, 47054, 47055, 47056, + 47057, 47058, 47059, 47060, 47061, 47062, 47063, 47064, + 47065, 47066, 47067, 47068, 47069, 47070, 47071, 47072, + 47073, 47074, 47075, 47076, 47077, 47078, 47079, 47080, + 47081, 47082, 47083, 47084, 47085, 47086, 47087, 47088, + 47089, 47090, 47091, 47092, 47093, 47094, 47095, 47096, + 47097, 47098, 47099, 47100, 47101, 47102, 47103, 47104, + 47105, 47106, 47107, 47108, 47109, 47110, 47111, 47112, + 47113, 47114, 47115, 47116, 47117, 47118, 47119, 47120, + 47121, 47122, 47123, 47124, 47125, 47126, 47127, 47128, + 47129, 47130, 47131, 47132, 47133, 47134, 47135, 47136, + 47137, 47138, 47139, 47140, 47141, 47142, 47143, 47144, + 47145, 47146, 47147, 47148, 47149, 47150, 47151, 47152, + 47153, 47154, 47155, 47156, 47157, 47158, 47159, 47160, + 47161, 47162, 47163, 47164, 47165, 47166, 47167, 47168, + 47169, 47170, 47171, 47172, 47173, 47174, 47175, 47176, + 47177, 47178, 47179, 47180, 47181, 47182, 47183, 47184, + 47185, 47186, 47187, 47188, 47189, 47190, 47191, 47192, + 47193, 47194, 47195, 47196, 47197, 47198, 47199, 47200, + 47201, 47202, 47203, 47204, 47205, 47206, 47207, 47208, + 47209, 47210, 47211, 47212, 47213, 47214, 47215, 47216, + 47217, 47218, 47219, 47220, 47221, 47222, 47223, 47224, + 47225, 47226, 47227, 47228, 47229, 47230, 47231, 47232, + 47233, 47234, 47235, 47236, 47237, 47238, 47239, 47240, + 47241, 47242, 47243, 47244, 47245, 47246, 47247, 47248, + 47249, 47250, 47251, 47252, 47253, 47254, 47255, 47256, + 47257, 47258, 47259, 47260, 47261, 47262, 47263, 47264, + 47265, 47266, 47267, 47268, 47269, 47270, 47271, 47272, + 47273, 47274, 47275, 47276, 47277, 47278, 47279, 47280, + 47281, 47282, 47283, 47284, 47285, 47286, 47287, 47288, + 47289, 47290, 47291, 47292, 47293, 47294, 47295, 47296, + 47297, 47298, 47299, 47300, 47301, 47302, 47303, 47304, + 47305, 47306, 47307, 47308, 47309, 47310, 47311, 47312, + 47313, 47314, 47315, 47316, 47317, 47318, 47319, 47320, + 47321, 47322, 47323, 47324, 47325, 47326, 47327, 47328, + 47329, 47330, 47331, 47332, 47333, 47334, 47335, 47336, + 47337, 47338, 47339, 47340, 47341, 47342, 47343, 47344, + 47345, 47346, 47347, 47348, 47349, 47350, 47351, 47352, + 47353, 47354, 47355, 47356, 47357, 47358, 47359, 47360, + 47361, 47362, 47363, 47364, 47365, 47366, 47367, 47368, + 47369, 47370, 47371, 47372, 47373, 47374, 47375, 47376, + 47377, 47378, 47379, 47380, 47381, 47382, 47383, 47384, + 47385, 47386, 47387, 47388, 47389, 47390, 47391, 47392, + 47393, 47394, 47395, 47396, 47397, 47398, 47399, 47400, + 47401, 47402, 47403, 47404, 47405, 47406, 47407, 47408, + 47409, 47410, 47411, 47412, 47413, 47414, 47415, 47416, + 47417, 47418, 47419, 47420, 47421, 47422, 47423, 47424, + 47425, 47426, 47427, 47428, 47429, 47430, 47431, 47432, + 47433, 47434, 47435, 47436, 47437, 47438, 47439, 47440, + 47441, 47442, 47443, 47444, 47445, 47446, 47447, 47448, + 47449, 47450, 47451, 47452, 47453, 47454, 47455, 47456, + 47457, 47458, 47459, 47460, 47461, 47462, 47463, 47464, + 47465, 47466, 47467, 47468, 47469, 47470, 47471, 47472, + 47473, 47474, 47475, 47476, 47477, 47478, 47479, 47480, + 47481, 47482, 47483, 47484, 47485, 47486, 47487, 47488, + 47489, 47490, 47491, 47492, 47493, 47494, 47495, 47496, + 47497, 47498, 47499, 47500, 47501, 47502, 47503, 47504, + 47505, 47506, 47507, 47508, 47509, 47510, 47511, 47512, + 47513, 47514, 47515, 47516, 47517, 47518, 47519, 47520, + 47521, 47522, 47523, 47524, 47525, 47526, 47527, 47528, + 47529, 47530, 47531, 47532, 47533, 47534, 47535, 47536, + 47537, 47538, 47539, 47540, 47541, 47542, 47543, 47544, + 47545, 47546, 47547, 47548, 47549, 47550, 47551, 47552, + 47553, 47554, 47555, 47556, 47557, 47558, 47559, 47560, + 47561, 47562, 47563, 47564, 47565, 47566, 47567, 47568, + 47569, 47570, 47571, 47572, 47573, 47574, 47575, 47576, + 47577, 47578, 47579, 47580, 47581, 47582, 47583, 47584, + 47585, 47586, 47587, 47588, 47589, 47590, 47591, 47592, + 47593, 47594, 47595, 47596, 47597, 47598, 47599, 47600, + 47601, 47602, 47603, 47604, 47605, 47606, 47607, 47608, + 47609, 47610, 47611, 47612, 47613, 47614, 47615, 47616, + 47617, 47618, 47619, 47620, 47621, 47622, 47623, 47624, + 47625, 47626, 47627, 47628, 47629, 47630, 47631, 47632, + 47633, 47634, 47635, 47636, 47637, 47638, 47639, 47640, + 47641, 47642, 47643, 47644, 47645, 47646, 47647, 47648, + 47649, 47650, 47651, 47652, 47653, 47654, 47655, 47656, + 47657, 47658, 47659, 47660, 47661, 47662, 47663, 47664, + 47665, 47666, 47667, 47668, 47669, 47670, 47671, 47672, + 47673, 47674, 47675, 47676, 47677, 47678, 47679, 47680, + 47681, 47682, 47683, 47684, 47685, 47686, 47687, 47688, + 47689, 47690, 47691, 47692, 47693, 47694, 47695, 47696, + 47697, 47698, 47699, 47700, 47701, 47702, 47703, 47704, + 47705, 47706, 47707, 47708, 47709, 47710, 47711, 47712, + 47713, 47714, 47715, 47716, 47717, 47718, 47719, 47720, + 47721, 47722, 47723, 47724, 47725, 47726, 47727, 47728, + 47729, 47730, 47731, 47732, 47733, 47734, 47735, 47736, + 47737, 47738, 47739, 47740, 47741, 47742, 47743, 47744, + 47745, 47746, 47747, 47748, 47749, 47750, 47751, 47752, + 47753, 47754, 47755, 47756, 47757, 47758, 47759, 47760, + 47761, 47762, 47763, 47764, 47765, 47766, 47767, 47768, + 47769, 47770, 47771, 47772, 47773, 47774, 47775, 47776, + 47777, 47778, 47779, 47780, 47781, 47782, 47783, 47784, + 47785, 47786, 47787, 47788, 47789, 47790, 47791, 47792, + 47793, 47794, 47795, 47796, 47797, 47798, 47799, 47800, + 47801, 47802, 47803, 47804, 47805, 47806, 47807, 47808, + 47809, 47810, 47811, 47812, 47813, 47814, 47815, 47816, + 47817, 47818, 47819, 47820, 47821, 47822, 47823, 47824, + 47825, 47826, 47827, 47828, 47829, 47830, 47831, 47832, + 47833, 47834, 47835, 47836, 47837, 47838, 47839, 47840, + 47841, 47842, 47843, 47844, 47845, 47846, 47847, 47848, + 47849, 47850, 47851, 47852, 47853, 47854, 47855, 47856, + 47857, 47858, 47859, 47860, 47861, 47862, 47863, 47864, + 47865, 47866, 47867, 47868, 47869, 47870, 47871, 47872, + 47873, 47874, 47875, 47876, 47877, 47878, 47879, 47880, + 47881, 47882, 47883, 47884, 47885, 47886, 47887, 47888, + 47889, 47890, 47891, 47892, 47893, 47894, 47895, 47896, + 47897, 47898, 47899, 47900, 47901, 47902, 47903, 47904, + 47905, 47906, 47907, 47908, 47909, 47910, 47911, 47912, + 47913, 47914, 47915, 47916, 47917, 47918, 47919, 47920, + 47921, 47922, 47923, 47924, 47925, 47926, 47927, 47928, + 47929, 47930, 47931, 47932, 47933, 47934, 47935, 47936, + 47937, 47938, 47939, 47940, 47941, 47942, 47943, 47944, + 47945, 47946, 47947, 47948, 47949, 47950, 47951, 47952, + 47953, 47954, 47955, 47956, 47957, 47958, 47959, 47960, + 47961, 47962, 47963, 47964, 47965, 47966, 47967, 47968, + 47969, 47970, 47971, 47972, 47973, 47974, 47975, 47976, + 47977, 47978, 47979, 47980, 47981, 47982, 47983, 47984, + 47985, 47986, 47987, 47988, 47989, 47990, 47991, 47992, + 47993, 47994, 47995, 47996, 47997, 47998, 47999, 48000, + 48001, 48002, 48003, 48004, 48005, 48006, 48007, 48008, + 48009, 48010, 48011, 48012, 48013, 48014, 48015, 48016, + 48017, 48018, 48019, 48020, 48021, 48022, 48023, 48024, + 48025, 48026, 48027, 48028, 48029, 48030, 48031, 48032, + 48033, 48034, 48035, 48036, 48037, 48038, 48039, 48040, + 48041, 48042, 48043, 48044, 48045, 48046, 48047, 48048, + 48049, 48050, 48051, 48052, 48053, 48054, 48055, 48056, + 48057, 48058, 48059, 48060, 48061, 48062, 48063, 48064, + 48065, 48066, 48067, 48068, 48069, 48070, 48071, 48072, + 48073, 48074, 48075, 48076, 48077, 48078, 48079, 48080, + 48081, 48082, 48083, 48084, 48085, 48086, 48087, 48088, + 48089, 48090, 48091, 48092, 48093, 48094, 48095, 48096, + 48097, 48098, 48099, 48100, 48101, 48102, 48103, 48104, + 48105, 48106, 48107, 48108, 48109, 48110, 48111, 48112, + 48113, 48114, 48115, 48116, 48117, 48118, 48119, 48120, + 48121, 48122, 48123, 48124, 48125, 48126, 48127, 48128, + 48129, 48130, 48131, 48132, 48133, 48134, 48135, 48136, + 48137, 48138, 48139, 48140, 48141, 48142, 48143, 48144, + 48145, 48146, 48147, 48148, 48149, 48150, 48151, 48152, + 48153, 48154, 48155, 48156, 48157, 48158, 48159, 48160, + 48161, 48162, 48163, 48164, 48165, 48166, 48167, 48168, + 48169, 48170, 48171, 48172, 48173, 48174, 48175, 48176, + 48177, 48178, 48179, 48180, 48181, 48182, 48183, 48184, + 48185, 48186, 48187, 48188, 48189, 48190, 48191, 48192, + 48193, 48194, 48195, 48196, 48197, 48198, 48199, 48200, + 48201, 48202, 48203, 48204, 48205, 48206, 48207, 48208, + 48209, 48210, 48211, 48212, 48213, 48214, 48215, 48216, + 48217, 48218, 48219, 48220, 48221, 48222, 48223, 48224, + 48225, 48226, 48227, 48228, 48229, 48230, 48231, 48232, + 48233, 48234, 48235, 48236, 48237, 48238, 48239, 48240, + 48241, 48242, 48243, 48244, 48245, 48246, 48247, 48248, + 48249, 48250, 48251, 48252, 48253, 48254, 48255, 48256, + 48257, 48258, 48259, 48260, 48261, 48262, 48263, 48264, + 48265, 48266, 48267, 48268, 48269, 48270, 48271, 48272, + 48273, 48274, 48275, 48276, 48277, 48278, 48279, 48280, + 48281, 48282, 48283, 48284, 48285, 48286, 48287, 48288, + 48289, 48290, 48291, 48292, 48293, 48294, 48295, 48296, + 48297, 48298, 48299, 48300, 48301, 48302, 48303, 48304, + 48305, 48306, 48307, 48308, 48309, 48310, 48311, 48312, + 48313, 48314, 48315, 48316, 48317, 48318, 48319, 48320, + 48321, 48322, 48323, 48324, 48325, 48326, 48327, 48328, + 48329, 48330, 48331, 48332, 48333, 48334, 48335, 48336, + 48337, 48338, 48339, 48340, 48341, 48342, 48343, 48344, + 48345, 48346, 48347, 48348, 48349, 48350, 48351, 48352, + 48353, 48354, 48355, 48356, 48357, 48358, 48359, 48360, + 48361, 48362, 48363, 48364, 48365, 48366, 48367, 48368, + 48369, 48370, 48371, 48372, 48373, 48374, 48375, 48376, + 48377, 48378, 48379, 48380, 48381, 48382, 48383, 48384, + 48385, 48386, 48387, 48388, 48389, 48390, 48391, 48392, + 48393, 48394, 48395, 48396, 48397, 48398, 48399, 48400, + 48401, 48402, 48403, 48404, 48405, 48406, 48407, 48408, + 48409, 48410, 48411, 48412, 48413, 48414, 48415, 48416, + 48417, 48418, 48419, 48420, 48421, 48422, 48423, 48424, + 48425, 48426, 48427, 48428, 48429, 48430, 48431, 48432, + 48433, 48434, 48435, 48436, 48437, 48438, 48439, 48440, + 48441, 48442, 48443, 48444, 48445, 48446, 48447, 48448, + 48449, 48450, 48451, 48452, 48453, 48454, 48455, 48456, + 48457, 48458, 48459, 48460, 48461, 48462, 48463, 48464, + 48465, 48466, 48467, 48468, 48469, 48470, 48471, 48472, + 48473, 48474, 48475, 48476, 48477, 48478, 48479, 48480, + 48481, 48482, 48483, 48484, 48485, 48486, 48487, 48488, + 48489, 48490, 48491, 48492, 48493, 48494, 48495, 48496, + 48497, 48498, 48499, 48500, 48501, 48502, 48503, 48504, + 48505, 48506, 48507, 48508, 48509, 48510, 48511, 48512, + 48513, 48514, 48515, 48516, 48517, 48518, 48519, 48520, + 48521, 48522, 48523, 48524, 48525, 48526, 48527, 48528, + 48529, 48530, 48531, 48532, 48533, 48534, 48535, 48536, + 48537, 48538, 48539, 48540, 48541, 48542, 48543, 48544, + 48545, 48546, 48547, 48548, 48549, 48550, 48551, 48552, + 48553, 48554, 48555, 48556, 48557, 48558, 48559, 48560, + 48561, 48562, 48563, 48564, 48565, 48566, 48567, 48568, + 48569, 48570, 48571, 48572, 48573, 48574, 48575, 48576, + 48577, 48578, 48579, 48580, 48581, 48582, 48583, 48584, + 48585, 48586, 48587, 48588, 48589, 48590, 48591, 48592, + 48593, 48594, 48595, 48596, 48597, 48598, 48599, 48600, + 48601, 48602, 48603, 48604, 48605, 48606, 48607, 48608, + 48609, 48610, 48611, 48612, 48613, 48614, 48615, 48616, + 48617, 48618, 48619, 48620, 48621, 48622, 48623, 48624, + 48625, 48626, 48627, 48628, 48629, 48630, 48631, 48632, + 48633, 48634, 48635, 48636, 48637, 48638, 48639, 48640, + 48641, 48642, 48643, 48644, 48645, 48646, 48647, 48648, + 48649, 48650, 48651, 48652, 48653, 48654, 48655, 48656, + 48657, 48658, 48659, 48660, 48661, 48662, 48663, 48664, + 48665, 48666, 48667, 48668, 48669, 48670, 48671, 48672, + 48673, 48674, 48675, 48676, 48677, 48678, 48679, 48680, + 48681, 48682, 48683, 48684, 48685, 48686, 48687, 48688, + 48689, 48690, 48691, 48692, 48693, 48694, 48695, 48696, + 48697, 48698, 48699, 48700, 48701, 48702, 48703, 48704, + 48705, 48706, 48707, 48708, 48709, 48710, 48711, 48712, + 48713, 48714, 48715, 48716, 48717, 48718, 48719, 48720, + 48721, 48722, 48723, 48724, 48725, 48726, 48727, 48728, + 48729, 48730, 48731, 48732, 48733, 48734, 48735, 48736, + 48737, 48738, 48739, 48740, 48741, 48742, 48743, 48744, + 48745, 48746, 48747, 48748, 48749, 48750, 48751, 48752, + 48753, 48754, 48755, 48756, 48757, 48758, 48759, 48760, + 48761, 48762, 48763, 48764, 48765, 48766, 48767, 48768, + 48769, 48770, 48771, 48772, 48773, 48774, 48775, 48776, + 48777, 48778, 48779, 48780, 48781, 48782, 48783, 48784, + 48785, 48786, 48787, 48788, 48789, 48790, 48791, 48792, + 48793, 48794, 48795, 48796, 48797, 48798, 48799, 48800, + 48801, 48802, 48803, 48804, 48805, 48806, 48807, 48808, + 48809, 48810, 48811, 48812, 48813, 48814, 48815, 48816, + 48817, 48818, 48819, 48820, 48821, 48822, 48823, 48824, + 48825, 48826, 48827, 48828, 48829, 48830, 48831, 48832, + 48833, 48834, 48835, 48836, 48837, 48838, 48839, 48840, + 48841, 48842, 48843, 48844, 48845, 48846, 48847, 48848, + 48849, 48850, 48851, 48852, 48853, 48854, 48855, 48856, + 48857, 48858, 48859, 48860, 48861, 48862, 48863, 48864, + 48865, 48866, 48867, 48868, 48869, 48870, 48871, 48872, + 48873, 48874, 48875, 48876, 48877, 48878, 48879, 48880, + 48881, 48882, 48883, 48884, 48885, 48886, 48887, 48888, + 48889, 48890, 48891, 48892, 48893, 48894, 48895, 48896, + 48897, 48898, 48899, 48900, 48901, 48902, 48903, 48904, + 48905, 48906, 48907, 48908, 48909, 48910, 48911, 48912, + 48913, 48914, 48915, 48916, 48917, 48918, 48919, 48920, + 48921, 48922, 48923, 48924, 48925, 48926, 48927, 48928, + 48929, 48930, 48931, 48932, 48933, 48934, 48935, 48936, + 48937, 48938, 48939, 48940, 48941, 48942, 48943, 48944, + 48945, 48946, 48947, 48948, 48949, 48950, 48951, 48952, + 48953, 48954, 48955, 48956, 48957, 48958, 48959, 48960, + 48961, 48962, 48963, 48964, 48965, 48966, 48967, 48968, + 48969, 48970, 48971, 48972, 48973, 48974, 48975, 48976, + 48977, 48978, 48979, 48980, 48981, 48982, 48983, 48984, + 48985, 48986, 48987, 48988, 48989, 48990, 48991, 48992, + 48993, 48994, 48995, 48996, 48997, 48998, 48999, 49000, + 49001, 49002, 49003, 49004, 49005, 49006, 49007, 49008, + 49009, 49010, 49011, 49012, 49013, 49014, 49015, 49016, + 49017, 49018, 49019, 49020, 49021, 49022, 49023, 49024, + 49025, 49026, 49027, 49028, 49029, 49030, 49031, 49032, + 49033, 49034, 49035, 49036, 49037, 49038, 49039, 49040, + 49041, 49042, 49043, 49044, 49045, 49046, 49047, 49048, + 49049, 49050, 49051, 49052, 49053, 49054, 49055, 49056, + 49057, 49058, 49059, 49060, 49061, 49062, 49063, 49064, + 49065, 49066, 49067, 49068, 49069, 49070, 49071, 49072, + 49073, 49074, 49075, 49076, 49077, 49078, 49079, 49080, + 49081, 49082, 49083, 49084, 49085, 49086, 49087, 49088, + 49089, 49090, 49091, 49092, 49093, 49094, 49095, 49096, + 49097, 49098, 49099, 49100, 49101, 49102, 49103, 49104, + 49105, 49106, 49107, 49108, 49109, 49110, 49111, 49112, + 49113, 49114, 49115, 49116, 49117, 49118, 49119, 49120, + 49121, 49122, 49123, 49124, 49125, 49126, 49127, 49128, + 49129, 49130, 49131, 49132, 49133, 49134, 49135, 49136, + 49137, 49138, 49139, 49140, 49141, 49142, 49143, 49144, + 49145, 49146, 49147, 49148, 49149, 49150, 49151, 49152, + 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, + 49161, 49162, 49163, 49164, 49165, 49166, 49167, 49168, + 49169, 49170, 49171, 49172, 49173, 49174, 49175, 49176, + 49177, 49178, 49179, 49180, 49181, 49182, 49183, 49184, + 49185, 49186, 49187, 49188, 49189, 49190, 49191, 49192, + 49193, 49194, 49195, 49196, 49197, 49198, 49199, 49200, + 49201, 49202, 49203, 49204, 49205, 49206, 49207, 49208, + 49209, 49210, 49211, 49212, 49213, 49214, 49215, 49216, + 49217, 49218, 49219, 49220, 49221, 49222, 49223, 49224, + 49225, 49226, 49227, 49228, 49229, 49230, 49231, 49232, + 49233, 49234, 49235, 49236, 49237, 49238, 49239, 49240, + 49241, 49242, 49243, 49244, 49245, 49246, 49247, 49248, + 49249, 49250, 49251, 49252, 49253, 49254, 49255, 49256, + 49257, 49258, 49259, 49260, 49261, 49262, 49263, 49264, + 49265, 49266, 49267, 49268, 49269, 49270, 49271, 49272, + 49273, 49274, 49275, 49276, 49277, 49278, 49279, 49280, + 49281, 49282, 49283, 49284, 49285, 49286, 49287, 49288, + 49289, 49290, 49291, 49292, 49293, 49294, 49295, 49296, + 49297, 49298, 49299, 49300, 49301, 49302, 49303, 49304, + 49305, 49306, 49307, 49308, 49309, 49310, 49311, 49312, + 49313, 49314, 49315, 49316, 49317, 49318, 49319, 49320, + 49321, 49322, 49323, 49324, 49325, 49326, 49327, 49328, + 49329, 49330, 49331, 49332, 49333, 49334, 49335, 49336, + 49337, 49338, 49339, 49340, 49341, 49342, 49343, 49344, + 49345, 49346, 49347, 49348, 49349, 49350, 49351, 49352, + 49353, 49354, 49355, 49356, 49357, 49358, 49359, 49360, + 49361, 49362, 49363, 49364, 49365, 49366, 49367, 49368, + 49369, 49370, 49371, 49372, 49373, 49374, 49375, 49376, + 49377, 49378, 49379, 49380, 49381, 49382, 49383, 49384, + 49385, 49386, 49387, 49388, 49389, 49390, 49391, 49392, + 49393, 49394, 49395, 49396, 49397, 49398, 49399, 49400, + 49401, 49402, 49403, 49404, 49405, 49406, 49407, 49408, + 49409, 49410, 49411, 49412, 49413, 49414, 49415, 49416, + 49417, 49418, 49419, 49420, 49421, 49422, 49423, 49424, + 49425, 49426, 49427, 49428, 49429, 49430, 49431, 49432, + 49433, 49434, 49435, 49436, 49437, 49438, 49439, 49440, + 49441, 49442, 49443, 49444, 49445, 49446, 49447, 49448, + 49449, 49450, 49451, 49452, 49453, 49454, 49455, 49456, + 49457, 49458, 49459, 49460, 49461, 49462, 49463, 49464, + 49465, 49466, 49467, 49468, 49469, 49470, 49471, 49472, + 49473, 49474, 49475, 49476, 49477, 49478, 49479, 49480, + 49481, 49482, 49483, 49484, 49485, 49486, 49487, 49488, + 49489, 49490, 49491, 49492, 49493, 49494, 49495, 49496, + 49497, 49498, 49499, 49500, 49501, 49502, 49503, 49504, + 49505, 49506, 49507, 49508, 49509, 49510, 49511, 49512, + 49513, 49514, 49515, 49516, 49517, 49518, 49519, 49520, + 49521, 49522, 49523, 49524, 49525, 49526, 49527, 49528, + 49529, 49530, 49531, 49532, 49533, 49534, 49535, 49536, + 49537, 49538, 49539, 49540, 49541, 49542, 49543, 49544, + 49545, 49546, 49547, 49548, 49549, 49550, 49551, 49552, + 49553, 49554, 49555, 49556, 49557, 49558, 49559, 49560, + 49561, 49562, 49563, 49564, 49565, 49566, 49567, 49568, + 49569, 49570, 49571, 49572, 49573, 49574, 49575, 49576, + 49577, 49578, 49579, 49580, 49581, 49582, 49583, 49584, + 49585, 49586, 49587, 49588, 49589, 49590, 49591, 49592, + 49593, 49594, 49595, 49596, 49597, 49598, 49599, 49600, + 49601, 49602, 49603, 49604, 49605, 49606, 49607, 49608, + 49609, 49610, 49611, 49612, 49613, 49614, 49615, 49616, + 49617, 49618, 49619, 49620, 49621, 49622, 49623, 49624, + 49625, 49626, 49627, 49628, 49629, 49630, 49631, 49632, + 49633, 49634, 49635, 49636, 49637, 49638, 49639, 49640, + 49641, 49642, 49643, 49644, 49645, 49646, 49647, 49648, + 49649, 49650, 49651, 49652, 49653, 49654, 49655, 49656, + 49657, 49658, 49659, 49660, 49661, 49662, 49663, 49664, + 49665, 49666, 49667, 49668, 49669, 49670, 49671, 49672, + 49673, 49674, 49675, 49676, 49677, 49678, 49679, 49680, + 49681, 49682, 49683, 49684, 49685, 49686, 49687, 49688, + 49689, 49690, 49691, 49692, 49693, 49694, 49695, 49696, + 49697, 49698, 49699, 49700, 49701, 49702, 49703, 49704, + 49705, 49706, 49707, 49708, 49709, 49710, 49711, 49712, + 49713, 49714, 49715, 49716, 49717, 49718, 49719, 49720, + 49721, 49722, 49723, 49724, 49725, 49726, 49727, 49728, + 49729, 49730, 49731, 49732, 49733, 49734, 49735, 49736, + 49737, 49738, 49739, 49740, 49741, 49742, 49743, 49744, + 49745, 49746, 49747, 49748, 49749, 49750, 49751, 49752, + 49753, 49754, 49755, 49756, 49757, 49758, 49759, 49760, + 49761, 49762, 49763, 49764, 49765, 49766, 49767, 49768, + 49769, 49770, 49771, 49772, 49773, 49774, 49775, 49776, + 49777, 49778, 49779, 49780, 49781, 49782, 49783, 49784, + 49785, 49786, 49787, 49788, 49789, 49790, 49791, 49792, + 49793, 49794, 49795, 49796, 49797, 49798, 49799, 49800, + 49801, 49802, 49803, 49804, 49805, 49806, 49807, 49808, + 49809, 49810, 49811, 49812, 49813, 49814, 49815, 49816, + 49817, 49818, 49819, 49820, 49821, 49822, 49823, 49824, + 49825, 49826, 49827, 49828, 49829, 49830, 49831, 49832, + 49833, 49834, 49835, 49836, 49837, 49838, 49839, 49840, + 49841, 49842, 49843, 49844, 49845, 49846, 49847, 49848, + 49849, 49850, 49851, 49852, 49853, 49854, 49855, 49856, + 49857, 49858, 49859, 49860, 49861, 49862, 49863, 49864, + 49865, 49866, 49867, 49868, 49869, 49870, 49871, 49872, + 49873, 49874, 49875, 49876, 49877, 49878, 49879, 49880, + 49881, 49882, 49883, 49884, 49885, 49886, 49887, 49888, + 49889, 49890, 49891, 49892, 49893, 49894, 49895, 49896, + 49897, 49898, 49899, 49900, 49901, 49902, 49903, 49904, + 49905, 49906, 49907, 49908, 49909, 49910, 49911, 49912, + 49913, 49914, 49915, 49916, 49917, 49918, 49919, 49920, + 49921, 49922, 49923, 49924, 49925, 49926, 49927, 49928, + 49929, 49930, 49931, 49932, 49933, 49934, 49935, 49936, + 49937, 49938, 49939, 49940, 49941, 49942, 49943, 49944, + 49945, 49946, 49947, 49948, 49949, 49950, 49951, 49952, + 49953, 49954, 49955, 49956, 49957, 49958, 49959, 49960, + 49961, 49962, 49963, 49964, 49965, 49966, 49967, 49968, + 49969, 49970, 49971, 49972, 49973, 49974, 49975, 49976, + 49977, 49978, 49979, 49980, 49981, 49982, 49983, 49984, + 49985, 49986, 49987, 49988, 49989, 49990, 49991, 49992, + 49993, 49994, 49995, 49996, 49997, 49998, 49999, 50000, + 50001, 50002, 50003, 50004, 50005, 50006, 50007, 50008, + 50009, 50010, 50011, 50012, 50013, 50014, 50015, 50016, + 50017, 50018, 50019, 50020, 50021, 50022, 50023, 50024, + 50025, 50026, 50027, 50028, 50029, 50030, 50031, 50032, + 50033, 50034, 50035, 50036, 50037, 50038, 50039, 50040, + 50041, 50042, 50043, 50044, 50045, 50046, 50047, 50048, + 50049, 50050, 50051, 50052, 50053, 50054, 50055, 50056, + 50057, 50058, 50059, 50060, 50061, 50062, 50063, 50064, + 50065, 50066, 50067, 50068, 50069, 50070, 50071, 50072, + 50073, 50074, 50075, 50076, 50077, 50078, 50079, 50080, + 50081, 50082, 50083, 50084, 50085, 50086, 50087, 50088, + 50089, 50090, 50091, 50092, 50093, 50094, 50095, 50096, + 50097, 50098, 50099, 50100, 50101, 50102, 50103, 50104, + 50105, 50106, 50107, 50108, 50109, 50110, 50111, 50112, + 50113, 50114, 50115, 50116, 50117, 50118, 50119, 50120, + 50121, 50122, 50123, 50124, 50125, 50126, 50127, 50128, + 50129, 50130, 50131, 50132, 50133, 50134, 50135, 50136, + 50137, 50138, 50139, 50140, 50141, 50142, 50143, 50144, + 50145, 50146, 50147, 50148, 50149, 50150, 50151, 50152, + 50153, 50154, 50155, 50156, 50157, 50158, 50159, 50160, + 50161, 50162, 50163, 50164, 50165, 50166, 50167, 50168, + 50169, 50170, 50171, 50172, 50173, 50174, 50175, 50176, + 50177, 50178, 50179, 50180, 50181, 50182, 50183, 50184, + 50185, 50186, 50187, 50188, 50189, 50190, 50191, 50192, + 50193, 50194, 50195, 50196, 50197, 50198, 50199, 50200, + 50201, 50202, 50203, 50204, 50205, 50206, 50207, 50208, + 50209, 50210, 50211, 50212, 50213, 50214, 50215, 50216, + 50217, 50218, 50219, 50220, 50221, 50222, 50223, 50224, + 50225, 50226, 50227, 50228, 50229, 50230, 50231, 50232, + 50233, 50234, 50235, 50236, 50237, 50238, 50239, 50240, + 50241, 50242, 50243, 50244, 50245, 50246, 50247, 50248, + 50249, 50250, 50251, 50252, 50253, 50254, 50255, 50256, + 50257, 50258, 50259, 50260, 50261, 50262, 50263, 50264, + 50265, 50266, 50267, 50268, 50269, 50270, 50271, 50272, + 50273, 50274, 50275, 50276, 50277, 50278, 50279, 50280, + 50281, 50282, 50283, 50284, 50285, 50286, 50287, 50288, + 50289, 50290, 50291, 50292, 50293, 50294, 50295, 50296, + 50297, 50298, 50299, 50300, 50301, 50302, 50303, 50304, + 50305, 50306, 50307, 50308, 50309, 50310, 50311, 50312, + 50313, 50314, 50315, 50316, 50317, 50318, 50319, 50320, + 50321, 50322, 50323, 50324, 50325, 50326, 50327, 50328, + 50329, 50330, 50331, 50332, 50333, 50334, 50335, 50336, + 50337, 50338, 50339, 50340, 50341, 50342, 50343, 50344, + 50345, 50346, 50347, 50348, 50349, 50350, 50351, 50352, + 50353, 50354, 50355, 50356, 50357, 50358, 50359, 50360, + 50361, 50362, 50363, 50364, 50365, 50366, 50367, 50368, + 50369, 50370, 50371, 50372, 50373, 50374, 50375, 50376, + 50377, 50378, 50379, 50380, 50381, 50382, 50383, 50384, + 50385, 50386, 50387, 50388, 50389, 50390, 50391, 50392, + 50393, 50394, 50395, 50396, 50397, 50398, 50399, 50400, + 50401, 50402, 50403, 50404, 50405, 50406, 50407, 50408, + 50409, 50410, 50411, 50412, 50413, 50414, 50415, 50416, + 50417, 50418, 50419, 50420, 50421, 50422, 50423, 50424, + 50425, 50426, 50427, 50428, 50429, 50430, 50431, 50432, + 50433, 50434, 50435, 50436, 50437, 50438, 50439, 50440, + 50441, 50442, 50443, 50444, 50445, 50446, 50447, 50448, + 50449, 50450, 50451, 50452, 50453, 50454, 50455, 50456, + 50457, 50458, 50459, 50460, 50461, 50462, 50463, 50464, + 50465, 50466, 50467, 50468, 50469, 50470, 50471, 50472, + 50473, 50474, 50475, 50476, 50477, 50478, 50479, 50480, + 50481, 50482, 50483, 50484, 50485, 50486, 50487, 50488, + 50489, 50490, 50491, 50492, 50493, 50494, 50495, 50496, + 50497, 50498, 50499, 50500, 50501, 50502, 50503, 50504, + 50505, 50506, 50507, 50508, 50509, 50510, 50511, 50512, + 50513, 50514, 50515, 50516, 50517, 50518, 50519, 50520, + 50521, 50522, 50523, 50524, 50525, 50526, 50527, 50528, + 50529, 50530, 50531, 50532, 50533, 50534, 50535, 50536, + 50537, 50538, 50539, 50540, 50541, 50542, 50543, 50544, + 50545, 50546, 50547, 50548, 50549, 50550, 50551, 50552, + 50553, 50554, 50555, 50556, 50557, 50558, 50559, 50560, + 50561, 50562, 50563, 50564, 50565, 50566, 50567, 50568, + 50569, 50570, 50571, 50572, 50573, 50574, 50575, 50576, + 50577, 50578, 50579, 50580, 50581, 50582, 50583, 50584, + 50585, 50586, 50587, 50588, 50589, 50590, 50591, 50592, + 50593, 50594, 50595, 50596, 50597, 50598, 50599, 50600, + 50601, 50602, 50603, 50604, 50605, 50606, 50607, 50608, + 50609, 50610, 50611, 50612, 50613, 50614, 50615, 50616, + 50617, 50618, 50619, 50620, 50621, 50622, 50623, 50624, + 50625, 50626, 50627, 50628, 50629, 50630, 50631, 50632, + 50633, 50634, 50635, 50636, 50637, 50638, 50639, 50640, + 50641, 50642, 50643, 50644, 50645, 50646, 50647, 50648, + 50649, 50650, 50651, 50652, 50653, 50654, 50655, 50656, + 50657, 50658, 50659, 50660, 50661, 50662, 50663, 50664, + 50665, 50666, 50667, 50668, 50669, 50670, 50671, 50672, + 50673, 50674, 50675, 50676, 50677, 50678, 50679, 50680, + 50681, 50682, 50683, 50684, 50685, 50686, 50687, 50688, + 50689, 50690, 50691, 50692, 50693, 50694, 50695, 50696, + 50697, 50698, 50699, 50700, 50701, 50702, 50703, 50704, + 50705, 50706, 50707, 50708, 50709, 50710, 50711, 50712, + 50713, 50714, 50715, 50716, 50717, 50718, 50719, 50720, + 50721, 50722, 50723, 50724, 50725, 50726, 50727, 50728, + 50729, 50730, 50731, 50732, 50733, 50734, 50735, 50736, + 50737, 50738, 50739, 50740, 50741, 50742, 50743, 50744, + 50745, 50746, 50747, 50748, 50749, 50750, 50751, 50752, + 50753, 50754, 50755, 50756, 50757, 50758, 50759, 50760, + 50761, 50762, 50763, 50764, 50765, 50766, 50767, 50768, + 50769, 50770, 50771, 50772, 50773, 50774, 50775, 50776, + 50777, 50778, 50779, 50780, 50781, 50782, 50783, 50784, + 50785, 50786, 50787, 50788, 50789, 50790, 50791, 50792, + 50793, 50794, 50795, 50796, 50797, 50798, 50799, 50800, + 50801, 50802, 50803, 50804, 50805, 50806, 50807, 50808, + 50809, 50810, 50811, 50812, 50813, 50814, 50815, 50816, + 50817, 50818, 50819, 50820, 50821, 50822, 50823, 50824, + 50825, 50826, 50827, 50828, 50829, 50830, 50831, 50832, + 50833, 50834, 50835, 50836, 50837, 50838, 50839, 50840, + 50841, 50842, 50843, 50844, 50845, 50846, 50847, 50848, + 50849, 50850, 50851, 50852, 50853, 50854, 50855, 50856, + 50857, 50858, 50859, 50860, 50861, 50862, 50863, 50864, + 50865, 50866, 50867, 50868, 50869, 50870, 50871, 50872, + 50873, 50874, 50875, 50876, 50877, 50878, 50879, 50880, + 50881, 50882, 50883, 50884, 50885, 50886, 50887, 50888, + 50889, 50890, 50891, 50892, 50893, 50894, 50895, 50896, + 50897, 50898, 50899, 50900, 50901, 50902, 50903, 50904, + 50905, 50906, 50907, 50908, 50909, 50910, 50911, 50912, + 50913, 50914, 50915, 50916, 50917, 50918, 50919, 50920, + 50921, 50922, 50923, 50924, 50925, 50926, 50927, 50928, + 50929, 50930, 50931, 50932, 50933, 50934, 50935, 50936, + 50937, 50938, 50939, 50940, 50941, 50942, 50943, 50944, + 50945, 50946, 50947, 50948, 50949, 50950, 50951, 50952, + 50953, 50954, 50955, 50956, 50957, 50958, 50959, 50960, + 50961, 50962, 50963, 50964, 50965, 50966, 50967, 50968, + 50969, 50970, 50971, 50972, 50973, 50974, 50975, 50976, + 50977, 50978, 50979, 50980, 50981, 50982, 50983, 50984, + 50985, 50986, 50987, 50988, 50989, 50990, 50991, 50992, + 50993, 50994, 50995, 50996, 50997, 50998, 50999, 51000, + 51001, 51002, 51003, 51004, 51005, 51006, 51007, 51008, + 51009, 51010, 51011, 51012, 51013, 51014, 51015, 51016, + 51017, 51018, 51019, 51020, 51021, 51022, 51023, 51024, + 51025, 51026, 51027, 51028, 51029, 51030, 51031, 51032, + 51033, 51034, 51035, 51036, 51037, 51038, 51039, 51040, + 51041, 51042, 51043, 51044, 51045, 51046, 51047, 51048, + 51049, 51050, 51051, 51052, 51053, 51054, 51055, 51056, + 51057, 51058, 51059, 51060, 51061, 51062, 51063, 51064, + 51065, 51066, 51067, 51068, 51069, 51070, 51071, 51072, + 51073, 51074, 51075, 51076, 51077, 51078, 51079, 51080, + 51081, 51082, 51083, 51084, 51085, 51086, 51087, 51088, + 51089, 51090, 51091, 51092, 51093, 51094, 51095, 51096, + 51097, 51098, 51099, 51100, 51101, 51102, 51103, 51104, + 51105, 51106, 51107, 51108, 51109, 51110, 51111, 51112, + 51113, 51114, 51115, 51116, 51117, 51118, 51119, 51120, + 51121, 51122, 51123, 51124, 51125, 51126, 51127, 51128, + 51129, 51130, 51131, 51132, 51133, 51134, 51135, 51136, + 51137, 51138, 51139, 51140, 51141, 51142, 51143, 51144, + 51145, 51146, 51147, 51148, 51149, 51150, 51151, 51152, + 51153, 51154, 51155, 51156, 51157, 51158, 51159, 51160, + 51161, 51162, 51163, 51164, 51165, 51166, 51167, 51168, + 51169, 51170, 51171, 51172, 51173, 51174, 51175, 51176, + 51177, 51178, 51179, 51180, 51181, 51182, 51183, 51184, + 51185, 51186, 51187, 51188, 51189, 51190, 51191, 51192, + 51193, 51194, 51195, 51196, 51197, 51198, 51199, 51200, + 51201, 51202, 51203, 51204, 51205, 51206, 51207, 51208, + 51209, 51210, 51211, 51212, 51213, 51214, 51215, 51216, + 51217, 51218, 51219, 51220, 51221, 51222, 51223, 51224, + 51225, 51226, 51227, 51228, 51229, 51230, 51231, 51232, + 51233, 51234, 51235, 51236, 51237, 51238, 51239, 51240, + 51241, 51242, 51243, 51244, 51245, 51246, 51247, 51248, + 51249, 51250, 51251, 51252, 51253, 51254, 51255, 51256, + 51257, 51258, 51259, 51260, 51261, 51262, 51263, 51264, + 51265, 51266, 51267, 51268, 51269, 51270, 51271, 51272, + 51273, 51274, 51275, 51276, 51277, 51278, 51279, 51280, + 51281, 51282, 51283, 51284, 51285, 51286, 51287, 51288, + 51289, 51290, 51291, 51292, 51293, 51294, 51295, 51296, + 51297, 51298, 51299, 51300, 51301, 51302, 51303, 51304, + 51305, 51306, 51307, 51308, 51309, 51310, 51311, 51312, + 51313, 51314, 51315, 51316, 51317, 51318, 51319, 51320, + 51321, 51322, 51323, 51324, 51325, 51326, 51327, 51328, + 51329, 51330, 51331, 51332, 51333, 51334, 51335, 51336, + 51337, 51338, 51339, 51340, 51341, 51342, 51343, 51344, + 51345, 51346, 51347, 51348, 51349, 51350, 51351, 51352, + 51353, 51354, 51355, 51356, 51357, 51358, 51359, 51360, + 51361, 51362, 51363, 51364, 51365, 51366, 51367, 51368, + 51369, 51370, 51371, 51372, 51373, 51374, 51375, 51376, + 51377, 51378, 51379, 51380, 51381, 51382, 51383, 51384, + 51385, 51386, 51387, 51388, 51389, 51390, 51391, 51392, + 51393, 51394, 51395, 51396, 51397, 51398, 51399, 51400, + 51401, 51402, 51403, 51404, 51405, 51406, 51407, 51408, + 51409, 51410, 51411, 51412, 51413, 51414, 51415, 51416, + 51417, 51418, 51419, 51420, 51421, 51422, 51423, 51424, + 51425, 51426, 51427, 51428, 51429, 51430, 51431, 51432, + 51433, 51434, 51435, 51436, 51437, 51438, 51439, 51440, + 51441, 51442, 51443, 51444, 51445, 51446, 51447, 51448, + 51449, 51450, 51451, 51452, 51453, 51454, 51455, 51456, + 51457, 51458, 51459, 51460, 51461, 51462, 51463, 51464, + 51465, 51466, 51467, 51468, 51469, 51470, 51471, 51472, + 51473, 51474, 51475, 51476, 51477, 51478, 51479, 51480, + 51481, 51482, 51483, 51484, 51485, 51486, 51487, 51488, + 51489, 51490, 51491, 51492, 51493, 51494, 51495, 51496, + 51497, 51498, 51499, 51500, 51501, 51502, 51503, 51504, + 51505, 51506, 51507, 51508, 51509, 51510, 51511, 51512, + 51513, 51514, 51515, 51516, 51517, 51518, 51519, 51520, + 51521, 51522, 51523, 51524, 51525, 51526, 51527, 51528, + 51529, 51530, 51531, 51532, 51533, 51534, 51535, 51536, + 51537, 51538, 51539, 51540, 51541, 51542, 51543, 51544, + 51545, 51546, 51547, 51548, 51549, 51550, 51551, 51552, + 51553, 51554, 51555, 51556, 51557, 51558, 51559, 51560, + 51561, 51562, 51563, 51564, 51565, 51566, 51567, 51568, + 51569, 51570, 51571, 51572, 51573, 51574, 51575, 51576, + 51577, 51578, 51579, 51580, 51581, 51582, 51583, 51584, + 51585, 51586, 51587, 51588, 51589, 51590, 51591, 51592, + 51593, 51594, 51595, 51596, 51597, 51598, 51599, 51600, + 51601, 51602, 51603, 51604, 51605, 51606, 51607, 51608, + 51609, 51610, 51611, 51612, 51613, 51614, 51615, 51616, + 51617, 51618, 51619, 51620, 51621, 51622, 51623, 51624, + 51625, 51626, 51627, 51628, 51629, 51630, 51631, 51632, + 51633, 51634, 51635, 51636, 51637, 51638, 51639, 51640, + 51641, 51642, 51643, 51644, 51645, 51646, 51647, 51648, + 51649, 51650, 51651, 51652, 51653, 51654, 51655, 51656, + 51657, 51658, 51659, 51660, 51661, 51662, 51663, 51664, + 51665, 51666, 51667, 51668, 51669, 51670, 51671, 51672, + 51673, 51674, 51675, 51676, 51677, 51678, 51679, 51680, + 51681, 51682, 51683, 51684, 51685, 51686, 51687, 51688, + 51689, 51690, 51691, 51692, 51693, 51694, 51695, 51696, + 51697, 51698, 51699, 51700, 51701, 51702, 51703, 51704, + 51705, 51706, 51707, 51708, 51709, 51710, 51711, 51712, + 51713, 51714, 51715, 51716, 51717, 51718, 51719, 51720, + 51721, 51722, 51723, 51724, 51725, 51726, 51727, 51728, + 51729, 51730, 51731, 51732, 51733, 51734, 51735, 51736, + 51737, 51738, 51739, 51740, 51741, 51742, 51743, 51744, + 51745, 51746, 51747, 51748, 51749, 51750, 51751, 51752, + 51753, 51754, 51755, 51756, 51757, 51758, 51759, 51760, + 51761, 51762, 51763, 51764, 51765, 51766, 51767, 51768, + 51769, 51770, 51771, 51772, 51773, 51774, 51775, 51776, + 51777, 51778, 51779, 51780, 51781, 51782, 51783, 51784, + 51785, 51786, 51787, 51788, 51789, 51790, 51791, 51792, + 51793, 51794, 51795, 51796, 51797, 51798, 51799, 51800, + 51801, 51802, 51803, 51804, 51805, 51806, 51807, 51808, + 51809, 51810, 51811, 51812, 51813, 51814, 51815, 51816, + 51817, 51818, 51819, 51820, 51821, 51822, 51823, 51824, + 51825, 51826, 51827, 51828, 51829, 51830, 51831, 51832, + 51833, 51834, 51835, 51836, 51837, 51838, 51839, 51840, + 51841, 51842, 51843, 51844, 51845, 51846, 51847, 51848, + 51849, 51850, 51851, 51852, 51853, 51854, 51855, 51856, + 51857, 51858, 51859, 51860, 51861, 51862, 51863, 51864, + 51865, 51866, 51867, 51868, 51869, 51870, 51871, 51872, + 51873, 51874, 51875, 51876, 51877, 51878, 51879, 51880, + 51881, 51882, 51883, 51884, 51885, 51886, 51887, 51888, + 51889, 51890, 51891, 51892, 51893, 51894, 51895, 51896, + 51897, 51898, 51899, 51900, 51901, 51902, 51903, 51904, + 51905, 51906, 51907, 51908, 51909, 51910, 51911, 51912, + 51913, 51914, 51915, 51916, 51917, 51918, 51919, 51920, + 51921, 51922, 51923, 51924, 51925, 51926, 51927, 51928, + 51929, 51930, 51931, 51932, 51933, 51934, 51935, 51936, + 51937, 51938, 51939, 51940, 51941, 51942, 51943, 51944, + 51945, 51946, 51947, 51948, 51949, 51950, 51951, 51952, + 51953, 51954, 51955, 51956, 51957, 51958, 51959, 51960, + 51961, 51962, 51963, 51964, 51965, 51966, 51967, 51968, + 51969, 51970, 51971, 51972, 51973, 51974, 51975, 51976, + 51977, 51978, 51979, 51980, 51981, 51982, 51983, 51984, + 51985, 51986, 51987, 51988, 51989, 51990, 51991, 51992, + 51993, 51994, 51995, 51996, 51997, 51998, 51999, 52000, + 52001, 52002, 52003, 52004, 52005, 52006, 52007, 52008, + 52009, 52010, 52011, 52012, 52013, 52014, 52015, 52016, + 52017, 52018, 52019, 52020, 52021, 52022, 52023, 52024, + 52025, 52026, 52027, 52028, 52029, 52030, 52031, 52032, + 52033, 52034, 52035, 52036, 52037, 52038, 52039, 52040, + 52041, 52042, 52043, 52044, 52045, 52046, 52047, 52048, + 52049, 52050, 52051, 52052, 52053, 52054, 52055, 52056, + 52057, 52058, 52059, 52060, 52061, 52062, 52063, 52064, + 52065, 52066, 52067, 52068, 52069, 52070, 52071, 52072, + 52073, 52074, 52075, 52076, 52077, 52078, 52079, 52080, + 52081, 52082, 52083, 52084, 52085, 52086, 52087, 52088, + 52089, 52090, 52091, 52092, 52093, 52094, 52095, 52096, + 52097, 52098, 52099, 52100, 52101, 52102, 52103, 52104, + 52105, 52106, 52107, 52108, 52109, 52110, 52111, 52112, + 52113, 52114, 52115, 52116, 52117, 52118, 52119, 52120, + 52121, 52122, 52123, 52124, 52125, 52126, 52127, 52128, + 52129, 52130, 52131, 52132, 52133, 52134, 52135, 52136, + 52137, 52138, 52139, 52140, 52141, 52142, 52143, 52144, + 52145, 52146, 52147, 52148, 52149, 52150, 52151, 52152, + 52153, 52154, 52155, 52156, 52157, 52158, 52159, 52160, + 52161, 52162, 52163, 52164, 52165, 52166, 52167, 52168, + 52169, 52170, 52171, 52172, 52173, 52174, 52175, 52176, + 52177, 52178, 52179, 52180, 52181, 52182, 52183, 52184, + 52185, 52186, 52187, 52188, 52189, 52190, 52191, 52192, + 52193, 52194, 52195, 52196, 52197, 52198, 52199, 52200, + 52201, 52202, 52203, 52204, 52205, 52206, 52207, 52208, + 52209, 52210, 52211, 52212, 52213, 52214, 52215, 52216, + 52217, 52218, 52219, 52220, 52221, 52222, 52223, 52224, + 52225, 52226, 52227, 52228, 52229, 52230, 52231, 52232, + 52233, 52234, 52235, 52236, 52237, 52238, 52239, 52240, + 52241, 52242, 52243, 52244, 52245, 52246, 52247, 52248, + 52249, 52250, 52251, 52252, 52253, 52254, 52255, 52256, + 52257, 52258, 52259, 52260, 52261, 52262, 52263, 52264, + 52265, 52266, 52267, 52268, 52269, 52270, 52271, 52272, + 52273, 52274, 52275, 52276, 52277, 52278, 52279, 52280, + 52281, 52282, 52283, 52284, 52285, 52286, 52287, 52288, + 52289, 52290, 52291, 52292, 52293, 52294, 52295, 52296, + 52297, 52298, 52299, 52300, 52301, 52302, 52303, 52304, + 52305, 52306, 52307, 52308, 52309, 52310, 52311, 52312, + 52313, 52314, 52315, 52316, 52317, 52318, 52319, 52320, + 52321, 52322, 52323, 52324, 52325, 52326, 52327, 52328, + 52329, 52330, 52331, 52332, 52333, 52334, 52335, 52336, + 52337, 52338, 52339, 52340, 52341, 52342, 52343, 52344, + 52345, 52346, 52347, 52348, 52349, 52350, 52351, 52352, + 52353, 52354, 52355, 52356, 52357, 52358, 52359, 52360, + 52361, 52362, 52363, 52364, 52365, 52366, 52367, 52368, + 52369, 52370, 52371, 52372, 52373, 52374, 52375, 52376, + 52377, 52378, 52379, 52380, 52381, 52382, 52383, 52384, + 52385, 52386, 52387, 52388, 52389, 52390, 52391, 52392, + 52393, 52394, 52395, 52396, 52397, 52398, 52399, 52400, + 52401, 52402, 52403, 52404, 52405, 52406, 52407, 52408, + 52409, 52410, 52411, 52412, 52413, 52414, 52415, 52416, + 52417, 52418, 52419, 52420, 52421, 52422, 52423, 52424, + 52425, 52426, 52427, 52428, 52429, 52430, 52431, 52432, + 52433, 52434, 52435, 52436, 52437, 52438, 52439, 52440, + 52441, 52442, 52443, 52444, 52445, 52446, 52447, 52448, + 52449, 52450, 52451, 52452, 52453, 52454, 52455, 52456, + 52457, 52458, 52459, 52460, 52461, 52462, 52463, 52464, + 52465, 52466, 52467, 52468, 52469, 52470, 52471, 52472, + 52473, 52474, 52475, 52476, 52477, 52478, 52479, 52480, + 52481, 52482, 52483, 52484, 52485, 52486, 52487, 52488, + 52489, 52490, 52491, 52492, 52493, 52494, 52495, 52496, + 52497, 52498, 52499, 52500, 52501, 52502, 52503, 52504, + 52505, 52506, 52507, 52508, 52509, 52510, 52511, 52512, + 52513, 52514, 52515, 52516, 52517, 52518, 52519, 52520, + 52521, 52522, 52523, 52524, 52525, 52526, 52527, 52528, + 52529, 52530, 52531, 52532, 52533, 52534, 52535, 52536, + 52537, 52538, 52539, 52540, 52541, 52542, 52543, 52544, + 52545, 52546, 52547, 52548, 52549, 52550, 52551, 52552, + 52553, 52554, 52555, 52556, 52557, 52558, 52559, 52560, + 52561, 52562, 52563, 52564, 52565, 52566, 52567, 52568, + 52569, 52570, 52571, 52572, 52573, 52574, 52575, 52576, + 52577, 52578, 52579, 52580, 52581, 52582, 52583, 52584, + 52585, 52586, 52587, 52588, 52589, 52590, 52591, 52592, + 52593, 52594, 52595, 52596, 52597, 52598, 52599, 52600, + 52601, 52602, 52603, 52604, 52605, 52606, 52607, 52608, + 52609, 52610, 52611, 52612, 52613, 52614, 52615, 52616, + 52617, 52618, 52619, 52620, 52621, 52622, 52623, 52624, + 52625, 52626, 52627, 52628, 52629, 52630, 52631, 52632, + 52633, 52634, 52635, 52636, 52637, 52638, 52639, 52640, + 52641, 52642, 52643, 52644, 52645, 52646, 52647, 52648, + 52649, 52650, 52651, 52652, 52653, 52654, 52655, 52656, + 52657, 52658, 52659, 52660, 52661, 52662, 52663, 52664, + 52665, 52666, 52667, 52668, 52669, 52670, 52671, 52672, + 52673, 52674, 52675, 52676, 52677, 52678, 52679, 52680, + 52681, 52682, 52683, 52684, 52685, 52686, 52687, 52688, + 52689, 52690, 52691, 52692, 52693, 52694, 52695, 52696, + 52697, 52698, 52699, 52700, 52701, 52702, 52703, 52704, + 52705, 52706, 52707, 52708, 52709, 52710, 52711, 52712, + 52713, 52714, 52715, 52716, 52717, 52718, 52719, 52720, + 52721, 52722, 52723, 52724, 52725, 52726, 52727, 52728, + 52729, 52730, 52731, 52732, 52733, 52734, 52735, 52736, + 52737, 52738, 52739, 52740, 52741, 52742, 52743, 52744, + 52745, 52746, 52747, 52748, 52749, 52750, 52751, 52752, + 52753, 52754, 52755, 52756, 52757, 52758, 52759, 52760, + 52761, 52762, 52763, 52764, 52765, 52766, 52767, 52768, + 52769, 52770, 52771, 52772, 52773, 52774, 52775, 52776, + 52777, 52778, 52779, 52780, 52781, 52782, 52783, 52784, + 52785, 52786, 52787, 52788, 52789, 52790, 52791, 52792, + 52793, 52794, 52795, 52796, 52797, 52798, 52799, 52800, + 52801, 52802, 52803, 52804, 52805, 52806, 52807, 52808, + 52809, 52810, 52811, 52812, 52813, 52814, 52815, 52816, + 52817, 52818, 52819, 52820, 52821, 52822, 52823, 52824, + 52825, 52826, 52827, 52828, 52829, 52830, 52831, 52832, + 52833, 52834, 52835, 52836, 52837, 52838, 52839, 52840, + 52841, 52842, 52843, 52844, 52845, 52846, 52847, 52848, + 52849, 52850, 52851, 52852, 52853, 52854, 52855, 52856, + 52857, 52858, 52859, 52860, 52861, 52862, 52863, 52864, + 52865, 52866, 52867, 52868, 52869, 52870, 52871, 52872, + 52873, 52874, 52875, 52876, 52877, 52878, 52879, 52880, + 52881, 52882, 52883, 52884, 52885, 52886, 52887, 52888, + 52889, 52890, 52891, 52892, 52893, 52894, 52895, 52896, + 52897, 52898, 52899, 52900, 52901, 52902, 52903, 52904, + 52905, 52906, 52907, 52908, 52909, 52910, 52911, 52912, + 52913, 52914, 52915, 52916, 52917, 52918, 52919, 52920, + 52921, 52922, 52923, 52924, 52925, 52926, 52927, 52928, + 52929, 52930, 52931, 52932, 52933, 52934, 52935, 52936, + 52937, 52938, 52939, 52940, 52941, 52942, 52943, 52944, + 52945, 52946, 52947, 52948, 52949, 52950, 52951, 52952, + 52953, 52954, 52955, 52956, 52957, 52958, 52959, 52960, + 52961, 52962, 52963, 52964, 52965, 52966, 52967, 52968, + 52969, 52970, 52971, 52972, 52973, 52974, 52975, 52976, + 52977, 52978, 52979, 52980, 52981, 52982, 52983, 52984, + 52985, 52986, 52987, 52988, 52989, 52990, 52991, 52992, + 52993, 52994, 52995, 52996, 52997, 52998, 52999, 53000, + 53001, 53002, 53003, 53004, 53005, 53006, 53007, 53008, + 53009, 53010, 53011, 53012, 53013, 53014, 53015, 53016, + 53017, 53018, 53019, 53020, 53021, 53022, 53023, 53024, + 53025, 53026, 53027, 53028, 53029, 53030, 53031, 53032, + 53033, 53034, 53035, 53036, 53037, 53038, 53039, 53040, + 53041, 53042, 53043, 53044, 53045, 53046, 53047, 53048, + 53049, 53050, 53051, 53052, 53053, 53054, 53055, 53056, + 53057, 53058, 53059, 53060, 53061, 53062, 53063, 53064, + 53065, 53066, 53067, 53068, 53069, 53070, 53071, 53072, + 53073, 53074, 53075, 53076, 53077, 53078, 53079, 53080, + 53081, 53082, 53083, 53084, 53085, 53086, 53087, 53088, + 53089, 53090, 53091, 53092, 53093, 53094, 53095, 53096, + 53097, 53098, 53099, 53100, 53101, 53102, 53103, 53104, + 53105, 53106, 53107, 53108, 53109, 53110, 53111, 53112, + 53113, 53114, 53115, 53116, 53117, 53118, 53119, 53120, + 53121, 53122, 53123, 53124, 53125, 53126, 53127, 53128, + 53129, 53130, 53131, 53132, 53133, 53134, 53135, 53136, + 53137, 53138, 53139, 53140, 53141, 53142, 53143, 53144, + 53145, 53146, 53147, 53148, 53149, 53150, 53151, 53152, + 53153, 53154, 53155, 53156, 53157, 53158, 53159, 53160, + 53161, 53162, 53163, 53164, 53165, 53166, 53167, 53168, + 53169, 53170, 53171, 53172, 53173, 53174, 53175, 53176, + 53177, 53178, 53179, 53180, 53181, 53182, 53183, 53184, + 53185, 53186, 53187, 53188, 53189, 53190, 53191, 53192, + 53193, 53194, 53195, 53196, 53197, 53198, 53199, 53200, + 53201, 53202, 53203, 53204, 53205, 53206, 53207, 53208, + 53209, 53210, 53211, 53212, 53213, 53214, 53215, 53216, + 53217, 53218, 53219, 53220, 53221, 53222, 53223, 53224, + 53225, 53226, 53227, 53228, 53229, 53230, 53231, 53232, + 53233, 53234, 53235, 53236, 53237, 53238, 53239, 53240, + 53241, 53242, 53243, 53244, 53245, 53246, 53247, 53248, + 53249, 53250, 53251, 53252, 53253, 53254, 53255, 53256, + 53257, 53258, 53259, 53260, 53261, 53262, 53263, 53264, + 53265, 53266, 53267, 53268, 53269, 53270, 53271, 53272, + 53273, 53274, 53275, 53276, 53277, 53278, 53279, 53280, + 53281, 53282, 53283, 53284, 53285, 53286, 53287, 53288, + 53289, 53290, 53291, 53292, 53293, 53294, 53295, 53296, + 53297, 53298, 53299, 53300, 53301, 53302, 53303, 53304, + 53305, 53306, 53307, 53308, 53309, 53310, 53311, 53312, + 53313, 53314, 53315, 53316, 53317, 53318, 53319, 53320, + 53321, 53322, 53323, 53324, 53325, 53326, 53327, 53328, + 53329, 53330, 53331, 53332, 53333, 53334, 53335, 53336, + 53337, 53338, 53339, 53340, 53341, 53342, 53343, 53344, + 53345, 53346, 53347, 53348, 53349, 53350, 53351, 53352, + 53353, 53354, 53355, 53356, 53357, 53358, 53359, 53360, + 53361, 53362, 53363, 53364, 53365, 53366, 53367, 53368, + 53369, 53370, 53371, 53372, 53373, 53374, 53375, 53376, + 53377, 53378, 53379, 53380, 53381, 53382, 53383, 53384, + 53385, 53386, 53387, 53388, 53389, 53390, 53391, 53392, + 53393, 53394, 53395, 53396, 53397, 53398, 53399, 53400, + 53401, 53402, 53403, 53404, 53405, 53406, 53407, 53408, + 53409, 53410, 53411, 53412, 53413, 53414, 53415, 53416, + 53417, 53418, 53419, 53420, 53421, 53422, 53423, 53424, + 53425, 53426, 53427, 53428, 53429, 53430, 53431, 53432, + 53433, 53434, 53435, 53436, 53437, 53438, 53439, 53440, + 53441, 53442, 53443, 53444, 53445, 53446, 53447, 53448, + 53449, 53450, 53451, 53452, 53453, 53454, 53455, 53456, + 53457, 53458, 53459, 53460, 53461, 53462, 53463, 53464, + 53465, 53466, 53467, 53468, 53469, 53470, 53471, 53472, + 53473, 53474, 53475, 53476, 53477, 53478, 53479, 53480, + 53481, 53482, 53483, 53484, 53485, 53486, 53487, 53488, + 53489, 53490, 53491, 53492, 53493, 53494, 53495, 53496, + 53497, 53498, 53499, 53500, 53501, 53502, 53503, 53504, + 53505, 53506, 53507, 53508, 53509, 53510, 53511, 53512, + 53513, 53514, 53515, 53516, 53517, 53518, 53519, 53520, + 53521, 53522, 53523, 53524, 53525, 53526, 53527, 53528, + 53529, 53530, 53531, 53532, 53533, 53534, 53535, 53536, + 53537, 53538, 53539, 53540, 53541, 53542, 53543, 53544, + 53545, 53546, 53547, 53548, 53549, 53550, 53551, 53552, + 53553, 53554, 53555, 53556, 53557, 53558, 53559, 53560, + 53561, 53562, 53563, 53564, 53565, 53566, 53567, 53568, + 53569, 53570, 53571, 53572, 53573, 53574, 53575, 53576, + 53577, 53578, 53579, 53580, 53581, 53582, 53583, 53584, + 53585, 53586, 53587, 53588, 53589, 53590, 53591, 53592, + 53593, 53594, 53595, 53596, 53597, 53598, 53599, 53600, + 53601, 53602, 53603, 53604, 53605, 53606, 53607, 53608, + 53609, 53610, 53611, 53612, 53613, 53614, 53615, 53616, + 53617, 53618, 53619, 53620, 53621, 53622, 53623, 53624, + 53625, 53626, 53627, 53628, 53629, 53630, 53631, 53632, + 53633, 53634, 53635, 53636, 53637, 53638, 53639, 53640, + 53641, 53642, 53643, 53644, 53645, 53646, 53647, 53648, + 53649, 53650, 53651, 53652, 53653, 53654, 53655, 53656, + 53657, 53658, 53659, 53660, 53661, 53662, 53663, 53664, + 53665, 53666, 53667, 53668, 53669, 53670, 53671, 53672, + 53673, 53674, 53675, 53676, 53677, 53678, 53679, 53680, + 53681, 53682, 53683, 53684, 53685, 53686, 53687, 53688, + 53689, 53690, 53691, 53692, 53693, 53694, 53695, 53696, + 53697, 53698, 53699, 53700, 53701, 53702, 53703, 53704, + 53705, 53706, 53707, 53708, 53709, 53710, 53711, 53712, + 53713, 53714, 53715, 53716, 53717, 53718, 53719, 53720, + 53721, 53722, 53723, 53724, 53725, 53726, 53727, 53728, + 53729, 53730, 53731, 53732, 53733, 53734, 53735, 53736, + 53737, 53738, 53739, 53740, 53741, 53742, 53743, 53744, + 53745, 53746, 53747, 53748, 53749, 53750, 53751, 53752, + 53753, 53754, 53755, 53756, 53757, 53758, 53759, 53760, + 53761, 53762, 53763, 53764, 53765, 53766, 53767, 53768, + 53769, 53770, 53771, 53772, 53773, 53774, 53775, 53776, + 53777, 53778, 53779, 53780, 53781, 53782, 53783, 53784, + 53785, 53786, 53787, 53788, 53789, 53790, 53791, 53792, + 53793, 53794, 53795, 53796, 53797, 53798, 53799, 53800, + 53801, 53802, 53803, 53804, 53805, 53806, 53807, 53808, + 53809, 53810, 53811, 53812, 53813, 53814, 53815, 53816, + 53817, 53818, 53819, 53820, 53821, 53822, 53823, 53824, + 53825, 53826, 53827, 53828, 53829, 53830, 53831, 53832, + 53833, 53834, 53835, 53836, 53837, 53838, 53839, 53840, + 53841, 53842, 53843, 53844, 53845, 53846, 53847, 53848, + 53849, 53850, 53851, 53852, 53853, 53854, 53855, 53856, + 53857, 53858, 53859, 53860, 53861, 53862, 53863, 53864, + 53865, 53866, 53867, 53868, 53869, 53870, 53871, 53872, + 53873, 53874, 53875, 53876, 53877, 53878, 53879, 53880, + 53881, 53882, 53883, 53884, 53885, 53886, 53887, 53888, + 53889, 53890, 53891, 53892, 53893, 53894, 53895, 53896, + 53897, 53898, 53899, 53900, 53901, 53902, 53903, 53904, + 53905, 53906, 53907, 53908, 53909, 53910, 53911, 53912, + 53913, 53914, 53915, 53916, 53917, 53918, 53919, 53920, + 53921, 53922, 53923, 53924, 53925, 53926, 53927, 53928, + 53929, 53930, 53931, 53932, 53933, 53934, 53935, 53936, + 53937, 53938, 53939, 53940, 53941, 53942, 53943, 53944, + 53945, 53946, 53947, 53948, 53949, 53950, 53951, 53952, + 53953, 53954, 53955, 53956, 53957, 53958, 53959, 53960, + 53961, 53962, 53963, 53964, 53965, 53966, 53967, 53968, + 53969, 53970, 53971, 53972, 53973, 53974, 53975, 53976, + 53977, 53978, 53979, 53980, 53981, 53982, 53983, 53984, + 53985, 53986, 53987, 53988, 53989, 53990, 53991, 53992, + 53993, 53994, 53995, 53996, 53997, 53998, 53999, 54000, + 54001, 54002, 54003, 54004, 54005, 54006, 54007, 54008, + 54009, 54010, 54011, 54012, 54013, 54014, 54015, 54016, + 54017, 54018, 54019, 54020, 54021, 54022, 54023, 54024, + 54025, 54026, 54027, 54028, 54029, 54030, 54031, 54032, + 54033, 54034, 54035, 54036, 54037, 54038, 54039, 54040, + 54041, 54042, 54043, 54044, 54045, 54046, 54047, 54048, + 54049, 54050, 54051, 54052, 54053, 54054, 54055, 54056, + 54057, 54058, 54059, 54060, 54061, 54062, 54063, 54064, + 54065, 54066, 54067, 54068, 54069, 54070, 54071, 54072, + 54073, 54074, 54075, 54076, 54077, 54078, 54079, 54080, + 54081, 54082, 54083, 54084, 54085, 54086, 54087, 54088, + 54089, 54090, 54091, 54092, 54093, 54094, 54095, 54096, + 54097, 54098, 54099, 54100, 54101, 54102, 54103, 54104, + 54105, 54106, 54107, 54108, 54109, 54110, 54111, 54112, + 54113, 54114, 54115, 54116, 54117, 54118, 54119, 54120, + 54121, 54122, 54123, 54124, 54125, 54126, 54127, 54128, + 54129, 54130, 54131, 54132, 54133, 54134, 54135, 54136, + 54137, 54138, 54139, 54140, 54141, 54142, 54143, 54144, + 54145, 54146, 54147, 54148, 54149, 54150, 54151, 54152, + 54153, 54154, 54155, 54156, 54157, 54158, 54159, 54160, + 54161, 54162, 54163, 54164, 54165, 54166, 54167, 54168, + 54169, 54170, 54171, 54172, 54173, 54174, 54175, 54176, + 54177, 54178, 54179, 54180, 54181, 54182, 54183, 54184, + 54185, 54186, 54187, 54188, 54189, 54190, 54191, 54192, + 54193, 54194, 54195, 54196, 54197, 54198, 54199, 54200, + 54201, 54202, 54203, 54204, 54205, 54206, 54207, 54208, + 54209, 54210, 54211, 54212, 54213, 54214, 54215, 54216, + 54217, 54218, 54219, 54220, 54221, 54222, 54223, 54224, + 54225, 54226, 54227, 54228, 54229, 54230, 54231, 54232, + 54233, 54234, 54235, 54236, 54237, 54238, 54239, 54240, + 54241, 54242, 54243, 54244, 54245, 54246, 54247, 54248, + 54249, 54250, 54251, 54252, 54253, 54254, 54255, 54256, + 54257, 54258, 54259, 54260, 54261, 54262, 54263, 54264, + 54265, 54266, 54267, 54268, 54269, 54270, 54271, 54272, + 54273, 54274, 54275, 54276, 54277, 54278, 54279, 54280, + 54281, 54282, 54283, 54284, 54285, 54286, 54287, 54288, + 54289, 54290, 54291, 54292, 54293, 54294, 54295, 54296, + 54297, 54298, 54299, 54300, 54301, 54302, 54303, 54304, + 54305, 54306, 54307, 54308, 54309, 54310, 54311, 54312, + 54313, 54314, 54315, 54316, 54317, 54318, 54319, 54320, + 54321, 54322, 54323, 54324, 54325, 54326, 54327, 54328, + 54329, 54330, 54331, 54332, 54333, 54334, 54335, 54336, + 54337, 54338, 54339, 54340, 54341, 54342, 54343, 54344, + 54345, 54346, 54347, 54348, 54349, 54350, 54351, 54352, + 54353, 54354, 54355, 54356, 54357, 54358, 54359, 54360, + 54361, 54362, 54363, 54364, 54365, 54366, 54367, 54368, + 54369, 54370, 54371, 54372, 54373, 54374, 54375, 54376, + 54377, 54378, 54379, 54380, 54381, 54382, 54383, 54384, + 54385, 54386, 54387, 54388, 54389, 54390, 54391, 54392, + 54393, 54394, 54395, 54396, 54397, 54398, 54399, 54400, + 54401, 54402, 54403, 54404, 54405, 54406, 54407, 54408, + 54409, 54410, 54411, 54412, 54413, 54414, 54415, 54416, + 54417, 54418, 54419, 54420, 54421, 54422, 54423, 54424, + 54425, 54426, 54427, 54428, 54429, 54430, 54431, 54432, + 54433, 54434, 54435, 54436, 54437, 54438, 54439, 54440, + 54441, 54442, 54443, 54444, 54445, 54446, 54447, 54448, + 54449, 54450, 54451, 54452, 54453, 54454, 54455, 54456, + 54457, 54458, 54459, 54460, 54461, 54462, 54463, 54464, + 54465, 54466, 54467, 54468, 54469, 54470, 54471, 54472, + 54473, 54474, 54475, 54476, 54477, 54478, 54479, 54480, + 54481, 54482, 54483, 54484, 54485, 54486, 54487, 54488, + 54489, 54490, 54491, 54492, 54493, 54494, 54495, 54496, + 54497, 54498, 54499, 54500, 54501, 54502, 54503, 54504, + 54505, 54506, 54507, 54508, 54509, 54510, 54511, 54512, + 54513, 54514, 54515, 54516, 54517, 54518, 54519, 54520, + 54521, 54522, 54523, 54524, 54525, 54526, 54527, 54528, + 54529, 54530, 54531, 54532, 54533, 54534, 54535, 54536, + 54537, 54538, 54539, 54540, 54541, 54542, 54543, 54544, + 54545, 54546, 54547, 54548, 54549, 54550, 54551, 54552, + 54553, 54554, 54555, 54556, 54557, 54558, 54559, 54560, + 54561, 54562, 54563, 54564, 54565, 54566, 54567, 54568, + 54569, 54570, 54571, 54572, 54573, 54574, 54575, 54576, + 54577, 54578, 54579, 54580, 54581, 54582, 54583, 54584, + 54585, 54586, 54587, 54588, 54589, 54590, 54591, 54592, + 54593, 54594, 54595, 54596, 54597, 54598, 54599, 54600, + 54601, 54602, 54603, 54604, 54605, 54606, 54607, 54608, + 54609, 54610, 54611, 54612, 54613, 54614, 54615, 54616, + 54617, 54618, 54619, 54620, 54621, 54622, 54623, 54624, + 54625, 54626, 54627, 54628, 54629, 54630, 54631, 54632, + 54633, 54634, 54635, 54636, 54637, 54638, 54639, 54640, + 54641, 54642, 54643, 54644, 54645, 54646, 54647, 54648, + 54649, 54650, 54651, 54652, 54653, 54654, 54655, 54656, + 54657, 54658, 54659, 54660, 54661, 54662, 54663, 54664, + 54665, 54666, 54667, 54668, 54669, 54670, 54671, 54672, + 54673, 54674, 54675, 54676, 54677, 54678, 54679, 54680, + 54681, 54682, 54683, 54684, 54685, 54686, 54687, 54688, + 54689, 54690, 54691, 54692, 54693, 54694, 54695, 54696, + 54697, 54698, 54699, 54700, 54701, 54702, 54703, 54704, + 54705, 54706, 54707, 54708, 54709, 54710, 54711, 54712, + 54713, 54714, 54715, 54716, 54717, 54718, 54719, 54720, + 54721, 54722, 54723, 54724, 54725, 54726, 54727, 54728, + 54729, 54730, 54731, 54732, 54733, 54734, 54735, 54736, + 54737, 54738, 54739, 54740, 54741, 54742, 54743, 54744, + 54745, 54746, 54747, 54748, 54749, 54750, 54751, 54752, + 54753, 54754, 54755, 54756, 54757, 54758, 54759, 54760, + 54761, 54762, 54763, 54764, 54765, 54766, 54767, 54768, + 54769, 54770, 54771, 54772, 54773, 54774, 54775, 54776, + 54777, 54778, 54779, 54780, 54781, 54782, 54783, 54784, + 54785, 54786, 54787, 54788, 54789, 54790, 54791, 54792, + 54793, 54794, 54795, 54796, 54797, 54798, 54799, 54800, + 54801, 54802, 54803, 54804, 54805, 54806, 54807, 54808, + 54809, 54810, 54811, 54812, 54813, 54814, 54815, 54816, + 54817, 54818, 54819, 54820, 54821, 54822, 54823, 54824, + 54825, 54826, 54827, 54828, 54829, 54830, 54831, 54832, + 54833, 54834, 54835, 54836, 54837, 54838, 54839, 54840, + 54841, 54842, 54843, 54844, 54845, 54846, 54847, 54848, + 54849, 54850, 54851, 54852, 54853, 54854, 54855, 54856, + 54857, 54858, 54859, 54860, 54861, 54862, 54863, 54864, + 54865, 54866, 54867, 54868, 54869, 54870, 54871, 54872, + 54873, 54874, 54875, 54876, 54877, 54878, 54879, 54880, + 54881, 54882, 54883, 54884, 54885, 54886, 54887, 54888, + 54889, 54890, 54891, 54892, 54893, 54894, 54895, 54896, + 54897, 54898, 54899, 54900, 54901, 54902, 54903, 54904, + 54905, 54906, 54907, 54908, 54909, 54910, 54911, 54912, + 54913, 54914, 54915, 54916, 54917, 54918, 54919, 54920, + 54921, 54922, 54923, 54924, 54925, 54926, 54927, 54928, + 54929, 54930, 54931, 54932, 54933, 54934, 54935, 54936, + 54937, 54938, 54939, 54940, 54941, 54942, 54943, 54944, + 54945, 54946, 54947, 54948, 54949, 54950, 54951, 54952, + 54953, 54954, 54955, 54956, 54957, 54958, 54959, 54960, + 54961, 54962, 54963, 54964, 54965, 54966, 54967, 54968, + 54969, 54970, 54971, 54972, 54973, 54974, 54975, 54976, + 54977, 54978, 54979, 54980, 54981, 54982, 54983, 54984, + 54985, 54986, 54987, 54988, 54989, 54990, 54991, 54992, + 54993, 54994, 54995, 54996, 54997, 54998, 54999, 55000, + 55001, 55002, 55003, 55004, 55005, 55006, 55007, 55008, + 55009, 55010, 55011, 55012, 55013, 55014, 55015, 55016, + 55017, 55018, 55019, 55020, 55021, 55022, 55023, 55024, + 55025, 55026, 55027, 55028, 55029, 55030, 55031, 55032, + 55033, 55034, 55035, 55036, 55037, 55038, 55039, 55040, + 55041, 55042, 55043, 55044, 55045, 55046, 55047, 55048, + 55049, 55050, 55051, 55052, 55053, 55054, 55055, 55056, + 55057, 55058, 55059, 55060, 55061, 55062, 55063, 55064, + 55065, 55066, 55067, 55068, 55069, 55070, 55071, 55072, + 55073, 55074, 55075, 55076, 55077, 55078, 55079, 55080, + 55081, 55082, 55083, 55084, 55085, 55086, 55087, 55088, + 55089, 55090, 55091, 55092, 55093, 55094, 55095, 55096, + 55097, 55098, 55099, 55100, 55101, 55102, 55103, 55104, + 55105, 55106, 55107, 55108, 55109, 55110, 55111, 55112, + 55113, 55114, 55115, 55116, 55117, 55118, 55119, 55120, + 55121, 55122, 55123, 55124, 55125, 55126, 55127, 55128, + 55129, 55130, 55131, 55132, 55133, 55134, 55135, 55136, + 55137, 55138, 55139, 55140, 55141, 55142, 55143, 55144, + 55145, 55146, 55147, 55148, 55149, 55150, 55151, 55152, + 55153, 55154, 55155, 55156, 55157, 55158, 55159, 55160, + 55161, 55162, 55163, 55164, 55165, 55166, 55167, 55168, + 55169, 55170, 55171, 55172, 55173, 55174, 55175, 55176, + 55177, 55178, 55179, 55180, 55181, 55182, 55183, 55184, + 55185, 55186, 55187, 55188, 55189, 55190, 55191, 55192, + 55193, 55194, 55195, 55196, 55197, 55198, 55199, 55200, + 55201, 55202, 55203, 55216, 55217, 55218, 55219, 55220, + 55221, 55222, 55223, 55224, 55225, 55226, 55227, 55228, + 55229, 55230, 55231, 55232, 55233, 55234, 55235, 55236, + 55237, 55238, 55243, 55244, 55245, 55246, 55247, 55248, + 55249, 55250, 55251, 55252, 55253, 55254, 55255, 55256, + 55257, 55258, 55259, 55260, 55261, 55262, 55263, 55264, + 55265, 55266, 55267, 55268, 55269, 55270, 55271, 55272, + 55273, 55274, 55275, 55276, 55277, 55278, 55279, 55280, + 55281, 55282, 55283, 55284, 55285, 55286, 55287, 55288, + 55289, 55290, 55291, 63744, 63745, 63746, 63747, 63748, + 63749, 63750, 63751, 63752, 63753, 63754, 63755, 63756, + 63757, 63758, 63759, 63760, 63761, 63762, 63763, 63764, + 63765, 63766, 63767, 63768, 63769, 63770, 63771, 63772, + 63773, 63774, 63775, 63776, 63777, 63778, 63779, 63780, + 63781, 63782, 63783, 63784, 63785, 63786, 63787, 63788, + 63789, 63790, 63791, 63792, 63793, 63794, 63795, 63796, + 63797, 63798, 63799, 63800, 63801, 63802, 63803, 63804, + 63805, 63806, 63807, 63808, 63809, 63810, 63811, 63812, + 63813, 63814, 63815, 63816, 63817, 63818, 63819, 63820, + 63821, 63822, 63823, 63824, 63825, 63826, 63827, 63828, + 63829, 63830, 63831, 63832, 63833, 63834, 63835, 63836, + 63837, 63838, 63839, 63840, 63841, 63842, 63843, 63844, + 63845, 63846, 63847, 63848, 63849, 63850, 63851, 63852, + 63853, 63854, 63855, 63856, 63857, 63858, 63859, 63860, + 63861, 63862, 63863, 63864, 63865, 63866, 63867, 63868, + 63869, 63870, 63871, 63872, 63873, 63874, 63875, 63876, + 63877, 63878, 63879, 63880, 63881, 63882, 63883, 63884, + 63885, 63886, 63887, 63888, 63889, 63890, 63891, 63892, + 63893, 63894, 63895, 63896, 63897, 63898, 63899, 63900, + 63901, 63902, 63903, 63904, 63905, 63906, 63907, 63908, + 63909, 63910, 63911, 63912, 63913, 63914, 63915, 63916, + 63917, 63918, 63919, 63920, 63921, 63922, 63923, 63924, + 63925, 63926, 63927, 63928, 63929, 63930, 63931, 63932, + 63933, 63934, 63935, 63936, 63937, 63938, 63939, 63940, + 63941, 63942, 63943, 63944, 63945, 63946, 63947, 63948, + 63949, 63950, 63951, 63952, 63953, 63954, 63955, 63956, + 63957, 63958, 63959, 63960, 63961, 63962, 63963, 63964, + 63965, 63966, 63967, 63968, 63969, 63970, 63971, 63972, + 63973, 63974, 63975, 63976, 63977, 63978, 63979, 63980, + 63981, 63982, 63983, 63984, 63985, 63986, 63987, 63988, + 63989, 63990, 63991, 63992, 63993, 63994, 63995, 63996, + 63997, 63998, 63999, 64000, 64001, 64002, 64003, 64004, + 64005, 64006, 64007, 64008, 64009, 64010, 64011, 64012, + 64013, 64014, 64015, 64016, 64017, 64018, 64019, 64020, + 64021, 64022, 64023, 64024, 64025, 64026, 64027, 64028, + 64029, 64030, 64031, 64032, 64033, 64034, 64035, 64036, + 64037, 64038, 64039, 64040, 64041, 64042, 64043, 64044, + 64045, 64046, 64047, 64048, 64049, 64050, 64051, 64052, + 64053, 64054, 64055, 64056, 64057, 64058, 64059, 64060, + 64061, 64062, 64063, 64064, 64065, 64066, 64067, 64068, + 64069, 64070, 64071, 64072, 64073, 64074, 64075, 64076, + 64077, 64078, 64079, 64080, 64081, 64082, 64083, 64084, + 64085, 64086, 64087, 64088, 64089, 64090, 64091, 64092, + 64093, 64094, 64095, 64096, 64097, 64098, 64099, 64100, + 64101, 64102, 64103, 64104, 64105, 64106, 64107, 64108, + 64109, 64112, 64113, 64114, 64115, 64116, 64117, 64118, + 64119, 64120, 64121, 64122, 64123, 64124, 64125, 64126, + 64127, 64128, 64129, 64130, 64131, 64132, 64133, 64134, + 64135, 64136, 64137, 64138, 64139, 64140, 64141, 64142, + 64143, 64144, 64145, 64146, 64147, 64148, 64149, 64150, + 64151, 64152, 64153, 64154, 64155, 64156, 64157, 64158, + 64159, 64160, 64161, 64162, 64163, 64164, 64165, 64166, + 64167, 64168, 64169, 64170, 64171, 64172, 64173, 64174, + 64175, 64176, 64177, 64178, 64179, 64180, 64181, 64182, + 64183, 64184, 64185, 64186, 64187, 64188, 64189, 64190, + 64191, 64192, 64193, 64194, 64195, 64196, 64197, 64198, + 64199, 64200, 64201, 64202, 64203, 64204, 64205, 64206, + 64207, 64208, 64209, 64210, 64211, 64212, 64213, 64214, + 64215, 64216, 64217, 64256, 64257, 64258, 64259, 64260, + 64261, 64262, 64275, 64276, 64277, 64278, 64279, 64285, + 64287, 64288, 64289, 64290, 64291, 64292, 64293, 64294, + 64295, 64296, 64298, 64299, 64300, 64301, 64302, 64303, + 64304, 64305, 64306, 64307, 64308, 64309, 64310, 64312, + 64313, 64314, 64315, 64316, 64318, 64320, 64321, 64323, + 64324, 64326, 64327, 64328, 64329, 64330, 64331, 64332, + 64333, 64334, 64335, 64336, 64337, 64338, 64339, 64340, + 64341, 64342, 64343, 64344, 64345, 64346, 64347, 64348, + 64349, 64350, 64351, 64352, 64353, 64354, 64355, 64356, + 64357, 64358, 64359, 64360, 64361, 64362, 64363, 64364, + 64365, 64366, 64367, 64368, 64369, 64370, 64371, 64372, + 64373, 64374, 64375, 64376, 64377, 64378, 64379, 64380, + 64381, 64382, 64383, 64384, 64385, 64386, 64387, 64388, + 64389, 64390, 64391, 64392, 64393, 64394, 64395, 64396, + 64397, 64398, 64399, 64400, 64401, 64402, 64403, 64404, + 64405, 64406, 64407, 64408, 64409, 64410, 64411, 64412, + 64413, 64414, 64415, 64416, 64417, 64418, 64419, 64420, + 64421, 64422, 64423, 64424, 64425, 64426, 64427, 64428, + 64429, 64430, 64431, 64432, 64433, 64467, 64468, 64469, + 64470, 64471, 64472, 64473, 64474, 64475, 64476, 64477, + 64478, 64479, 64480, 64481, 64482, 64483, 64484, 64485, + 64486, 64487, 64488, 64489, 64490, 64491, 64492, 64493, + 64494, 64495, 64496, 64497, 64498, 64499, 64500, 64501, + 64502, 64503, 64504, 64505, 64506, 64507, 64508, 64509, + 64510, 64511, 64512, 64513, 64514, 64515, 64516, 64517, + 64518, 64519, 64520, 64521, 64522, 64523, 64524, 64525, + 64526, 64527, 64528, 64529, 64530, 64531, 64532, 64533, + 64534, 64535, 64536, 64537, 64538, 64539, 64540, 64541, + 64542, 64543, 64544, 64545, 64546, 64547, 64548, 64549, + 64550, 64551, 64552, 64553, 64554, 64555, 64556, 64557, + 64558, 64559, 64560, 64561, 64562, 64563, 64564, 64565, + 64566, 64567, 64568, 64569, 64570, 64571, 64572, 64573, + 64574, 64575, 64576, 64577, 64578, 64579, 64580, 64581, + 64582, 64583, 64584, 64585, 64586, 64587, 64588, 64589, + 64590, 64591, 64592, 64593, 64594, 64595, 64596, 64597, + 64598, 64599, 64600, 64601, 64602, 64603, 64604, 64605, + 64606, 64607, 64608, 64609, 64610, 64611, 64612, 64613, + 64614, 64615, 64616, 64617, 64618, 64619, 64620, 64621, + 64622, 64623, 64624, 64625, 64626, 64627, 64628, 64629, + 64630, 64631, 64632, 64633, 64634, 64635, 64636, 64637, + 64638, 64639, 64640, 64641, 64642, 64643, 64644, 64645, + 64646, 64647, 64648, 64649, 64650, 64651, 64652, 64653, + 64654, 64655, 64656, 64657, 64658, 64659, 64660, 64661, + 64662, 64663, 64664, 64665, 64666, 64667, 64668, 64669, + 64670, 64671, 64672, 64673, 64674, 64675, 64676, 64677, + 64678, 64679, 64680, 64681, 64682, 64683, 64684, 64685, + 64686, 64687, 64688, 64689, 64690, 64691, 64692, 64693, + 64694, 64695, 64696, 64697, 64698, 64699, 64700, 64701, + 64702, 64703, 64704, 64705, 64706, 64707, 64708, 64709, + 64710, 64711, 64712, 64713, 64714, 64715, 64716, 64717, + 64718, 64719, 64720, 64721, 64722, 64723, 64724, 64725, + 64726, 64727, 64728, 64729, 64730, 64731, 64732, 64733, + 64734, 64735, 64736, 64737, 64738, 64739, 64740, 64741, + 64742, 64743, 64744, 64745, 64746, 64747, 64748, 64749, + 64750, 64751, 64752, 64753, 64754, 64755, 64756, 64757, + 64758, 64759, 64760, 64761, 64762, 64763, 64764, 64765, + 64766, 64767, 64768, 64769, 64770, 64771, 64772, 64773, + 64774, 64775, 64776, 64777, 64778, 64779, 64780, 64781, + 64782, 64783, 64784, 64785, 64786, 64787, 64788, 64789, + 64790, 64791, 64792, 64793, 64794, 64795, 64796, 64797, + 64798, 64799, 64800, 64801, 64802, 64803, 64804, 64805, + 64806, 64807, 64808, 64809, 64810, 64811, 64812, 64813, + 64814, 64815, 64816, 64817, 64818, 64819, 64820, 64821, + 64822, 64823, 64824, 64825, 64826, 64827, 64828, 64829, + 64848, 64849, 64850, 64851, 64852, 64853, 64854, 64855, + 64856, 64857, 64858, 64859, 64860, 64861, 64862, 64863, + 64864, 64865, 64866, 64867, 64868, 64869, 64870, 64871, + 64872, 64873, 64874, 64875, 64876, 64877, 64878, 64879, + 64880, 64881, 64882, 64883, 64884, 64885, 64886, 64887, + 64888, 64889, 64890, 64891, 64892, 64893, 64894, 64895, + 64896, 64897, 64898, 64899, 64900, 64901, 64902, 64903, + 64904, 64905, 64906, 64907, 64908, 64909, 64910, 64911, + 64914, 64915, 64916, 64917, 64918, 64919, 64920, 64921, + 64922, 64923, 64924, 64925, 64926, 64927, 64928, 64929, + 64930, 64931, 64932, 64933, 64934, 64935, 64936, 64937, + 64938, 64939, 64940, 64941, 64942, 64943, 64944, 64945, + 64946, 64947, 64948, 64949, 64950, 64951, 64952, 64953, + 64954, 64955, 64956, 64957, 64958, 64959, 64960, 64961, + 64962, 64963, 64964, 64965, 64966, 64967, 65008, 65009, + 65010, 65011, 65012, 65013, 65014, 65015, 65016, 65017, + 65018, 65019, 65136, 65137, 65138, 65139, 65140, 65142, + 65143, 65144, 65145, 65146, 65147, 65148, 65149, 65150, + 65151, 65152, 65153, 65154, 65155, 65156, 65157, 65158, + 65159, 65160, 65161, 65162, 65163, 65164, 65165, 65166, + 65167, 65168, 65169, 65170, 65171, 65172, 65173, 65174, + 65175, 65176, 65177, 65178, 65179, 65180, 65181, 65182, + 65183, 65184, 65185, 65186, 65187, 65188, 65189, 65190, + 65191, 65192, 65193, 65194, 65195, 65196, 65197, 65198, + 65199, 65200, 65201, 65202, 65203, 65204, 65205, 65206, + 65207, 65208, 65209, 65210, 65211, 65212, 65213, 65214, + 65215, 65216, 65217, 65218, 65219, 65220, 65221, 65222, + 65223, 65224, 65225, 65226, 65227, 65228, 65229, 65230, + 65231, 65232, 65233, 65234, 65235, 65236, 65237, 65238, + 65239, 65240, 65241, 65242, 65243, 65244, 65245, 65246, + 65247, 65248, 65249, 65250, 65251, 65252, 65253, 65254, + 65255, 65256, 65257, 65258, 65259, 65260, 65261, 65262, + 65263, 65264, 65265, 65266, 65267, 65268, 65269, 65270, + 65271, 65272, 65273, 65274, 65275, 65276, 65313, 65314, + 65315, 65316, 65317, 65318, 65319, 65320, 65321, 65322, + 65323, 65324, 65325, 65326, 65327, 65328, 65329, 65330, + 65331, 65332, 65333, 65334, 65335, 65336, 65337, 65338, + 65345, 65346, 65347, 65348, 65349, 65350, 65351, 65352, + 65353, 65354, 65355, 65356, 65357, 65358, 65359, 65360, + 65361, 65362, 65363, 65364, 65365, 65366, 65367, 65368, + 65369, 65370, 65382, 65383, 65384, 65385, 65386, 65387, + 65388, 65389, 65390, 65391, 65392, 65393, 65394, 65395, + 65396, 65397, 65398, 65399, 65400, 65401, 65402, 65403, + 65404, 65405, 65406, 65407, 65408, 65409, 65410, 65411, + 65412, 65413, 65414, 65415, 65416, 65417, 65418, 65419, + 65420, 65421, 65422, 65423, 65424, 65425, 65426, 65427, + 65428, 65429, 65430, 65431, 65432, 65433, 65434, 65435, + 65436, 65437, 65438, 65439, 65440, 65441, 65442, 65443, + 65444, 65445, 65446, 65447, 65448, 65449, 65450, 65451, + 65452, 65453, 65454, 65455, 65456, 65457, 65458, 65459, + 65460, 65461, 65462, 65463, 65464, 65465, 65466, 65467, + 65468, 65469, 65470, 65474, 65475, 65476, 65477, 65478, + 65479, 65482, 65483, 65484, 65485, 65486, 65487, 65490, + 65491, 65492, 65493, 65494, 65495, 65498, 65499, 65500, + ]; + }, + {}, + ], + 4: [ + function (require, module, exports) { + // http://wiki.commonjs.org/wiki/Unit_Testing/1.0 + // + // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8! + // + // Originally from narwhal.js (http://narwhaljs.org) + // Copyright (c) 2009 Thomas Robinson <280north.com> + // + // 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. + // + // 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 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. + + // when used in node, this will actually load the util module we depend on + // versus loading the builtin util module as happens otherwise + // this is a bug in node module loading as far as I am concerned + var util = require("util/"); + + var pSlice = Array.prototype.slice; + var hasOwn = Object.prototype.hasOwnProperty; + + // 1. The assert module provides functions that throw + // AssertionError's when particular conditions are not met. The + // assert module must conform to the following interface. + + var assert = (module.exports = ok); + + // 2. The AssertionError is defined in assert. + // new assert.AssertionError({ message: message, + // actual: actual, + // expected: expected }) + + assert.AssertionError = function AssertionError(options) { + this.name = "AssertionError"; + this.actual = options.actual; + this.expected = options.expected; + this.operator = options.operator; + if (options.message) { + this.message = options.message; + this.generatedMessage = false; + } else { + this.message = getMessage(this); + this.generatedMessage = true; + } + var stackStartFunction = + options.stackStartFunction || fail; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, stackStartFunction); + } else { + // non v8 browsers so we can have a stacktrace + var err = new Error(); + if (err.stack) { + var out = err.stack; + + // try to strip useless frames + var fn_name = stackStartFunction.name; + var idx = out.indexOf("\n" + fn_name); + if (idx >= 0) { + // once we have located the function frame + // we need to strip out everything before it (and its line) + var next_line = out.indexOf("\n", idx + 1); + out = out.substring(next_line + 1); + } + + this.stack = out; + } + } + }; + + // assert.AssertionError instanceof Error + util.inherits(assert.AssertionError, Error); + + function replacer(key, value) { + if (util.isUndefined(value)) { + return "" + value; + } + if ( + util.isNumber(value) && + (isNaN(value) || !isFinite(value)) + ) { + return value.toString(); + } + if (util.isFunction(value) || util.isRegExp(value)) { + return value.toString(); + } + return value; + } + + function truncate(s, n) { + if (util.isString(s)) { + return s.length < n ? s : s.slice(0, n); + } else { + return s; + } + } + + function getMessage(self) { + return ( + truncate( + JSON.stringify(self.actual, replacer), + 128, + ) + + " " + + self.operator + + " " + + truncate( + JSON.stringify(self.expected, replacer), + 128, + ) + ); + } + + // At present only the three keys mentioned above are used and + // understood by the spec. Implementations or sub modules can pass + // other keys to the AssertionError's constructor - they will be + // ignored. + + // 3. All of the following functions must throw an AssertionError + // when a corresponding condition is not met, with a message that + // may be undefined if not provided. All assertion methods provide + // both the actual and expected values to the assertion error for + // display purposes. + + function fail( + actual, + expected, + message, + operator, + stackStartFunction, + ) { + throw new assert.AssertionError({ + message: message, + actual: actual, + expected: expected, + operator: operator, + stackStartFunction: stackStartFunction, + }); + } + + // EXTENSION! allows for well behaved errors defined elsewhere. + assert.fail = fail; + + // 4. Pure assertion tests whether a value is truthy, as determined + // by !!guard. + // assert.ok(guard, message_opt); + // This statement is equivalent to assert.equal(true, !!guard, + // message_opt);. To test strictly for the value true, use + // assert.strictEqual(true, guard, message_opt);. + + function ok(value, message) { + if (!value) fail(value, true, message, "==", assert.ok); + } + assert.ok = ok; + + // 5. The equality assertion tests shallow, coercive equality with + // ==. + // assert.equal(actual, expected, message_opt); + + assert.equal = function equal(actual, expected, message) { + if (actual != expected) + fail(actual, expected, message, "==", assert.equal); + }; + + // 6. The non-equality assertion tests for whether two objects are not equal + // with != assert.notEqual(actual, expected, message_opt); + + assert.notEqual = function notEqual( + actual, + expected, + message, + ) { + if (actual == expected) { + fail( + actual, + expected, + message, + "!=", + assert.notEqual, + ); + } + }; + + // 7. The equivalence assertion tests a deep equality relation. + // assert.deepEqual(actual, expected, message_opt); + + assert.deepEqual = function deepEqual( + actual, + expected, + message, + ) { + if (!_deepEqual(actual, expected)) { + fail( + actual, + expected, + message, + "deepEqual", + assert.deepEqual, + ); + } + }; + + function _deepEqual(actual, expected) { + // 7.1. All identical values are equivalent, as determined by ===. + if (actual === expected) { + return true; + } else if ( + util.isBuffer(actual) && + util.isBuffer(expected) + ) { + if (actual.length != expected.length) return false; + + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) return false; + } + + return true; + + // 7.2. If the expected value is a Date object, the actual value is + // equivalent if it is also a Date object that refers to the same time. + } else if ( + util.isDate(actual) && + util.isDate(expected) + ) { + return actual.getTime() === expected.getTime(); + + // 7.3 If the expected value is a RegExp object, the actual value is + // equivalent if it is also a RegExp object with the same source and + // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`). + } else if ( + util.isRegExp(actual) && + util.isRegExp(expected) + ) { + return ( + actual.source === expected.source && + actual.global === expected.global && + actual.multiline === expected.multiline && + actual.lastIndex === expected.lastIndex && + actual.ignoreCase === expected.ignoreCase + ); + + // 7.4. Other pairs that do not both pass typeof value == 'object', + // equivalence is determined by ==. + } else if ( + !util.isObject(actual) && + !util.isObject(expected) + ) { + return actual == expected; + + // 7.5 For all other Object pairs, including Array objects, equivalence is + // determined by having the same number of owned properties (as verified + // with Object.prototype.hasOwnProperty.call), the same set of keys + // (although not necessarily the same order), equivalent values for every + // corresponding key, and an identical 'prototype' property. Note: this + // accounts for both named and indexed properties on Arrays. + } else { + return objEquiv(actual, expected); + } + } + + function isArguments(object) { + return ( + Object.prototype.toString.call(object) == + "[object Arguments]" + ); + } + + function objEquiv(a, b) { + if ( + util.isNullOrUndefined(a) || + util.isNullOrUndefined(b) + ) + return false; + // an identical 'prototype' property. + if (a.prototype !== b.prototype) return false; + //~~~I've managed to break Object.keys through screwy arguments passing. + // Converting to array solves the problem. + if (isArguments(a)) { + if (!isArguments(b)) { + return false; + } + a = pSlice.call(a); + b = pSlice.call(b); + return _deepEqual(a, b); + } + try { + var ka = objectKeys(a), + kb = objectKeys(b), + key, + i; + } catch (e) { + //happens when one is a string literal and the other isn't + return false; + } + // having the same number of owned properties (keys incorporates + // hasOwnProperty) + if (ka.length != kb.length) return false; + //the same set of keys (although not necessarily the same order), + ka.sort(); + kb.sort(); + //~~~cheap key test + for (i = ka.length - 1; i >= 0; i--) { + if (ka[i] != kb[i]) return false; + } + //equivalent values for every corresponding key, and + //~~~possibly expensive deep test + for (i = ka.length - 1; i >= 0; i--) { + key = ka[i]; + if (!_deepEqual(a[key], b[key])) return false; + } + return true; + } + + // 8. The non-equivalence assertion tests for any deep inequality. + // assert.notDeepEqual(actual, expected, message_opt); + + assert.notDeepEqual = function notDeepEqual( + actual, + expected, + message, + ) { + if (_deepEqual(actual, expected)) { + fail( + actual, + expected, + message, + "notDeepEqual", + assert.notDeepEqual, + ); + } + }; + + // 9. The strict equality assertion tests strict equality, as determined by ===. + // assert.strictEqual(actual, expected, message_opt); + + assert.strictEqual = function strictEqual( + actual, + expected, + message, + ) { + if (actual !== expected) { + fail( + actual, + expected, + message, + "===", + assert.strictEqual, + ); + } + }; + + // 10. The strict non-equality assertion tests for strict inequality, as + // determined by !==. assert.notStrictEqual(actual, expected, message_opt); + + assert.notStrictEqual = function notStrictEqual( + actual, + expected, + message, + ) { + if (actual === expected) { + fail( + actual, + expected, + message, + "!==", + assert.notStrictEqual, + ); + } + }; + + function expectedException(actual, expected) { + if (!actual || !expected) { + return false; + } + + if ( + Object.prototype.toString.call(expected) == + "[object RegExp]" + ) { + return expected.test(actual); + } else if (actual instanceof expected) { + return true; + } else if (expected.call({}, actual) === true) { + return true; + } + + return false; + } + + function _throws(shouldThrow, block, expected, message) { + var actual; + + if (util.isString(expected)) { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + message = + (expected && expected.name + ? " (" + expected.name + ")." + : ".") + (message ? " " + message : "."); + + if (shouldThrow && !actual) { + fail( + actual, + expected, + "Missing expected exception" + message, + ); + } + + if ( + !shouldThrow && + expectedException(actual, expected) + ) { + fail( + actual, + expected, + "Got unwanted exception" + message, + ); + } + + if ( + (shouldThrow && + actual && + expected && + !expectedException(actual, expected)) || + (!shouldThrow && actual) + ) { + throw actual; + } + } + + // 11. Expected to throw an error: + // assert.throws(block, Error_opt, message_opt); + + assert.throws = function ( + block, + /*optional*/ error, + /*optional*/ message, + ) { + _throws.apply( + this, + [true].concat(pSlice.call(arguments)), + ); + }; + + // EXTENSION! This is annoying to write outside this module. + assert.doesNotThrow = function ( + block, + /*optional*/ message, + ) { + _throws.apply( + this, + [false].concat(pSlice.call(arguments)), + ); + }; + + assert.ifError = function (err) { + if (err) { + throw err; + } + }; + + var objectKeys = + Object.keys || + function (obj) { + var keys = []; + for (var key in obj) { + if ( + Object.prototype.hasOwnProperty.call( + obj, + key, + ) + ) + keys.push(key); + } + return keys; + }; + }, + { "util/": 9 }, + ], + 5: [ + function (require, module, exports) { + // Copyright Joyent, Inc. and other Node contributors. + // + // 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. + // + // 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. + + function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; + } + module.exports = EventEmitter; + + // Backwards-compat with node 0.10.x + EventEmitter.EventEmitter = EventEmitter; + + EventEmitter.prototype._events = undefined; + EventEmitter.prototype._maxListeners = undefined; + + // By default EventEmitters will print a warning if more than 10 listeners are + // added to it. This is a useful default which helps finding memory leaks. + EventEmitter.defaultMaxListeners = 10; + + // Obviously not all Emitters should be limited to 10. This function allows + // that to be increased. Set to zero for unlimited. + EventEmitter.prototype.setMaxListeners = function (n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError("n must be a positive number"); + this._maxListeners = n; + return this; + }; + + EventEmitter.prototype.emit = function (type) { + var er, handler, len, args, i, listeners; + + if (!this._events) this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === "error") { + if ( + !this._events.error || + (isObject(this._events.error) && + !this._events.error.length) + ) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + throw TypeError( + 'Uncaught, unspecified "error" event.', + ); + } + return false; + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call( + this, + arguments[1], + arguments[2], + ); + break; + // slower + default: + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + handler.apply(this, args); + } + } else if (isObject(handler)) { + len = arguments.length; + args = new Array(len - 1); + for (i = 1; i < len; i++) + args[i - 1] = arguments[i]; + + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; + }; + + EventEmitter.prototype.addListener = function ( + type, + listener, + ) { + var m; + + if (!isFunction(listener)) + throw TypeError("listener must be a function"); + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit( + "newListener", + type, + isFunction(listener.listener) + ? listener.listener + : listener, + ); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + // Adding the second element, need to change to array. + else + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if ( + isObject(this._events[type]) && + !this._events[type].warned + ) { + var m; + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error( + "(node) warning: possible EventEmitter memory " + + "leak detected. %d listeners added. " + + "Use emitter.setMaxListeners() to increase limit.", + this._events[type].length, + ); + console.trace(); + } + } + + return this; + }; + + EventEmitter.prototype.on = + EventEmitter.prototype.addListener; + + EventEmitter.prototype.once = function (type, listener) { + if (!isFunction(listener)) + throw TypeError("listener must be a function"); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; + }; + + // emits a 'removeListener' event iff the listener was removed + EventEmitter.prototype.removeListener = function ( + type, + listener, + ) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError("listener must be a function"); + + if (!this._events || !this._events[type]) return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if ( + list === listener || + (isFunction(list.listener) && + list.listener === listener) + ) { + delete this._events[type]; + if (this._events.removeListener) + this.emit("removeListener", type, listener); + } else if (isObject(list)) { + for (i = length; i-- > 0; ) { + if ( + list[i] === listener || + (list[i].listener && + list[i].listener === listener) + ) { + position = i; + break; + } + } + + if (position < 0) return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit("removeListener", type, listener); + } + + return this; + }; + + EventEmitter.prototype.removeAllListeners = function ( + type, + ) { + var key, listeners; + + if (!this._events) return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === "removeListener") continue; + this.removeAllListeners(key); + } + this.removeAllListeners("removeListener"); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else { + // LIFO order + while (listeners.length) + this.removeListener( + type, + listeners[listeners.length - 1], + ); + } + delete this._events[type]; + + return this; + }; + + EventEmitter.prototype.listeners = function (type) { + var ret; + if (!this._events || !this._events[type]) ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else ret = this._events[type].slice(); + return ret; + }; + + EventEmitter.listenerCount = function (emitter, type) { + var ret; + if (!emitter._events || !emitter._events[type]) ret = 0; + else if (isFunction(emitter._events[type])) ret = 1; + else ret = emitter._events[type].length; + return ret; + }; + + function isFunction(arg) { + return typeof arg === "function"; + } + + function isNumber(arg) { + return typeof arg === "number"; + } + + function isObject(arg) { + return typeof arg === "object" && arg !== null; + } + + function isUndefined(arg) { + return arg === void 0; + } + }, + {}, + ], + 6: [ + function (require, module, exports) { + if (typeof Object.create === "function") { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create( + superCtor.prototype, + { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true, + }, + }, + ); + }; + } else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor; + var TempCtor = function () {}; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + }; + } + }, + {}, + ], + 7: [ + function (require, module, exports) { + // shim for using process in browser + + var process = (module.exports = {}); + + process.nextTick = (function () { + var canSetImmediate = + typeof window !== "undefined" && + window.setImmediate; + var canPost = + typeof window !== "undefined" && + window.postMessage && + window.addEventListener; + if (canSetImmediate) { + return function (f) { + return window.setImmediate(f); + }; + } + + if (canPost) { + var queue = []; + window.addEventListener( + "message", + function (ev) { + var source = ev.source; + if ( + (source === window || + source === null) && + ev.data === "process-tick" + ) { + ev.stopPropagation(); + if (queue.length > 0) { + var fn = queue.shift(); + fn(); + } + } + }, + true, + ); + + return function nextTick(fn) { + queue.push(fn); + window.postMessage("process-tick", "*"); + }; + } + + return function nextTick(fn) { + setTimeout(fn, 0); + }; + })(); + + process.title = "browser"; + process.browser = true; + process.env = {}; + process.argv = []; + + process.binding = function (name) { + throw new Error("process.binding is not supported"); + }; + + // TODO(shtylman) + process.cwd = function () { + return "/"; + }; + process.chdir = function (dir) { + throw new Error("process.chdir is not supported"); + }; + }, + {}, + ], + 8: [ + function (require, module, exports) { + module.exports = function isBuffer(arg) { + return ( + arg && + typeof arg === "object" && + typeof arg.copy === "function" && + typeof arg.fill === "function" && + typeof arg.readUInt8 === "function" + ); + }; + }, + {}, + ], + 9: [ + function (require, module, exports) { + var process = require("__browserify_process"), + global = + typeof self !== "undefined" + ? self + : typeof window !== "undefined" + ? window + : {}; // Copyright Joyent, Inc. and other Node contributors. + // + // 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. + // + // 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. + + var formatRegExp = /%[sdj%]/g; + exports.format = function (f) { + if (!isString(f)) { + var objects = []; + for (var i = 0; i < arguments.length; i++) { + objects.push(inspect(arguments[i])); + } + return objects.join(" "); + } + + var i = 1; + var args = arguments; + var len = args.length; + var str = String(f).replace(formatRegExp, function (x) { + if (x === "%%") return "%"; + if (i >= len) return x; + switch (x) { + case "%s": + return String(args[i++]); + case "%d": + return Number(args[i++]); + case "%j": + try { + return JSON.stringify(args[i++]); + } catch (_) { + return "[Circular]"; + } + default: + return x; + } + }); + for (var x = args[i]; i < len; x = args[++i]) { + if (isNull(x) || !isObject(x)) { + str += " " + x; + } else { + str += " " + inspect(x); + } + } + return str; + }; + + // Mark that a method should not be used. + // Returns a modified function which warns once by default. + // If --no-deprecation is set, then it is a no-op. + exports.deprecate = function (fn, msg) { + // Allow for deprecating things in the process of starting up. + if (isUndefined(global.process)) { + return function () { + return exports + .deprecate(fn, msg) + .apply(this, arguments); + }; + } + + if (process.noDeprecation === true) { + return fn; + } + + var warned = false; + function deprecated() { + if (!warned) { + if (process.throwDeprecation) { + throw new Error(msg); + } else if (process.traceDeprecation) { + console.trace(msg); + } else { + console.error(msg); + } + warned = true; + } + return fn.apply(this, arguments); + } + + return deprecated; + }; + + var debugs = {}; + var debugEnviron; + exports.debuglog = function (set) { + if (isUndefined(debugEnviron)) + debugEnviron = process.env.NODE_DEBUG || ""; + set = set.toUpperCase(); + if (!debugs[set]) { + if ( + new RegExp("\\b" + set + "\\b", "i").test( + debugEnviron, + ) + ) { + var pid = process.pid; + debugs[set] = function () { + var msg = exports.format.apply( + exports, + arguments, + ); + console.error("%s %d: %s", set, pid, msg); + }; + } else { + debugs[set] = function () {}; + } + } + return debugs[set]; + }; + + /** + * Echos the value of a value. Tries to print the value out + * in the best way possible given the different types. + * + * @param {Object} obj The object to print out. + * @param {Object} opts Optional options object that alters the output. + */ + /* legacy: obj, showHidden, depth, colors*/ + function inspect(obj, opts) { + // default options + var ctx = { + seen: [], + stylize: stylizeNoColor, + }; + // legacy... + if (arguments.length >= 3) ctx.depth = arguments[2]; + if (arguments.length >= 4) ctx.colors = arguments[3]; + if (isBoolean(opts)) { + // legacy... + ctx.showHidden = opts; + } else if (opts) { + // got an "options" object + exports._extend(ctx, opts); + } + // set default options + if (isUndefined(ctx.showHidden)) ctx.showHidden = false; + if (isUndefined(ctx.depth)) ctx.depth = 2; + if (isUndefined(ctx.colors)) ctx.colors = false; + if (isUndefined(ctx.customInspect)) + ctx.customInspect = true; + if (ctx.colors) ctx.stylize = stylizeWithColor; + return formatValue(ctx, obj, ctx.depth); + } + exports.inspect = inspect; + + // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + inspect.colors = { + bold: [1, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + white: [37, 39], + grey: [90, 39], + black: [30, 39], + blue: [34, 39], + cyan: [36, 39], + green: [32, 39], + magenta: [35, 39], + red: [31, 39], + yellow: [33, 39], + }; + + // Don't use 'blue' not visible on cmd.exe + inspect.styles = { + special: "cyan", + number: "yellow", + boolean: "yellow", + undefined: "grey", + null: "bold", + string: "green", + date: "magenta", + // "name": intentionally not styling + regexp: "red", + }; + + function stylizeWithColor(str, styleType) { + var style = inspect.styles[styleType]; + + if (style) { + return ( + "\u001b[" + + inspect.colors[style][0] + + "m" + + str + + "\u001b[" + + inspect.colors[style][1] + + "m" + ); + } else { + return str; + } + } + + function stylizeNoColor(str, styleType) { + return str; + } + + function arrayToHash(array) { + var hash = {}; + + array.forEach(function (val, idx) { + hash[val] = true; + }); + + return hash; + } + + function formatValue(ctx, value, recurseTimes) { + // Provide a hook for user-specified inspect functions. + // Check that value is an object with an inspect function on it + if ( + ctx.customInspect && + value && + isFunction(value.inspect) && + // Filter out the util module, it's inspect function is special + value.inspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !( + value.constructor && + value.constructor.prototype === value + ) + ) { + var ret = value.inspect(recurseTimes, ctx); + if (!isString(ret)) { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; + } + + // Primitive types cannot have properties + var primitive = formatPrimitive(ctx, value); + if (primitive) { + return primitive; + } + + // Look up the keys of the object. + var keys = Object.keys(value); + var visibleKeys = arrayToHash(keys); + + if (ctx.showHidden) { + keys = Object.getOwnPropertyNames(value); + } + + // IE doesn't make error fields non-enumerable + // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx + if ( + isError(value) && + (keys.indexOf("message") >= 0 || + keys.indexOf("description") >= 0) + ) { + return formatError(value); + } + + // Some type of object without properties can be shortcutted. + if (keys.length === 0) { + if (isFunction(value)) { + var name = value.name ? ": " + value.name : ""; + return ctx.stylize( + "[Function" + name + "]", + "special", + ); + } + if (isRegExp(value)) { + return ctx.stylize( + RegExp.prototype.toString.call(value), + "regexp", + ); + } + if (isDate(value)) { + return ctx.stylize( + Date.prototype.toString.call(value), + "date", + ); + } + if (isError(value)) { + return formatError(value); + } + } + + var base = "", + array = false, + braces = ["{", "}"]; + + // Make Array say that they are Array + if (isArray(value)) { + array = true; + braces = ["[", "]"]; + } + + // Make functions say that they are functions + if (isFunction(value)) { + var n = value.name ? ": " + value.name : ""; + base = " [Function" + n + "]"; + } + + // Make RegExps say that they are RegExps + if (isRegExp(value)) { + base = " " + RegExp.prototype.toString.call(value); + } + + // Make dates with properties first say the date + if (isDate(value)) { + base = " " + Date.prototype.toUTCString.call(value); + } + + // Make error with message first say the error + if (isError(value)) { + base = " " + formatError(value); + } + + if ( + keys.length === 0 && + (!array || value.length == 0) + ) { + return braces[0] + base + braces[1]; + } + + if (recurseTimes < 0) { + if (isRegExp(value)) { + return ctx.stylize( + RegExp.prototype.toString.call(value), + "regexp", + ); + } else { + return ctx.stylize("[Object]", "special"); + } + } + + ctx.seen.push(value); + + var output; + if (array) { + output = formatArray( + ctx, + value, + recurseTimes, + visibleKeys, + keys, + ); + } else { + output = keys.map(function (key) { + return formatProperty( + ctx, + value, + recurseTimes, + visibleKeys, + key, + array, + ); + }); + } + + ctx.seen.pop(); + + return reduceToSingleString(output, base, braces); + } + + function formatPrimitive(ctx, value) { + if (isUndefined(value)) + return ctx.stylize("undefined", "undefined"); + if (isString(value)) { + var simple = + "'" + + JSON.stringify(value) + .replace(/^"|"$/g, "") + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + + "'"; + return ctx.stylize(simple, "string"); + } + if (isNumber(value)) + return ctx.stylize("" + value, "number"); + if (isBoolean(value)) + return ctx.stylize("" + value, "boolean"); + // For some reason typeof null is "object", so special case here. + if (isNull(value)) return ctx.stylize("null", "null"); + } + + function formatError(value) { + return "[" + Error.prototype.toString.call(value) + "]"; + } + + function formatArray( + ctx, + value, + recurseTimes, + visibleKeys, + keys, + ) { + var output = []; + for (var i = 0, l = value.length; i < l; ++i) { + if (hasOwnProperty(value, String(i))) { + output.push( + formatProperty( + ctx, + value, + recurseTimes, + visibleKeys, + String(i), + true, + ), + ); + } else { + output.push(""); + } + } + keys.forEach(function (key) { + if (!key.match(/^\d+$/)) { + output.push( + formatProperty( + ctx, + value, + recurseTimes, + visibleKeys, + key, + true, + ), + ); + } + }); + return output; + } + + function formatProperty( + ctx, + value, + recurseTimes, + visibleKeys, + key, + array, + ) { + var name, str, desc; + desc = Object.getOwnPropertyDescriptor(value, key) || { + value: value[key], + }; + if (desc.get) { + if (desc.set) { + str = ctx.stylize("[Getter/Setter]", "special"); + } else { + str = ctx.stylize("[Getter]", "special"); + } + } else { + if (desc.set) { + str = ctx.stylize("[Setter]", "special"); + } + } + if (!hasOwnProperty(visibleKeys, key)) { + name = "[" + key + "]"; + } + if (!str) { + if (ctx.seen.indexOf(desc.value) < 0) { + if (isNull(recurseTimes)) { + str = formatValue(ctx, desc.value, null); + } else { + str = formatValue( + ctx, + desc.value, + recurseTimes - 1, + ); + } + if (str.indexOf("\n") > -1) { + if (array) { + str = str + .split("\n") + .map(function (line) { + return " " + line; + }) + .join("\n") + .substr(2); + } else { + str = + "\n" + + str + .split("\n") + .map(function (line) { + return " " + line; + }) + .join("\n"); + } + } + } else { + str = ctx.stylize("[Circular]", "special"); + } + } + if (isUndefined(name)) { + if (array && key.match(/^\d+$/)) { + return str; + } + name = JSON.stringify("" + key); + if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { + name = name.substr(1, name.length - 2); + name = ctx.stylize(name, "name"); + } else { + name = name + .replace(/'/g, "\\'") + .replace(/\\"/g, '"') + .replace(/(^"|"$)/g, "'"); + name = ctx.stylize(name, "string"); + } + } + + return name + ": " + str; + } + + function reduceToSingleString(output, base, braces) { + var numLinesEst = 0; + var length = output.reduce(function (prev, cur) { + numLinesEst++; + if (cur.indexOf("\n") >= 0) numLinesEst++; + return ( + prev + + cur.replace(/\u001b\[\d\d?m/g, "").length + + 1 + ); + }, 0); + + if (length > 60) { + return ( + braces[0] + + (base === "" ? "" : base + "\n ") + + " " + + output.join(",\n ") + + " " + + braces[1] + ); + } + + return ( + braces[0] + + base + + " " + + output.join(", ") + + " " + + braces[1] + ); + } + + // NOTE: These type checking functions intentionally don't use `instanceof` + // because it is fragile and can be easily faked with `Object.create()`. + function isArray(ar) { + return Array.isArray(ar); + } + exports.isArray = isArray; + + function isBoolean(arg) { + return typeof arg === "boolean"; + } + exports.isBoolean = isBoolean; + + function isNull(arg) { + return arg === null; + } + exports.isNull = isNull; + + function isNullOrUndefined(arg) { + return arg == null; + } + exports.isNullOrUndefined = isNullOrUndefined; + + function isNumber(arg) { + return typeof arg === "number"; + } + exports.isNumber = isNumber; + + function isString(arg) { + return typeof arg === "string"; + } + exports.isString = isString; + + function isSymbol(arg) { + return typeof arg === "symbol"; + } + exports.isSymbol = isSymbol; + + function isUndefined(arg) { + return arg === void 0; + } + exports.isUndefined = isUndefined; + + function isRegExp(re) { + return ( + isObject(re) && + objectToString(re) === "[object RegExp]" + ); + } + exports.isRegExp = isRegExp; + + function isObject(arg) { + return typeof arg === "object" && arg !== null; + } + exports.isObject = isObject; + + function isDate(d) { + return ( + isObject(d) && objectToString(d) === "[object Date]" + ); + } + exports.isDate = isDate; + + function isError(e) { + return ( + isObject(e) && + (objectToString(e) === "[object Error]" || + e instanceof Error) + ); + } + exports.isError = isError; + + function isFunction(arg) { + return typeof arg === "function"; + } + exports.isFunction = isFunction; + + function isPrimitive(arg) { + return ( + arg === null || + typeof arg === "boolean" || + typeof arg === "number" || + typeof arg === "string" || + typeof arg === "symbol" || // ES6 symbol + typeof arg === "undefined" + ); + } + exports.isPrimitive = isPrimitive; + + exports.isBuffer = require("./support/isBuffer"); + + function objectToString(o) { + return Object.prototype.toString.call(o); + } + + function pad(n) { + return n < 10 ? "0" + n.toString(10) : n.toString(10); + } + + var months = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + + // 26 Feb 16:19:34 + function timestamp() { + var d = new Date(); + var time = [ + pad(d.getHours()), + pad(d.getMinutes()), + pad(d.getSeconds()), + ].join(":"); + return [d.getDate(), months[d.getMonth()], time].join( + " ", + ); + } + + // log is just a thin wrapper to console.log that prepends a timestamp + exports.log = function () { + console.log( + "%s - %s", + timestamp(), + exports.format.apply(exports, arguments), + ); + }; + + /** + * Inherit the prototype methods from one constructor into another. + * + * The Function.prototype.inherits from lang.js rewritten as a standalone + * function (not on Function.prototype). NOTE: If this file is to be loaded + * during bootstrapping this function needs to be rewritten using some native + * functions as prototype setup using normal JavaScript does not work as + * expected during bootstrapping (see mirror.js in r114903). + * + * @param {function} ctor Constructor function which needs to inherit the + * prototype. + * @param {function} superCtor Constructor function to inherit prototype from. + */ + exports.inherits = require("inherits"); + + exports._extend = function (origin, add) { + // Don't do anything if add isn't an object + if (!add || !isObject(add)) return origin; + + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; + }; + + function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); + } + }, + { + "./support/isBuffer": 8, + __browserify_process: 7, + inherits: 6, + }, + ], + 10: [ + function (require, module, exports) { + var global = + typeof self !== "undefined" + ? self + : typeof window !== "undefined" + ? window + : {}; /*global window, global*/ + var util = require("util"); + var assert = require("assert"); + + var slice = Array.prototype.slice; + var console; + var times = {}; + + if (typeof global !== "undefined" && global.console) { + console = global.console; + } else if ( + typeof window !== "undefined" && + window.console + ) { + console = window.console; + } else { + console = window.console = {}; + } + + var functions = [ + [log, "log"], + [info, "info"], + [warn, "warn"], + [error, "error"], + [time, "time"], + [timeEnd, "timeEnd"], + [trace, "trace"], + [dir, "dir"], + [assert, "assert"], + ]; + + for (var i = 0; i < functions.length; i++) { + var tuple = functions[i]; + var f = tuple[0]; + var name = tuple[1]; + + if (!console[name]) { + console[name] = f; + } + } + + module.exports = console; + + function log() {} + + function info() { + console.log.apply(console, arguments); + } + + function warn() { + console.log.apply(console, arguments); + } + + function error() { + console.warn.apply(console, arguments); + } + + function time(label) { + times[label] = Date.now(); + } + + function timeEnd(label) { + var time = times[label]; + if (!time) { + throw new Error("No such label: " + label); + } + + var duration = Date.now() - time; + console.log(label + ": " + duration + "ms"); + } + + function trace() { + var err = new Error(); + err.name = "Trace"; + err.message = util.format.apply(null, arguments); + console.error(err.stack); + } + + function dir(object) { + console.log(util.inspect(object) + "\n"); + } + + function assert(expression) { + if (!expression) { + var arr = slice.call(arguments, 1); + assert.ok(false, util.format.apply(null, arr)); + } + } + }, + { assert: 4, util: 9 }, + ], + 11: [ + function (require, module, exports) { + // Underscore.js 1.4.4 + // http://underscorejs.org + // (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. + // Underscore may be freely distributed under the MIT license. + + (function () { + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, + ObjProto = Object.prototype, + FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function (obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== "undefined") { + if ( + typeof module !== "undefined" && + module.exports + ) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = "1.4.4"; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = + (_.each = + _.forEach = + function (obj, iterator, context) { + if (obj == null) return; + if ( + nativeForEach && + obj.forEach === nativeForEach + ) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for ( + var i = 0, l = obj.length; + i < l; + i++ + ) { + if ( + iterator.call( + context, + obj[i], + i, + obj, + ) === breaker + ) + return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if ( + iterator.call( + context, + obj[key], + key, + obj, + ) === breaker + ) + return; + } + } + } + }); + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function (obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) + return obj.map(iterator, context); + each(obj, function (value, index, list) { + results[results.length] = iterator.call( + context, + value, + index, + list, + ); + }); + return results; + }; + + var reduceError = + "Reduce of empty array with no initial value"; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = + _.foldl = + _.inject = + function (obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if ( + nativeReduce && + obj.reduce === nativeReduce + ) { + if (context) + iterator = _.bind( + iterator, + context, + ); + return initial + ? obj.reduce(iterator, memo) + : obj.reduce(iterator); + } + each(obj, function (value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call( + context, + memo, + value, + index, + list, + ); + } + }); + if (!initial) + throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function ( + obj, + iterator, + memo, + context, + ) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if ( + nativeReduceRight && + obj.reduceRight === nativeReduceRight + ) { + if (context) + iterator = _.bind(iterator, context); + return initial + ? obj.reduceRight(iterator, memo) + : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function (value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call( + context, + memo, + obj[index], + index, + list, + ); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function (obj, iterator, context) { + var result; + any(obj, function (value, index, list) { + if ( + iterator.call(context, value, index, list) + ) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function ( + obj, + iterator, + context, + ) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) + return obj.filter(iterator, context); + each(obj, function (value, index, list) { + if (iterator.call(context, value, index, list)) + results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function (obj, iterator, context) { + return _.filter( + obj, + function (value, index, list) { + return !iterator.call( + context, + value, + index, + list, + ); + }, + context, + ); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function (obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) + return obj.every(iterator, context); + each(obj, function (value, index, list) { + if ( + !(result = + result && + iterator.call( + context, + value, + index, + list, + )) + ) + return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = + (_.some = + _.any = + function (obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) + return obj.some(iterator, context); + each(obj, function (value, index, list) { + if ( + result || + (result = iterator.call( + context, + value, + index, + list, + )) + ) + return breaker; + }); + return !!result; + }); + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function (obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) + return obj.indexOf(target) != -1; + return any(obj, function (value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function (obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function (value) { + return (isFunc ? method : value[method]).apply( + value, + args, + ); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function (obj, key) { + return _.map(obj, function (value) { + return value[key]; + }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function (obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? "find" : "filter"]( + obj, + function (value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) + return false; + } + return true; + }, + ); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function (obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function (obj, iterator, context) { + if ( + !iterator && + _.isArray(obj) && + obj[0] === +obj[0] && + obj.length < 65535 + ) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = { + computed: -Infinity, + value: -Infinity, + }; + each(obj, function (value, index, list) { + var computed = iterator + ? iterator.call(context, value, index, list) + : value; + computed >= result.computed && + (result = { + value: value, + computed: computed, + }); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function (obj, iterator, context) { + if ( + !iterator && + _.isArray(obj) && + obj[0] === +obj[0] && + obj.length < 65535 + ) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = { + computed: Infinity, + value: Infinity, + }; + each(obj, function (value, index, list) { + var computed = iterator + ? iterator.call(context, value, index, list) + : value; + computed < result.computed && + (result = { + value: value, + computed: computed, + }); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function (obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function (value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function (value) { + return _.isFunction(value) + ? value + : function (obj) { + return obj[value]; + }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function (obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck( + _.map(obj, function (value, index, list) { + return { + value: value, + index: index, + criteria: iterator.call( + context, + value, + index, + list, + ), + }; + }).sort(function (left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), + "value", + ); + }; + + // An internal function used for aggregate "group by" operations. + var group = function (obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function (value, index) { + var key = iterator.call( + context, + value, + index, + obj, + ); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function (obj, value, context) { + return group( + obj, + value, + context, + function (result, key, value) { + (_.has(result, key) + ? result[key] + : (result[key] = []) + ).push(value); + }, + ); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function (obj, value, context) { + return group( + obj, + value, + context, + function (result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }, + ); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function ( + array, + obj, + iterator, + context, + ) { + iterator = + iterator == null + ? _.identity + : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, + high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value + ? (low = mid + 1) + : (high = mid); + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function (obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) + return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function (obj) { + if (obj == null) return 0; + return obj.length === +obj.length + ? obj.length + : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = + _.head = + _.take = + function (array, n, guard) { + if (array == null) return void 0; + return n != null && !guard + ? slice.call(array, 0, n) + : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function (array, n, guard) { + return slice.call( + array, + 0, + array.length - (n == null || guard ? 1 : n), + ); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function (array, n, guard) { + if (array == null) return void 0; + if (n != null && !guard) { + return slice.call( + array, + Math.max(array.length - n, 0), + ); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = + _.tail = + _.drop = + function (array, n, guard) { + return slice.call( + array, + n == null || guard ? 1 : n, + ); + }; + + // Trim out all falsy values from an array. + _.compact = function (array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function (input, shallow, output) { + each(input, function (value) { + if (_.isArray(value)) { + shallow + ? push.apply(output, value) + : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function (array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function (array) { + return _.difference( + array, + slice.call(arguments, 1), + ); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function ( + array, + isSorted, + iterator, + context, + ) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator + ? _.map(array, iterator, context) + : array; + var results = []; + var seen = []; + each(initial, function (value, index) { + if ( + isSorted + ? !index || + seen[seen.length - 1] !== value + : !_.contains(seen, value) + ) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function () { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function (array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function (item) { + return _.every(rest, function (other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function (array) { + var rest = concat.apply( + ArrayProto, + slice.call(arguments, 1), + ); + return _.filter(array, function (value) { + return !_.contains(rest, value); + }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function () { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, "length")); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function (list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function (array, item, isSorted) { + if (array == null) return -1; + var i = 0, + l = array.length; + if (isSorted) { + if (typeof isSorted == "number") { + i = + isSorted < 0 + ? Math.max(0, l + isSorted) + : isSorted; + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if ( + nativeIndexOf && + array.indexOf === nativeIndexOf + ) + return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function (array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if ( + nativeLastIndexOf && + array.lastIndexOf === nativeLastIndexOf + ) { + return hasIndex + ? array.lastIndexOf(item, from) + : array.lastIndexOf(item); + } + var i = hasIndex ? from : array.length; + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function (start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max( + Math.ceil((stop - start) / step), + 0, + ); + var idx = 0; + var range = new Array(len); + + while (idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function (func, context) { + if (func.bind === nativeBind && nativeBind) + return nativeBind.apply( + func, + slice.call(arguments, 1), + ); + var args = slice.call(arguments, 2); + return function () { + return func.apply( + context, + args.concat(slice.call(arguments)), + ); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function (func) { + var args = slice.call(arguments, 1); + return function () { + return func.apply( + this, + args.concat(slice.call(arguments)), + ); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function (obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function (f) { + obj[f] = _.bind(obj[f], obj); + }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function (func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function () { + var key = hasher.apply(this, arguments); + return _.has(memo, key) + ? memo[key] + : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function (func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function () { + return func.apply(null, args); + }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function (func) { + return _.delay.apply( + _, + [func, 1].concat(slice.call(arguments, 1)), + ); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function (func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function () { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function () { + var now = new Date(); + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function (func, wait, immediate) { + var timeout, result; + return function () { + var context = this, + args = arguments; + var later = function () { + timeout = null; + if (!immediate) + result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function (func) { + var ran = false, + memo; + return function () { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function (func, wrapper) { + return function () { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function () { + var funcs = arguments; + return function () { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function (times, func) { + if (times <= 0) return func(); + return function () { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = + nativeKeys || + function (obj) { + if (obj !== Object(obj)) + throw new TypeError("Invalid object"); + var keys = []; + for (var key in obj) + if (_.has(obj, key)) + keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function (obj) { + var values = []; + for (var key in obj) + if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function (obj) { + var pairs = []; + for (var key in obj) + if (_.has(obj, key)) + pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function (obj) { + var result = {}; + for (var key in obj) + if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function (obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function (obj) { + each(slice.call(arguments, 1), function (source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function (obj) { + var copy = {}; + var keys = concat.apply( + ArrayProto, + slice.call(arguments, 1), + ); + each(keys, function (key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function (obj) { + var copy = {}; + var keys = concat.apply( + ArrayProto, + slice.call(arguments, 1), + ); + for (var key in obj) { + if (!_.contains(keys, key)) + copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function (obj) { + each(slice.call(arguments, 1), function (source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function (obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) + ? obj.slice() + : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function (obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function (a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case "[object String]": + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case "[object Number]": + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a + ? b != +b + : a == 0 + ? 1 / a == 1 / b + : a == +b; + case "[object Date]": + case "[object Boolean]": + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case "[object RegExp]": + return ( + a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase + ); + } + if (typeof a != "object" || typeof b != "object") + return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) + return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, + result = true; + // Recursively compare objects and arrays. + if (className == "[object Array]") { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if ( + !(result = eq( + a[size], + b[size], + aStack, + bStack, + )) + ) + break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, + bCtor = b.constructor; + if ( + aCtor !== bCtor && + !( + _.isFunction(aCtor) && + aCtor instanceof aCtor && + _.isFunction(bCtor) && + bCtor instanceof bCtor + ) + ) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if ( + !(result = + _.has(b, key) && + eq( + a[key], + b[key], + aStack, + bStack, + )) + ) + break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !size--) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function (a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function (obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) + return obj.length === 0; + for (var key in obj) + if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function (obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = + nativeIsArray || + function (obj) { + return toString.call(obj) == "[object Array]"; + }; + + // Is a given variable an object? + _.isObject = function (obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each( + [ + "Arguments", + "Function", + "String", + "Number", + "Date", + "RegExp", + ], + function (name) { + _["is" + name] = function (obj) { + return ( + toString.call(obj) == + "[object " + name + "]" + ); + }; + }, + ); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function (obj) { + return !!(obj && _.has(obj, "callee")); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof /./ !== "function") { + _.isFunction = function (obj) { + return typeof obj === "function"; + }; + } + + // Is a given object a finite number? + _.isFinite = function (obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function (obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function (obj) { + return ( + obj === true || + obj === false || + toString.call(obj) == "[object Boolean]" + ); + }; + + // Is a given value equal to null? + _.isNull = function (obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function (obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function (obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function () { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function (value) { + return value; + }; + + // Run a function **n** times. + _.times = function (n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) + accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function (min, max) { + if (max == null) { + max = min; + min = 0; + } + return ( + min + + Math.floor(Math.random() * (max - min + 1)) + ); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "/": "/", + }, + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp( + "[" + _.keys(entityMap.escape).join("") + "]", + "g", + ), + unescape: new RegExp( + "(" + + _.keys(entityMap.unescape).join("|") + + ")", + "g", + ), + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(["escape", "unescape"], function (method) { + _[method] = function (string) { + if (string == null) return ""; + return ("" + string).replace( + entityRegexes[method], + function (match) { + return entityMap[method][match]; + }, + ); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function (object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) + ? value.call(object) + : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function (obj) { + each(_.functions(obj), function (name) { + var func = (_[name] = obj[name]); + _.prototype[name] = function () { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call( + this, + func.apply(_, args), + ); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function (prefix) { + var id = ++idCounter + ""; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g, + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + "\\": "\\", + "\r": "r", + "\n": "n", + "\t": "t", + "\u2028": "u2028", + "\u2029": "u2029", + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function (text, data, settings) { + var render; + settings = _.defaults( + {}, + settings, + _.templateSettings, + ); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp( + [ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source, + ].join("|") + "|$", + "g", + ); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace( + matcher, + function ( + match, + escape, + interpolate, + evaluate, + offset, + ) { + source += text + .slice(index, offset) + .replace(escaper, function (match) { + return "\\" + escapes[match]; + }); + + if (escape) { + source += + "'+\n((__t=(" + + escape + + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += + "'+\n((__t=(" + + interpolate + + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += + "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }, + ); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) + source = "with(obj||{}){\n" + source + "}\n"; + + source = + "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + + "return __p;\n"; + + try { + render = new Function( + settings.variable || "obj", + "_", + source, + ); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function (data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = + "function(" + + (settings.variable || "obj") + + "){\n" + + source + + "}"; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function (obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function (obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each( + [ + "pop", + "push", + "reverse", + "shift", + "sort", + "splice", + "unshift", + ], + function (name) { + var method = ArrayProto[name]; + _.prototype[name] = function () { + var obj = this._wrapped; + method.apply(obj, arguments); + if ( + (name == "shift" || name == "splice") && + obj.length === 0 + ) + delete obj[0]; + return result.call(this, obj); + }; + }, + ); + + // Add all accessor Array functions to the wrapper. + each(["concat", "join", "slice"], function (name) { + var method = ArrayProto[name]; + _.prototype[name] = function () { + return result.call( + this, + method.apply(this._wrapped, arguments), + ); + }; + }); + + _.extend(_.prototype, { + // Start chaining a wrapped Underscore object. + chain: function () { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function () { + return this._wrapped; + }, + }); + }).call(this); + }, + {}, + ], + jshint: [ + function (require, module, exports) { + module.exports = require("nr+AlQ"); + }, + {}, + ], + "nr+AlQ": [ + function (require, module, exports) { + /*! + * JSHint, by JSHint Community. + * + * This file (and this file only) was licensed under the same slightly modified + * MIT license that JSLint is. After a relicensing in 2020 this is now MIT License (Expat). + * Relicensing: https://jshint.com/relicensing-2020/ + * License-Url: https://github.com/jshint/jshint/blob/main/LICENSE + * + * Copyright 2012 Anton Kovalyov (http://jshint.com) + * + * 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. + * + * 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. + * + */ + + /*jshint quotmark:double */ + /*global console:true */ + /*exported console */ + + var _ = require("underscore"); + var events = require("events"); + var vars = require("./vars.js"); + var messages = require("./messages.js"); + var Lexer = require("./lex.js").Lexer; + var reg = require("./reg.js"); + var state = require("./state.js").state; + var style = require("./style.js"); + + // We need this module here because environments such as IE and Rhino + // don't necessarily expose the 'console' API and browserify uses + // it to log things. It's a sad state of affair, really. + var console = require("console-browserify"); + + // We build the application inside a function so that we produce only a singleton + // variable. That function will be invoked immediately, and its return value is + // the JSHINT function itself. + + var JSHINT = (function () { + "use strict"; + + var anonname, // The guessed name for anonymous functions. + api, // Extension API + // These are operators that should not be used with the ! operator. + bang = { + "<": true, + "<=": true, + "==": true, + "===": true, + "!==": true, + "!=": true, + ">": true, + ">=": true, + "+": true, + "-": true, + "*": true, + "/": true, + "%": true, + }, + // These are the JSHint boolean options. + boolOptions = { + asi: true, // if automatic semicolon insertion should be tolerated + bitwise: true, // if bitwise operators should not be allowed + boss: true, // if advanced usage of assignments should be allowed + browser: true, // if the standard browser globals should be predefined + camelcase: true, // if identifiers should be required in camel case + couch: true, // if CouchDB globals should be predefined + curly: true, // if curly braces around all blocks should be required + debug: true, // if debugger statements should be allowed + devel: true, // if logging globals should be predefined (console, alert, etc.) + dojo: true, // if Dojo Toolkit globals should be predefined + eqeqeq: true, // if === should be required + eqnull: true, // if == null comparisons should be tolerated + notypeof: true, // if should report typos in typeof comparisons + es3: true, // if ES3 syntax should be allowed + es5: true, // if ES5 syntax should be allowed (is now set per default) + esnext: true, // if es.next specific syntax should be allowed + moz: true, // if mozilla specific syntax should be allowed + evil: true, // if eval should be allowed + expr: true, // if ExpressionStatement should be allowed as Programs + forin: true, // if for in statements must filter + funcscope: true, // if only function scope should be used for scope tests + gcl: true, // if JSHint should be compatible with Google Closure Linter + globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict') + immed: true, // if immediate invocations must be wrapped in parens + iterator: true, // if the `__iterator__` property should be allowed + jquery: true, // if jQuery globals should be predefined + lastsemic: true, // if semicolons may be omitted for the trailing + // statements inside of a one-line blocks. + laxbreak: true, // if line breaks should not be checked + laxcomma: true, // if line breaks should not be checked around commas + loopfunc: true, // if functions should be allowed to be defined within + // loops + mootools: true, // if MooTools globals should be predefined + multistr: true, // allow multiline strings + freeze: true, // if modifying native object prototypes should be disallowed + newcap: true, // if constructor names must be capitalized + noarg: true, // if arguments.caller and arguments.callee should be + // disallowed + node: true, // if the Node.js environment globals should be + // predefined + noempty: true, // if empty blocks should be disallowed + nonbsp: true, // if non-breaking spaces should be disallowed + nonew: true, // if using `new` for side-effects should be disallowed + nonstandard: true, // if non-standard (but widely adopted) globals should + // be predefined + nomen: true, // if names should be checked + onevar: true, // if only one var statement per function should be + // allowed + passfail: true, // if the scan should stop on first error + phantom: true, // if PhantomJS symbols should be allowed + plusplus: true, // if increment/decrement should not be allowed + proto: true, // if the `__proto__` property should be allowed + prototypejs: true, // if Prototype and Scriptaculous globals should be + // predefined + rhino: true, // if the Rhino environment globals should be predefined + shelljs: true, // if ShellJS globals should be predefined + typed: true, // if typed array globals should be predefined + undef: true, // if variables should be declared before used + scripturl: true, // if script-targeted URLs should be tolerated + smarttabs: true, // if smarttabs should be tolerated + // (http://www.emacswiki.org/emacs/SmartTabs) + strict: true, // require the "use strict"; pragma + sub: true, // if all forms of subscript notation are tolerated + supernew: true, // if `new function () { ... };` and `new Object;` + // should be tolerated + trailing: true, // if trailing whitespace rules apply + validthis: true, // if 'this' inside a non-constructor function is valid. + // This is a function scoped option only. + withstmt: true, // if with statements should be allowed + white: true, // if strict whitespace rules apply + worker: true, // if Web Worker script symbols should be allowed + wsh: true, // if the Windows Scripting Host environment globals + // should be predefined + yui: true, // YUI variables should be predefined + noyield: true, // allow generators without a yield + + // Obsolete options + onecase: true, // if one case switch statements should be allowed + regexp: true, // if the . should not be allowed in regexp literals + regexdash: true, // if unescaped first/last dash (-) inside brackets + // should be tolerated + }, + // These are the JSHint options that can take any value + // (we use this object to detect invalid options) + valOptions = { + maxlen: false, + indent: false, + maxerr: false, + predef: false, //predef is deprecated and being replaced by globals + globals: false, + quotmark: false, //'single'|'double'|true + scope: false, + maxstatements: false, // {int} max statements per function + maxdepth: false, // {int} max nested block depth per function + maxparams: false, // {int} max params per function + maxcomplexity: false, // {int} max cyclomatic complexity per function + shadow: false, // if variable shadowing should be tolerated + // "inner" - check for variables defined in the same scope only + // "outer" - check for variables defined in outer scopes as well + // false - same as inner + // true - allow variable shadowing + unused: true, // warn if variables are unused. Available options: + // false - don't check for unused variables + // true - "vars" + check last function param + // "vars" - skip checking unused function params + // "strict" - "vars" + check all function params + latedef: false, // warn if the variable is used before its definition + // false - don't emit any warnings + // true - warn if any variable is used before its definition + // "nofunc" - warn for any variable but function declarations + ignore: false, // start/end ignoring lines of code, bypassing the lexer + // start - start ignoring lines, including the current line + // end - stop ignoring lines, starting on the next line + // line - ignore warnings / errors for just a single line + // (this option does not bypass the lexer) + }, + // These are JSHint boolean options which are shared with JSLint + // where the definition in JSHint is opposite JSLint + invertedOptions = { + bitwise: true, + forin: true, + newcap: true, + nomen: true, + plusplus: true, + regexp: true, + undef: true, + white: true, + + // Inverted and renamed, use JSHint name here + eqeqeq: true, + onevar: true, + strict: true, + }, + // These are JSHint boolean options which are shared with JSLint + // where the name has been changed but the effect is unchanged + renamedOptions = { + eqeq: "eqeqeq", + vars: "onevar", + windows: "wsh", + sloppy: "strict", + }, + declared, // Globals that were declared using /*global ... */ syntax. + exported, // Variables that are used outside of the current file. + functionicity = [ + "closure", + "exception", + "global", + "label", + "outer", + "unused", + "var", + ], + funct, // The current function + functions, // All of the functions + global, // The global scope + implied, // Implied globals + inblock, + indent, + lookahead, + lex, + member, + membersOnly, + noreach, + predefined, // Global variables defined by option + scope, // The current scope + stack, + unuseds, + urls, + warnings, + extraModules = [], + emitter = new events.EventEmitter(); + + function checkOption(name, t) { + name = name.trim(); + + if (/^[+-]W\d{3}$/g.test(name)) { + return true; + } + + if ( + valOptions[name] === undefined && + boolOptions[name] === undefined + ) { + if (t.type !== "jslint") { + error("E001", t, name); + return false; + } + } + + return true; + } + + function isString(obj) { + return ( + Object.prototype.toString.call(obj) === + "[object String]" + ); + } + + function isIdentifier(tkn, value) { + if (!tkn) return false; + + if (!tkn.identifier || tkn.value !== value) + return false; + + return true; + } + + function isReserved(token) { + if (!token.reserved) { + return false; + } + var meta = token.meta; + + if ( + meta && + meta.isFutureReservedWord && + state.option.inES5() + ) { + // ES3 FutureReservedWord in an ES5 environment. + if (!meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (meta.strictOnly) { + if ( + !state.option.strict && + !state.directive["use strict"] + ) { + return false; + } + } + + if (token.isProperty) { + return false; + } + } + + return true; + } + + function supplant(str, data) { + return str.replace( + /\{([^{}]*)\}/g, + function (a, b) { + var r = data[b]; + return typeof r === "string" || + typeof r === "number" + ? r + : a; + }, + ); + } + + function combine(dest, src) { + Object.keys(src).forEach(function (name) { + if (JSHINT.blacklist.hasOwnProperty(name)) + return; + dest[name] = src[name]; + }); + } + + function assume() { + if (state.option.es5) { + warning("I003"); + } + if (state.option.couch) { + combine(predefined, vars.couch); + } + + if (state.option.rhino) { + combine(predefined, vars.rhino); + } + + if (state.option.shelljs) { + combine(predefined, vars.shelljs); + combine(predefined, vars.node); + } + if (state.option.typed) { + combine(predefined, vars.typed); + } + + if (state.option.phantom) { + combine(predefined, vars.phantom); + } + + if (state.option.prototypejs) { + combine(predefined, vars.prototypejs); + } + + if (state.option.node) { + combine(predefined, vars.node); + combine(predefined, vars.typed); + } + + if (state.option.devel) { + combine(predefined, vars.devel); + } + + if (state.option.dojo) { + combine(predefined, vars.dojo); + } + + if (state.option.browser) { + combine(predefined, vars.browser); + combine(predefined, vars.typed); + } + + if (state.option.nonstandard) { + combine(predefined, vars.nonstandard); + } + + if (state.option.jquery) { + combine(predefined, vars.jquery); + } + + if (state.option.mootools) { + combine(predefined, vars.mootools); + } + + if (state.option.worker) { + combine(predefined, vars.worker); + } + + if (state.option.wsh) { + combine(predefined, vars.wsh); + } + + if ( + state.option.globalstrict && + state.option.strict !== false + ) { + state.option.strict = true; + } + + if (state.option.yui) { + combine(predefined, vars.yui); + } + + // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz + + state.option.inMoz = function (strict) { + if (strict) { + return ( + state.option.moz && !state.option.esnext + ); + } + return state.option.moz; + }; + + state.option.inESNext = function (strict) { + if (strict) { + return ( + !state.option.moz && state.option.esnext + ); + } + return state.option.moz || state.option.esnext; + }; + + state.option.inES5 = function (/* strict */) { + return !state.option.es3; + }; + + state.option.inES3 = function (strict) { + if (strict) { + return ( + !state.option.moz && + !state.option.esnext && + state.option.es3 + ); + } + return state.option.es3; + }; + } + + // Produce an error warning. + function quit(code, line, chr) { + var percentage = Math.floor( + (line / state.lines.length) * 100, + ); + var message = messages.errors[code].desc; + + throw { + name: "JSHintError", + line: line, + character: chr, + message: + message + " (" + percentage + "% scanned).", + raw: message, + code: code, + }; + } + + function isundef(scope, code, token, a) { + return JSHINT.undefs.push([scope, code, token, a]); + } + + function warning(code, t, a, b, c, d) { + var ch, l, w, msg; + + if (/^W\d{3}$/.test(code)) { + if (state.ignored[code]) return; + + msg = messages.warnings[code]; + } else if (/E\d{3}/.test(code)) { + msg = messages.errors[code]; + } else if (/I\d{3}/.test(code)) { + msg = messages.info[code]; + } + + t = t || state.tokens.next; + if (t.id === "(end)") { + // `~ + t = state.tokens.curr; + } + + l = t.line || 0; + ch = t.from || 0; + + w = { + id: "(error)", + raw: msg.desc, + code: msg.code, + evidence: state.lines[l - 1] || "", + line: l, + character: ch, + scope: JSHINT.scope, + a: a, + b: b, + c: c, + d: d, + }; + + w.reason = supplant(msg.desc, w); + JSHINT.errors.push(w); + + if (state.option.passfail) { + quit("E042", l, ch); + } + + warnings += 1; + if (warnings >= state.option.maxerr) { + quit("E043", l, ch); + } + + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning( + m, + { + line: l, + from: ch, + }, + a, + b, + c, + d, + ); + } + + function error(m, t, a, b, c, d) { + warning(m, t, a, b, c, d); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error( + m, + { + line: l, + from: ch, + }, + a, + b, + c, + d, + ); + } + + // Tracking of "internal" scripts, like eval containing a static string + function addInternalSrc(elem, src) { + var i; + i = { + id: "(internal)", + elem: elem, + value: src, + }; + JSHINT.internals.push(i); + return i; + } + + // name: string + // opts: { type: string, token: token, islet: bool } + function addlabel(name, opts) { + opts = opts || {}; + + var type = opts.type; + var token = opts.token; + var islet = opts.islet; + + // Define label in the current function in the current scope. + if (type === "exception") { + if (_.has(funct["(context)"], name)) { + if ( + funct[name] !== true && + !state.option.node + ) { + warning( + "W002", + state.tokens.next, + name, + ); + } + } + } + + if (_.has(funct, name) && !funct["(global)"]) { + if (funct[name] === true) { + if (state.option.latedef) { + if ( + (state.option.latedef === true && + _.contains( + [funct[name], type], + "unction", + )) || + !_.contains( + [funct[name], type], + "unction", + ) + ) { + warning( + "W003", + state.tokens.next, + name, + ); + } + } + } else { + if ( + ((!state.option.shadow || + _.contains( + ["inner", "outer"], + state.option.shadow, + )) && + type !== "exception") || + funct["(blockscope)"].getlabel(name) + ) { + warning( + "W004", + state.tokens.next, + name, + ); + } + } + } + + if ( + funct["(context)"] && + _.has(funct["(context)"], name) && + type !== "function" + ) { + if (state.option.shadow === "outer") { + warning("W123", state.tokens.next, name); + } + } + + // if the identifier is from a let, adds it only to the current blockscope + if (islet) { + funct["(blockscope)"].current.add( + name, + type, + state.tokens.curr, + ); + } else { + funct["(blockscope)"].shadow(name); + funct[name] = type; + + if (token) { + funct["(tokens)"][name] = token; + } + + setprop(funct, name, { + unused: opts.unused || false, + }); + + if (funct["(global)"]) { + global[name] = funct; + if (_.has(implied, name)) { + if (state.option.latedef) { + if ( + (state.option.latedef === + true && + _.contains( + [funct[name], type], + "unction", + )) || + !_.contains( + [funct[name], type], + "unction", + ) + ) { + warning( + "W003", + state.tokens.next, + name, + ); + } + } + + delete implied[name]; + } + } else { + scope[name] = funct; + } + } + } + + function doOption() { + var nt = state.tokens.next; + var body = nt.body.split(",").map(function (s) { + return s.trim(); + }); + var predef = {}; + + if (nt.type === "globals") { + body.forEach(function (g) { + g = g.split(":"); + var key = (g[0] || "").trim(); + var val = (g[1] || "").trim(); + + if (key.charAt(0) === "-") { + key = key.slice(1); + val = false; + + JSHINT.blacklist[key] = key; + delete predefined[key]; + } else { + predef[key] = val === "true"; + } + }); + + combine(predefined, predef); + + for (var key in predef) { + if (_.has(predef, key)) { + declared[key] = nt; + } + } + } + + if (nt.type === "exported") { + body.forEach(function (e) { + exported[e] = true; + }); + } + + if (nt.type === "members") { + membersOnly = membersOnly || {}; + + body.forEach(function (m) { + var ch1 = m.charAt(0); + var ch2 = m.charAt(m.length - 1); + + if ( + ch1 === ch2 && + (ch1 === '"' || ch1 === "'") + ) { + m = m + .substr(1, m.length - 2) + .replace('\\"', '"'); + } + + membersOnly[m] = false; + }); + } + + var numvals = [ + "maxstatements", + "maxparams", + "maxdepth", + "maxcomplexity", + "maxerr", + "maxlen", + "indent", + ]; + + if (nt.type === "jshint" || nt.type === "jslint") { + body.forEach(function (g) { + g = g.split(":"); + var key = (g[0] || "").trim(); + var val = (g[1] || "").trim(); + + if (!checkOption(key, nt)) { + return; + } + + if (numvals.indexOf(key) >= 0) { + // GH988 - numeric options can be disabled by setting them to `false` + if (val !== "false") { + val = +val; + + if ( + typeof val !== "number" || + !isFinite(val) || + val <= 0 || + Math.floor(val) !== val + ) { + error("E032", nt, g[1].trim()); + return; + } + + if (key === "indent") { + state.option[ + "(explicitIndent)" + ] = true; + } + state.option[key] = val; + } else { + if (key === "indent") { + state.option[ + "(explicitIndent)" + ] = false; + } else { + state.option[key] = false; + } + } + + return; + } + + if (key === "validthis") { + // `validthis` is valid only within a function scope. + + if (funct["(global)"]) + return void error("E009"); + + if (val !== "true" && val !== "false") + return void error("E002", nt); + + state.option.validthis = val === "true"; + return; + } + + if (key === "quotmark") { + switch (val) { + case "true": + case "false": + state.option.quotmark = + val === "true"; + break; + case "double": + case "single": + state.option.quotmark = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "shadow") { + switch (val) { + case "true": + state.option.shadow = true; + break; + case "outer": + state.option.shadow = "outer"; + break; + case "false": + case "inner": + state.option.shadow = "inner"; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "unused") { + switch (val) { + case "true": + state.option.unused = true; + break; + case "false": + state.option.unused = false; + break; + case "vars": + case "strict": + state.option.unused = val; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "latedef") { + switch (val) { + case "true": + state.option.latedef = true; + break; + case "false": + state.option.latedef = false; + break; + case "nofunc": + state.option.latedef = "nofunc"; + break; + default: + error("E002", nt); + } + return; + } + + if (key === "ignore") { + switch (val) { + case "start": + state.ignoreLinterErrors = true; + break; + case "end": + state.ignoreLinterErrors = false; + break; + case "line": + // Any errors or warnings that happened on the current line, make them go away. + JSHINT.errors = _.reject( + JSHINT.errors, + function (error) { + // nt.line returns to the current line + return ( + error.line === + nt.line + ); + }, + ); + break; + default: + error("E002", nt); + } + return; + } + + var match = /^([+-])(W\d{3})$/g.exec(key); + if (match) { + // ignore for -W..., unignore for +W... + state.ignored[match[2]] = + match[1] === "-"; + return; + } + + var tn; + if (val === "true" || val === "false") { + if (nt.type === "jslint") { + tn = renamedOptions[key] || key; + state.option[tn] = val === "true"; + + if ( + invertedOptions[tn] !== + undefined + ) { + state.option[tn] = + !state.option[tn]; + } + } else { + state.option[key] = val === "true"; + } + + if (key === "newcap") { + state.option["(explicitNewcap)"] = + true; + } + return; + } + + error("E002", nt); + }); + + assume(); + } + } + + // We need a peek function. If it has an argument, it peeks that much farther + // ahead. It is used to distinguish + // for ( var i in ... + // from + // for ( var i = ... + + function peek(p) { + var i = p || 0, + j = 0, + t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + // Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (state.tokens.curr.id) { + case "(number)": + if (state.tokens.next.id === ".") { + warning("W005", state.tokens.curr); + } + break; + case "-": + if ( + state.tokens.next.id === "-" || + state.tokens.next.id === "--" + ) { + warning("W006"); + } + break; + case "+": + if ( + state.tokens.next.id === "+" || + state.tokens.next.id === "++" + ) { + warning("W007"); + } + break; + } + + if ( + state.tokens.curr.type === "(string)" || + state.tokens.curr.identifier + ) { + anonname = state.tokens.curr.value; + } + + if (id && state.tokens.next.id !== id) { + if (t) { + if (state.tokens.next.id === "(end)") { + error("E019", t, t.id); + } else { + error( + "E020", + state.tokens.next, + id, + t.id, + t.line, + state.tokens.next.value, + ); + } + } else if ( + state.tokens.next.type !== "(identifier)" || + state.tokens.next.value !== id + ) { + warning( + "W116", + state.tokens.next, + id, + state.tokens.next.value, + ); + } + } + + state.tokens.prev = state.tokens.curr; + state.tokens.curr = state.tokens.next; + for (;;) { + state.tokens.next = + lookahead.shift() || lex.token(); + + if (!state.tokens.next) { + // No more tokens left, give up + quit("E041", state.tokens.curr.line); + } + + if ( + state.tokens.next.id === "(end)" || + state.tokens.next.id === "(error)" + ) { + return; + } + + if (state.tokens.next.check) { + state.tokens.next.check(); + } + + if (state.tokens.next.isSpecial) { + doOption(); + } else { + if (state.tokens.next.id !== "(endline)") { + break; + } + } + } + } + + function isInfix(token) { + return ( + token.infix || + (!token.identifier && !!token.led) + ); + } + + function isEndOfExpr() { + var curr = state.tokens.curr; + var next = state.tokens.next; + if ( + next.id === ";" || + next.id === "}" || + next.id === ":" + ) { + return true; + } + if ( + isInfix(next) === isInfix(curr) || + (curr.id === "yield" && + state.option.inMoz(true)) + ) { + return curr.line !== next.line; + } + return false; + } + + // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it + // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is + // like .nud except that it is only used on the first token of a statement. + // Having .fud makes it much easier to define statement-oriented languages like + // JavaScript. I retained Pratt's nomenclature. + + // .nud Null denotation + // .fud First null denotation + // .led Left denotation + // lbp Left binding power + // rbp Right binding power + + // They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left, + isArray = false, + isObject = false, + isLetExpr = false; + + // if current expression is a let expression + if ( + !initial && + state.tokens.next.value === "let" && + peek(0).value === "(" + ) { + if (!state.option.inMoz(true)) { + warning( + "W118", + state.tokens.next, + "let expressions", + ); + } + isLetExpr = true; + // create a new block scope we use only for the current expression + funct["(blockscope)"].stack(); + advance("let"); + advance("("); + state.syntax["let"].fud.call( + state.syntax["let"].fud, + false, + ); + advance(")"); + } + + if (state.tokens.next.id === "(end)") + error("E006", state.tokens.curr); + + advance(); + + if (initial) { + anonname = "anonymous"; + funct["(verb)"] = state.tokens.curr.value; + } + + if (initial === true && state.tokens.curr.fud) { + left = state.tokens.curr.fud(); + } else { + if (state.tokens.curr.nud) { + left = state.tokens.curr.nud(); + } else { + error( + "E030", + state.tokens.curr, + state.tokens.curr.id, + ); + } + + while ( + rbp < state.tokens.next.lbp && + !isEndOfExpr() + ) { + isArray = + state.tokens.curr.value === "Array"; + isObject = + state.tokens.curr.value === "Object"; + + // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() + // Line breaks in IfStatement heads exist to satisfy the checkJSHint + // "Line too long." error. + if ( + left && + (left.value || + (left.first && left.first.value)) + ) { + // If the left.value is not "new", or the left.first.value is a "." + // then safely assume that this is not "new Array()" and possibly + // not "new Object()"... + if ( + left.value !== "new" || + (left.first && + left.first.value && + left.first.value === ".") + ) { + isArray = false; + // ...In the case of Object, if the left.value and state.tokens.curr.value + // are not equal, then safely assume that this not "new Object()" + if ( + left.value !== + state.tokens.curr.value + ) { + isObject = false; + } + } + } + + advance(); + + if ( + isArray && + state.tokens.curr.id === "(" && + state.tokens.next.id === ")" + ) { + warning("W009", state.tokens.curr); + } + + if ( + isObject && + state.tokens.curr.id === "(" && + state.tokens.next.id === ")" + ) { + warning("W010", state.tokens.curr); + } + + if (left && state.tokens.curr.led) { + left = state.tokens.curr.led(left); + } else { + error( + "E033", + state.tokens.curr, + state.tokens.curr.id, + ); + } + } + } + if (isLetExpr) { + funct["(blockscope)"].unstack(); + } + return left; + } + + // Functions for conformance of style. + + function adjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white) { + if ( + left.character !== right.from && + left.line === right.line + ) { + left.from += left.character - left.from; + warning("W011", left, left.value); + } + } + } + + function nobreak(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if ( + state.option.white && + (left.character !== right.from || + left.line !== right.line) + ) { + warning("W012", right, right.value); + } + } + + function nospace(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (state.option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + + if (left.value === ";" && right.value === ";") { + return; + } + + if ( + left.line === right.line && + left.character === right.from + ) { + left.from += left.character - left.from; + warning("W013", left, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if ( + !state.option.laxbreak && + left.line !== right.line + ) { + warning("W014", right, right.value); + } else if (state.option.white) { + left = left || state.tokens.curr; + right = right || state.tokens.next; + if (left.character === right.from) { + left.from += left.character - left.from; + warning("W013", left, left.value); + } + } + } + + function indentation(bias) { + if ( + !state.option.white && + !state.option["(explicitIndent)"] + ) { + return; + } + + if (state.tokens.next.id === "(end)") { + return; + } + + var i = indent + (bias || 0); + if (state.tokens.next.from !== i) { + warning( + "W015", + state.tokens.next, + state.tokens.next.value, + i, + state.tokens.next.from, + ); + } + } + + function nolinebreak(t) { + t = t || state.tokens.curr; + if (t.line !== state.tokens.next.line) { + warning("E022", t, t.value); + } + } + + function nobreakcomma(left, right) { + if (left.line !== right.line) { + if (!state.option.laxcomma) { + if (comma.first) { + warning("I001"); + comma.first = false; + } + warning("W014", left, right.value); + } + } else if ( + !left.comment && + left.character !== right.from && + state.option.white + ) { + left.from += left.character - left.from; + warning("W011", left, left.value); + } + } + + function comma(opts) { + opts = opts || {}; + + if (!opts.peek) { + nobreakcomma( + state.tokens.curr, + state.tokens.next, + ); + advance(","); + } else { + nobreakcomma( + state.tokens.prev, + state.tokens.curr, + ); + } + + // TODO: This is a temporary solution to fight against false-positives in + // arrays and objects with trailing commas (see GH-363). The best solution + // would be to extract all whitespace rules out of parser. + + if ( + state.tokens.next.value !== "]" && + state.tokens.next.value !== "}" + ) { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + } + + if ( + state.tokens.next.identifier && + !(opts.property && state.option.inES5()) + ) { + // Keywords that cannot follow a comma operator. + switch (state.tokens.next.value) { + case "break": + case "case": + case "catch": + case "continue": + case "default": + case "do": + case "else": + case "finally": + case "for": + case "if": + case "in": + case "instanceof": + case "return": + case "switch": + case "throw": + case "try": + case "var": + case "let": + case "while": + case "with": + error( + "E024", + state.tokens.next, + state.tokens.next.value, + ); + return false; + } + } + + if (state.tokens.next.type === "(punctuator)") { + switch (state.tokens.next.value) { + case "}": + case "]": + case ",": + if (opts.allowTrailing) { + return true; + } + + /* falls through */ + case ")": + error( + "E024", + state.tokens.next, + state.tokens.next.value, + ); + return false; + } + } + return true; + } + + // Functional constructors for making the symbols that will be inherited by + // tokens. + + function symbol(s, p) { + var x = state.syntax[s]; + if (!x || typeof x !== "object") { + state.syntax[s] = x = { + id: s, + lbp: p, + value: s, + }; + } + return x; + } + + function delim(s) { + return symbol(s, 0); + } + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + function reserveName(x) { + var c = x.id.charAt(0); + if ( + (c >= "a" && c <= "z") || + (c >= "A" && c <= "Z") + ) { + x.identifier = x.reserved = true; + } + return x; + } + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + + x.nud = + typeof f === "function" + ? f + : function () { + this.right = expression(150); + this.arity = "unary"; + + if ( + this.id === "++" || + this.id === "--" + ) { + if (state.option.plusplus) { + warning( + "W016", + this, + this.id, + ); + } else if ( + this.right && + (!this.right.identifier || + isReserved( + this.right, + )) && + this.right.id !== "." && + this.right.id !== "[" + ) { + warning("W017", this); + } + } + + return this; + }; + + return x; + } + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + function reserve(name, func) { + var x = type(name, func); + x.identifier = true; + x.reserved = true; + return x; + } + + function FutureReservedWord(name, meta) { + var x = type( + name, + (meta && meta.nud) || + function () { + return this; + }, + ); + + meta = meta || {}; + meta.isFutureReservedWord = true; + + x.value = name; + x.identifier = true; + x.reserved = true; + x.meta = meta; + + return x; + } + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === "function") { + v(this); + } + return this; + }); + } + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.infix = true; + x.led = function (left) { + if (!w) { + nobreaknonadjacent( + state.tokens.prev, + state.tokens.curr, + ); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + } + if (s === "in" && left.id === "!") { + warning("W018", left, "!"); + } + if (typeof f === "function") { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + function application(s) { + var x = symbol(s, 42); + + x.led = function (left) { + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.curr, + "arrow function syntax (=>)", + ); + } + + nobreaknonadjacent( + state.tokens.prev, + state.tokens.curr, + ); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + + this.left = left; + this.right = doFunction( + undefined, + undefined, + false, + left, + ); + return this; + }; + return x; + } + + function relation(s, f) { + var x = symbol(s, 100); + + x.led = function (left) { + nobreaknonadjacent( + state.tokens.prev, + state.tokens.curr, + ); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + var right = expression(100); + + if ( + isIdentifier(left, "NaN") || + isIdentifier(right, "NaN") + ) { + warning("W019", this); + } else if (f) { + f.apply(this, [left, right]); + } + + if (!left || !right) { + quit("E041", state.tokens.curr.line); + } + + if (left.id === "!") { + warning("W018", left, "!"); + } + + if (right.id === "!") { + warning("W018", right, "!"); + } + + this.left = left; + this.right = right; + return this; + }; + return x; + } + + function isPoorRelation(node) { + return ( + node && + ((node.type === "(number)" && + +node.value === 0) || + (node.type === "(string)" && + node.value === "") || + (node.type === "null" && + !state.option.eqnull) || + node.type === "true" || + node.type === "false" || + node.type === "undefined") + ); + } + + // Checks whether the 'typeof' operator is used with the correct + // value. For docs on 'typeof' see: + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof + + function isTypoTypeof(left, right) { + if (state.option.notypeof) return false; + + if (!left || !right) return false; + + var values = [ + "undefined", + "object", + "boolean", + "number", + "string", + "function", + "xml", + "object", + "unknown", + ]; + + if ( + right.type === "(identifier)" && + right.value === "typeof" && + left.type === "(string)" + ) + return !_.contains(values, left.value); + + return false; + } + + function findNativePrototype(left) { + var natives = [ + "Array", + "ArrayBuffer", + "Boolean", + "Collator", + "DataView", + "Date", + "DateTimeFormat", + "Error", + "EvalError", + "Float32Array", + "Float64Array", + "Function", + "Infinity", + "Intl", + "Int16Array", + "Int32Array", + "Int8Array", + "Iterator", + "Number", + "NumberFormat", + "Object", + "RangeError", + "ReferenceError", + "RegExp", + "StopIteration", + "String", + "SyntaxError", + "TypeError", + "Uint16Array", + "Uint32Array", + "Uint8Array", + "Uint8ClampedArray", + "URIError", + ]; + + function walkPrototype(obj) { + if (typeof obj !== "object") return; + return obj.right === "prototype" + ? obj + : walkPrototype(obj.left); + } + + function walkNative(obj) { + while ( + !obj.identifier && + typeof obj.left === "object" + ) + obj = obj.left; + + if ( + obj.identifier && + natives.indexOf(obj.value) >= 0 + ) + return obj.value; + } + + var prototype = walkPrototype(left); + if (prototype) return walkNative(prototype); + } + + function assignop(s, f, p) { + var x = infix( + s, + typeof f === "function" + ? f + : function (left, that) { + that.left = left; + + if (left) { + if (state.option.freeze) { + var nativeObject = + findNativePrototype( + left, + ); + if (nativeObject) + warning( + "W121", + left, + nativeObject, + ); + } + + if ( + predefined[left.value] === + false && + scope[left.value][ + "(global)" + ] === true + ) { + warning("W020", left); + } else if (left["function"]) { + warning( + "W021", + left, + left.value, + ); + } + + if ( + funct[left.value] === + "const" + ) { + error( + "E013", + left, + left.value, + ); + } + + if (left.id === ".") { + if (!left.left) { + warning("E031", that); + } else if ( + left.left.value === + "arguments" && + !state.directive[ + "use strict" + ] + ) { + warning("E031", that); + } + + that.right = expression(10); + return that; + } else if (left.id === "[") { + if ( + state.tokens.curr.left + .first + ) { + state.tokens.curr.left.first.forEach( + function (t) { + if ( + funct[ + t.value + ] === + "const" + ) { + error( + "E013", + t, + t.value, + ); + } + }, + ); + } else if (!left.left) { + warning("E031", that); + } else if ( + left.left.value === + "arguments" && + !state.directive[ + "use strict" + ] + ) { + warning("E031", that); + } + that.right = expression(10); + return that; + } else if ( + left.identifier && + !isReserved(left) + ) { + if ( + funct[left.value] === + "exception" + ) { + warning("W022", left); + } + that.right = expression(10); + return that; + } + + if ( + left === + state.syntax["function"] + ) { + warning( + "W023", + state.tokens.curr, + ); + } + } + + error("E031", that); + }, + p, + ); + + x.exps = true; + x.assign = true; + return x; + } + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = + typeof f === "function" + ? f + : function (left) { + if (state.option.bitwise) { + warning("W016", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + function bitwiseassignop(s) { + return assignop( + s, + function (left, that) { + if (state.option.bitwise) { + warning("W016", that, that.id); + } + nonadjacent( + state.tokens.prev, + state.tokens.curr, + ); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if (left) { + if ( + left.id === "." || + left.id === "[" || + (left.identifier && + !isReserved(left)) + ) { + expression(10); + return that; + } + if (left === state.syntax["function"]) { + warning("W023", state.tokens.curr); + } + return that; + } + error("E031", that); + }, + 20, + ); + } + + function suffix(s) { + var x = symbol(s, 150); + + x.led = function (left) { + if (state.option.plusplus) { + warning("W016", this, this.id); + } else if ( + (!left.identifier || isReserved(left)) && + left.id !== "." && + left.id !== "[" + ) { + warning("W017", this); + } + + this.left = left; + return this; + }; + return x; + } + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + // prop means that this identifier is that of an object property + + function optionalidentifier(fnparam, prop) { + if (!state.tokens.next.identifier) { + return; + } + + advance(); + + var curr = state.tokens.curr; + var val = state.tokens.curr.value; + + if (!isReserved(curr)) { + return val; + } + + if (prop) { + if (state.option.inES5()) { + return val; + } + } + + if (fnparam && val === "undefined") { + return val; + } + + // Display an info message about reserved words as properties + // and ES5 but do it only once. + if (prop && !api.getCache("displayed:I002")) { + api.setCache("displayed:I002", true); + warning("I002"); + } + + warning( + "W024", + state.tokens.curr, + state.tokens.curr.id, + ); + return val; + } + + // fnparam means that this identifier is being defined as a function + // argument + // prop means that this identifier is that of an object property + function identifier(fnparam, prop) { + var i = optionalidentifier(fnparam, prop); + if (i) { + return i; + } + if ( + state.tokens.curr.id === "function" && + state.tokens.next.id === "(" + ) { + warning("W025"); + } else { + error( + "E030", + state.tokens.next, + state.tokens.next.value, + ); + } + } + + function reachable(s) { + var i = 0, + t; + if (state.tokens.next.id !== ";" || noreach) { + return; + } + for (;;) { + do { + t = peek(i); + i += 1; + } while ( + t.id != "(end)" && + t.id === "(comment)" + ); + + if (t.reach) { + return; + } + if (t.id !== "(endline)") { + if (t.id === "function") { + if (state.option.latedef === true) { + warning("W026", t); + } + break; + } + + warning("W027", t, t.value, s); + break; + } + } + } + + function statement(noindent) { + var values; + var i = indent, + r, + s = scope, + t = state.tokens.next; + + if (t.id === ";") { + advance(";"); + return; + } + + // Is this a labelled statement? + var res = isReserved(t); + + // We're being more tolerant here: if someone uses + // a FutureReservedWord as a label, we warn but proceed + // anyway. + + if ( + res && + t.meta && + t.meta.isFutureReservedWord && + peek().id === ":" + ) { + warning("W024", t, t.id); + res = false; + } + + // detect a destructuring assignment + if (_.has(["[", "{"], t.value)) { + if (lookupBlockType().isDestAssign) { + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.curr, + "destructuring expression", + ); + } + values = destructuringExpression(); + values.forEach(function (tok) { + isundef( + funct, + "W117", + tok.token, + tok.id, + ); + }); + advance("="); + destructuringExpressionMatch( + values, + expression(10, true), + ); + advance(";"); + return; + } + } + if (t.identifier && !res && peek().id === ":") { + advance(); + advance(":"); + scope = Object.create(s); + addlabel(t.value, { type: "label" }); + + if ( + !state.tokens.next.labelled && + state.tokens.next.value !== "{" + ) { + warning( + "W028", + state.tokens.next, + t.value, + state.tokens.next.value, + ); + } + + state.tokens.next.label = t.value; + t = state.tokens.next; + } + + // Is it a lonely block? + + if (t.id === "{") { + // Is it a switch case block? + // + // switch (foo) { + // case bar: { <= here. + // ... + // } + // } + var iscase = + funct["(verb)"] === "case" && + state.tokens.curr.value === ":"; + block(true, true, false, false, iscase); + return; + } + + // Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + + if ( + r && + (!r.identifier || r.value !== "function") && + r.type !== "(punctuator)" + ) { + if ( + !state.directive["use strict"] && + state.option.globalstrict && + state.option.strict + ) { + warning("E007"); + } + } + + // Look for the final semicolon. + + if (!t.block) { + if (!state.option.expr && (!r || !r.exps)) { + warning("W030", state.tokens.curr); + } else if ( + state.option.nonew && + r && + r.left && + r.id === "(" && + r.left.id === "new" + ) { + warning("W031", t); + } + + if (state.tokens.next.id !== ";") { + if (!state.option.asi) { + // If this is the last statement in a block that ends on + // the same line *and* option lastsemic is on, ignore the warning. + // Otherwise, complain about missing semicolon. + if ( + !state.option.lastsemic || + state.tokens.next.id !== "}" || + state.tokens.next.line !== + state.tokens.curr.line + ) { + warningAt( + "W033", + state.tokens.curr.line, + state.tokens.curr.character, + ); + } + } + } else { + adjacent( + state.tokens.curr, + state.tokens.next, + ); + advance(";"); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + } + } + + // Restore the indentation. + + indent = i; + scope = s; + return r; + } + + function statements(startLine) { + var a = [], + p; + + while ( + !state.tokens.next.reach && + state.tokens.next.id !== "(end)" + ) { + if (state.tokens.next.id === ";") { + p = peek(); + + if (!p || (p.id !== "(" && p.id !== "[")) { + warning("W032"); + } + + advance(";"); + } else { + a.push( + statement( + startLine === + state.tokens.next.line, + ), + ); + } + } + return a; + } + + /* + * read all directives + * recognizes a simple form of asi, but always + * warns, if it is used + */ + function directives() { + var i, p, pn; + + for (;;) { + if (state.tokens.next.id === "(string)") { + p = peek(0); + if (p.id === "(endline)") { + i = 1; + do { + pn = peek(i); + i = i + 1; + } while (pn.id === "(endline)"); + + if (pn.id !== ";") { + if ( + pn.id !== "(string)" && + pn.id !== "(number)" && + pn.id !== "(regexp)" && + pn.identifier !== true && + pn.id !== "}" + ) { + break; + } + warning("W033", state.tokens.next); + } else { + p = pn; + } + } else if (p.id === "}") { + // Directive with no other statements, warn about missing semicolon + warning("W033", p); + } else if (p.id !== ";") { + break; + } + + indentation(); + advance(); + if ( + state.directive[state.tokens.curr.value] + ) { + warning( + "W034", + state.tokens.curr, + state.tokens.curr.value, + ); + } + + if ( + state.tokens.curr.value === "use strict" + ) { + if (!state.option["(explicitNewcap)"]) + state.option.newcap = true; + state.option.undef = true; + } + + // there's no directive negation, so always set to true + state.directive[state.tokens.curr.value] = + true; + + if (p.id === ";") { + advance(";"); + } + continue; + } + break; + } + } + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + * isfunc - true if block is a function body + * isfatarrow - true if its a body of a fat arrow function + * iscase - true if block is a switch case block + */ + function block( + ordinary, + stmt, + isfunc, + isfatarrow, + iscase, + ) { + var a, + b = inblock, + old_indent = indent, + m, + s = scope, + t, + line, + d; + + inblock = ordinary; + + if (!ordinary || !state.option.funcscope) + scope = Object.create(scope); + + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + + var metrics = funct["(metrics)"]; + metrics.nestedBlockDepth += 1; + metrics.verifyMaxNestedBlockDepthPerFunction(); + + if (state.tokens.next.id === "{") { + advance("{"); + + // create a new block scope + funct["(blockscope)"].stack(); + + line = state.tokens.curr.line; + if (state.tokens.next.id !== "}") { + indent += state.option.indent; + while ( + !ordinary && + state.tokens.next.from > indent + ) { + indent += state.option.indent; + } + + if (isfunc) { + m = {}; + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + directives(); + + if ( + state.option.strict && + funct["(context)"]["(global)"] + ) { + if ( + !m["use strict"] && + !state.directive["use strict"] + ) { + warning("E007"); + } + } + } + + a = statements(line); + + metrics.statementCount += a.length; + + if (isfunc) { + state.directive = m; + } + + indent -= state.option.indent; + if (line !== state.tokens.next.line) { + indentation(); + } + } else if (line !== state.tokens.next.line) { + indentation(); + } + advance("}", t); + + funct["(blockscope)"].unstack(); + + indent = old_indent; + } else if (!ordinary) { + if (isfunc) { + m = {}; + if ( + stmt && + !isfatarrow && + !state.option.inMoz(true) + ) { + error( + "W118", + state.tokens.curr, + "function closure expressions", + ); + } + + if (!stmt) { + for (d in state.directive) { + if (_.has(state.directive, d)) { + m[d] = state.directive[d]; + } + } + } + expression(10); + + if ( + state.option.strict && + funct["(context)"]["(global)"] + ) { + if ( + !m["use strict"] && + !state.directive["use strict"] + ) { + warning("E007"); + } + } + } else { + error( + "E021", + state.tokens.next, + "{", + state.tokens.next.value, + ); + } + } else { + // check to avoid let declaration not within a block + funct["(nolet)"] = true; + + if (!stmt || state.option.curly) { + warning( + "W116", + state.tokens.next, + "{", + state.tokens.next.value, + ); + } + + noreach = true; + indent += state.option.indent; + // test indentation only if statement is in new line + a = [ + statement( + state.tokens.next.line === + state.tokens.curr.line, + ), + ]; + indent -= state.option.indent; + noreach = false; + + delete funct["(nolet)"]; + } + // Don't clear and let it propagate out if it is "break", "return", or "throw" in switch case + if ( + !( + iscase && + ["break", "return", "throw"].indexOf( + funct["(verb)"], + ) != -1 + ) + ) { + funct["(verb)"] = null; + } + + if (!ordinary || !state.option.funcscope) scope = s; + inblock = b; + if ( + ordinary && + state.option.noempty && + (!a || a.length === 0) + ) { + warning("W035"); + } + metrics.nestedBlockDepth -= 1; + return a; + } + + function countMember(m) { + if ( + membersOnly && + typeof membersOnly[m] !== "boolean" + ) { + warning("W036", state.tokens.curr, m); + } + if (typeof member[m] === "number") { + member[m] += 1; + } else { + member[m] = 1; + } + } + + function note_implied(tkn) { + var name = tkn.value; + var desc = Object.getOwnPropertyDescriptor( + implied, + name, + ); + + if (!desc) implied[name] = [tkn.line]; + else desc.value.push(tkn.line); + } + + // Build the syntax table by declaring the syntactic elements of the language. + + type("(number)", function () { + return this; + }); + + type("(string)", function () { + return this; + }); + + state.syntax["(identifier)"] = { + type: "(identifier)", + lbp: 0, + identifier: true, + + nud: function () { + var v = this.value; + var s = scope[v]; + var f; + var block; + + if (typeof s === "function") { + // Protection against accidental inheritance. + s = undefined; + } else if ( + !funct["(blockscope)"].current.has(v) && + typeof s === "boolean" + ) { + f = funct; + funct = functions[0]; + addlabel(v, { type: "var" }); + s = funct; + funct = f; + } + + block = funct["(blockscope)"].getlabel(v); + + // The name is in scope and defined in the current function. + if (funct === s || block) { + // Change 'unused' to 'var', and reject labels. + // the name is in a block scope. + switch ( + block ? block[v]["(type)"] : funct[v] + ) { + case "unused": + if (block) + block[v]["(type)"] = "var"; + else funct[v] = "var"; + break; + case "unction": + if (block) + block[v]["(type)"] = "function"; + else funct[v] = "function"; + this["function"] = true; + break; + case "const": + setprop(funct, v, { + unused: false, + }); + break; + case "function": + this["function"] = true; + break; + case "label": + warning( + "W037", + state.tokens.curr, + v, + ); + break; + } + } else if (funct["(global)"]) { + // The name is not defined in the function. If we are in the global + // scope, then we have an undefined variable. + // + // Operators typeof and delete do not raise runtime errors even if + // the base object of a reference is null so no need to display warning + // if we're inside of typeof or delete. + + if (typeof predefined[v] !== "boolean") { + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if ( + !( + anonname === "typeof" || + anonname === "delete" + ) || + (state.tokens.next && + (state.tokens.next.value === + "." || + state.tokens.next.value === + "[")) + ) { + // if we're in a list comprehension, variables are declared + // locally and used before being defined. So we check + // the presence of the given variable in the comp array + // before declaring it undefined. + + if ( + !funct["(comparray)"].check(v) + ) { + isundef( + funct, + "W117", + state.tokens.curr, + v, + ); + } + } + } + + note_implied(state.tokens.curr); + } else { + // If the name is already defined in the current + // function, but not as outer, then there is a scope error. + + switch (funct[v]) { + case "closure": + case "function": + case "var": + case "unused": + warning( + "W038", + state.tokens.curr, + v, + ); + break; + case "label": + warning( + "W037", + state.tokens.curr, + v, + ); + break; + case "outer": + case "global": + break; + default: + // If the name is defined in an outer function, make an outer entry, + // and if it was unused, make it var. + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning( + "W039", + state.tokens.curr, + v, + ); + note_implied(state.tokens.curr); + } else if (typeof s !== "object") { + // Operators typeof and delete do not raise runtime errors even + // if the base object of a reference is null so no need to + // + // display warning if we're inside of typeof or delete. + // Attempting to subscript a null reference will throw an + // error, even within the typeof and delete operators + if ( + !( + anonname === "typeof" || + anonname === "delete" + ) || + (state.tokens.next && + (state.tokens.next + .value === "." || + state.tokens.next + .value === "[")) + ) { + isundef( + funct, + "W117", + state.tokens.curr, + v, + ); + } + funct[v] = true; + note_implied(state.tokens.curr); + } else { + switch (s[v]) { + case "function": + case "unction": + this["function"] = true; + s[v] = "closure"; + funct[v] = s["(global)"] + ? "global" + : "outer"; + break; + case "var": + case "unused": + s[v] = "closure"; + funct[v] = s["(global)"] + ? "global" + : "outer"; + break; + case "const": + setprop(s, v, { + unused: false, + }); + break; + case "closure": + funct[v] = s["(global)"] + ? "global" + : "outer"; + break; + case "label": + warning( + "W037", + state.tokens.curr, + v, + ); + } + } + } + } + return this; + }, + + led: function () { + error( + "E033", + state.tokens.next, + state.tokens.next.value, + ); + }, + }; + + type("(regexp)", function () { + return this; + }); + + // ECMAScript parser + + delim("(endline)"); + delim("(begin)"); + delim("(end)").reach = true; + delim("(error)").reach = true; + delim("}").reach = true; + delim(")"); + delim("]"); + delim('"').reach = true; + delim("'").reach = true; + delim(";"); + delim(":").reach = true; + delim("#"); + + reserve("else"); + reserve("case").reach = true; + reserve("catch"); + reserve("default").reach = true; + reserve("finally"); + reservevar("arguments", function (x) { + if ( + state.directive["use strict"] && + funct["(global)"] + ) { + warning("E008", x); + } + }); + reservevar("eval"); + reservevar("false"); + reservevar("Infinity"); + reservevar("null"); + reservevar("this", function (x) { + if ( + state.directive["use strict"] && + !state.option.validthis && + ((funct["(statement)"] && + funct["(name)"].charAt(0) > "Z") || + funct["(global)"]) + ) { + warning("W040", x); + } + }); + reservevar("true"); + reservevar("undefined"); + + assignop("=", "assign", 20); + assignop("+=", "assignadd", 20); + assignop("-=", "assignsub", 20); + assignop("*=", "assignmult", 20); + assignop("/=", "assigndiv", 20).nud = function () { + error("E014"); + }; + assignop("%=", "assignmod", 20); + + bitwiseassignop("&=", "assignbitand", 20); + bitwiseassignop("|=", "assignbitor", 20); + bitwiseassignop("^=", "assignbitxor", 20); + bitwiseassignop("<<=", "assignshiftleft", 20); + bitwiseassignop(">>=", "assignshiftright", 20); + bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); + infix( + ",", + function (left, that) { + var expr; + that.exprs = [left]; + if (!comma({ peek: true })) { + return that; + } + while (true) { + if (!(expr = expression(10))) { + break; + } + that.exprs.push(expr); + if ( + state.tokens.next.value !== "," || + !comma() + ) { + break; + } + } + return that; + }, + 10, + true, + ); + + infix( + "?", + function (left, that) { + increaseComplexityCount(); + that.left = left; + that.right = expression(10); + advance(":"); + that["else"] = expression(10); + return that; + }, + 30, + ); + + var orPrecendence = 40; + infix( + "||", + function (left, that) { + increaseComplexityCount(); + that.left = left; + that.right = expression(orPrecendence); + return that; + }, + orPrecendence, + ); + infix("&&", "and", 50); + bitwise("|", "bitor", 70); + bitwise("^", "bitxor", 80); + bitwise("&", "bitand", 90); + relation("==", function (left, right) { + var eqnull = + state.option.eqnull && + (left.value === "null" || + right.value === "null"); + + switch (true) { + case !eqnull && state.option.eqeqeq: + this.from = this.character; + warning("W116", this, "===", "=="); + break; + case isPoorRelation(left): + warning("W041", this, "===", left.value); + break; + case isPoorRelation(right): + warning("W041", this, "===", right.value); + break; + case isTypoTypeof(right, left): + warning("W122", this, right.value); + break; + case isTypoTypeof(left, right): + warning("W122", this, left.value); + break; + } + + return this; + }); + relation("===", function (left, right) { + if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("!=", function (left, right) { + var eqnull = + state.option.eqnull && + (left.value === "null" || + right.value === "null"); + + if (!eqnull && state.option.eqeqeq) { + this.from = this.character; + warning("W116", this, "!==", "!="); + } else if (isPoorRelation(left)) { + warning("W041", this, "!==", left.value); + } else if (isPoorRelation(right)) { + warning("W041", this, "!==", right.value); + } else if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("!==", function (left, right) { + if (isTypoTypeof(right, left)) { + warning("W122", this, right.value); + } else if (isTypoTypeof(left, right)) { + warning("W122", this, left.value); + } + return this; + }); + relation("<"); + relation(">"); + relation("<="); + relation(">="); + bitwise("<<", "shiftleft", 120); + bitwise(">>", "shiftright", 120); + bitwise(">>>", "shiftrightunsigned", 120); + infix("in", "in", 120); + infix("instanceof", "instanceof", 120); + infix( + "+", + function (left, that) { + var right = expression(130); + if ( + left && + right && + left.id === "(string)" && + right.id === "(string)" + ) { + left.value += right.value; + left.character = right.character; + if ( + !state.option.scripturl && + reg.javascriptURL.test(left.value) + ) { + warning("W050", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, + 130, + ); + prefix("+", "num"); + prefix("+++", function () { + warning("W007"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix( + "+++", + function (left) { + warning("W007"); + this.left = left; + this.right = expression(130); + return this; + }, + 130, + ); + infix("-", "sub", 130); + prefix("-", "neg"); + prefix("---", function () { + warning("W006"); + this.right = expression(150); + this.arity = "unary"; + return this; + }); + infix( + "---", + function (left) { + warning("W006"); + this.left = left; + this.right = expression(130); + return this; + }, + 130, + ); + infix("*", "mult", 140); + infix("/", "div", 140); + infix("%", "mod", 140); + + suffix("++", "postinc"); + prefix("++", "preinc"); + state.syntax["++"].exps = true; + + suffix("--", "postdec"); + prefix("--", "predec"); + state.syntax["--"].exps = true; + prefix("delete", function () { + var p = expression(10); + if (!p || (p.id !== "." && p.id !== "[")) { + warning("W051"); + } + this.first = p; + return this; + }).exps = true; + + prefix("~", function () { + if (state.option.bitwise) { + warning("W052", this, "~"); + } + expression(150); + return this; + }); + + prefix("...", function () { + if (!state.option.inESNext()) { + warning("W104", this, "spread/rest operator"); + } + if (!state.tokens.next.identifier) { + error( + "E030", + state.tokens.next, + state.tokens.next.value, + ); + } + expression(150); + return this; + }); + + prefix("!", function () { + this.right = expression(150); + this.arity = "unary"; + + if (!this.right) { + // '!' followed by nothing? Give up. + quit("E041", this.line || 0); + } + + if (bang[this.right.id] === true) { + warning("W018", this, "!"); + } + return this; + }); + + prefix("typeof", "typeof"); + prefix("new", function () { + var c = expression(155), + i; + if (c && c.id !== "function") { + if (c.identifier) { + c["new"] = true; + switch (c.value) { + case "Number": + case "String": + case "Boolean": + case "Math": + case "JSON": + warning( + "W053", + state.tokens.prev, + c.value, + ); + break; + case "Function": + if (!state.option.evil) { + warning("W054"); + } + break; + case "Date": + case "RegExp": + case "this": + break; + default: + if (c.id !== "function") { + i = c.value.substr(0, 1); + if ( + state.option.newcap && + (i < "A" || i > "Z") && + !_.has(global, c.value) + ) { + warning( + "W055", + state.tokens.curr, + ); + } + } + } + } else { + if ( + c.id !== "." && + c.id !== "[" && + c.id !== "(" + ) { + warning("W056", state.tokens.curr); + } + } + } else { + if (!state.option.supernew) + warning("W057", this); + } + adjacent(state.tokens.curr, state.tokens.next); + if ( + state.tokens.next.id !== "(" && + !state.option.supernew + ) { + warning( + "W058", + state.tokens.curr, + state.tokens.curr.value, + ); + } + this.first = c; + return this; + }); + state.syntax["new"].exps = true; + + prefix("void").exps = true; + + infix( + ".", + function (left, that) { + adjacent(state.tokens.prev, state.tokens.curr); + nobreak(); + var m = identifier(false, true); + + if (typeof m === "string") { + countMember(m); + } + + that.left = left; + that.right = m; + + if ( + m && + m === "hasOwnProperty" && + state.tokens.next.value === "=" + ) { + warning("W001"); + } + + if ( + left && + left.value === "arguments" && + (m === "callee" || m === "caller") + ) { + if (state.option.noarg) + warning("W059", left, m); + else if (state.directive["use strict"]) + error("E008"); + } else if ( + !state.option.evil && + left && + left.value === "document" && + (m === "write" || m === "writeln") + ) { + warning("W060", left); + } + + if ( + !state.option.evil && + (m === "eval" || m === "execScript") + ) { + warning("W061"); + } + + return that; + }, + 160, + true, + ); + + infix( + "(", + function (left, that) { + if ( + state.tokens.prev.id !== "}" && + state.tokens.prev.id !== ")" + ) { + nobreak( + state.tokens.prev, + state.tokens.curr, + ); + } + + nospace(); + if ( + state.option.immed && + left && + !left.immed && + left.id === "function" + ) { + warning("W062"); + } + + var n = 0; + var p = []; + + if (left) { + if (left.type === "(identifier)") { + if ( + left.value.match( + /^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/, + ) + ) { + if ( + "Number String Boolean Date Object".indexOf( + left.value, + ) === -1 + ) { + if (left.value === "Math") { + warning("W063", left); + } else if ( + state.option.newcap + ) { + warning("W064", left); + } + } + } + } + } + + if (state.tokens.next.id !== ")") { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")"); + nospace(state.tokens.prev, state.tokens.curr); + + if (typeof left === "object") { + if ( + state.option.inES3() && + left.value === "parseInt" && + n === 1 + ) { + warning("W065", state.tokens.curr); + } + if (!state.option.evil) { + if ( + left.value === "eval" || + left.value === "Function" || + left.value === "execScript" + ) { + warning("W061", left); + + if (p[0] && [0].id === "(string)") { + addInternalSrc( + left, + p[0].value, + ); + } + } else if ( + p[0] && + p[0].id === "(string)" && + (left.value === "setTimeout" || + left.value === "setInterval") + ) { + warning("W066", left); + addInternalSrc(left, p[0].value); + + // window.setTimeout/setInterval + } else if ( + p[0] && + p[0].id === "(string)" && + left.value === "." && + left.left.value === "window" && + (left.right === "setTimeout" || + left.right === "setInterval") + ) { + warning("W066", left); + addInternalSrc(left, p[0].value); + } + } + if ( + !left.identifier && + left.id !== "." && + left.id !== "[" && + left.id !== "(" && + left.id !== "&&" && + left.id !== "||" && + left.id !== "?" + ) { + warning("W067", left); + } + } + + that.left = left; + return that; + }, + 155, + true, + ).exps = true; + + prefix("(", function () { + nospace(); + var bracket, + brackets = []; + var pn, + pn1, + i = 0; + var ret; + var parens = 1; + + do { + pn = peek(i); + + if (pn.value === "(") { + parens += 1; + } else if (pn.value === ")") { + parens -= 1; + } + + i += 1; + pn1 = peek(i); + i += 1; + } while ( + !(parens === 0 && pn.value === ")") && + pn1.value !== "=>" && + pn1.value !== ";" && + pn1.type !== "(end)" + ); + + if (state.tokens.next.id === "function") { + state.tokens.next.immed = true; + } + + var exprs = []; + + if (state.tokens.next.id !== ")") { + for (;;) { + if ( + pn1.value === "=>" && + _.contains( + ["{", "["], + state.tokens.next.value, + ) + ) { + bracket = state.tokens.next; + bracket.left = + destructuringExpression(); + brackets.push(bracket); + for (var t in bracket.left) { + exprs.push(bracket.left[t].token); + } + } else { + exprs.push(expression(10)); + } + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + + advance(")", this); + nospace(state.tokens.prev, state.tokens.curr); + if ( + state.option.immed && + exprs[0] && + exprs[0].id === "function" + ) { + if ( + state.tokens.next.id !== "(" && + (state.tokens.next.id !== "." || + (peek().value !== "call" && + peek().value !== "apply")) + ) { + warning("W068", this); + } + } + + if (state.tokens.next.value === "=>") { + return exprs; + } + if (!exprs.length) { + return; + } + if (exprs.length > 1) { + ret = Object.create(state.syntax[","]); + ret.exprs = exprs; + } else { + ret = exprs[0]; + } + if (ret) { + ret.paren = true; + } + return ret; + }); + + application("=>"); + + infix( + "[", + function (left, that) { + nobreak(state.tokens.prev, state.tokens.curr); + nospace(); + var e = expression(10), + s; + if (e && e.type === "(string)") { + if ( + !state.option.evil && + (e.value === "eval" || + e.value === "execScript") + ) { + warning("W061", that); + } + + countMember(e.value); + if ( + !state.option.sub && + reg.identifier.test(e.value) + ) { + s = state.syntax[e.value]; + if (!s || !isReserved(s)) { + warning( + "W069", + state.tokens.prev, + e.value, + ); + } + } + } + advance("]", that); + + if ( + e && + e.value === "hasOwnProperty" && + state.tokens.next.value === "=" + ) { + warning("W001"); + } + + nospace(state.tokens.prev, state.tokens.curr); + that.left = left; + that.right = e; + return that; + }, + 160, + true, + ); + + function comprehensiveArrayExpression() { + var res = {}; + res.exps = true; + funct["(comparray)"].stack(); + + // Handle reversed for expressions, used in spidermonkey + var reversed = false; + if (state.tokens.next.value !== "for") { + reversed = true; + if (!state.option.inMoz(true)) { + warning( + "W116", + state.tokens.next, + "for", + state.tokens.next.value, + ); + } + funct["(comparray)"].setState("use"); + res.right = expression(10); + } + + advance("for"); + if (state.tokens.next.value === "each") { + advance("each"); + if (!state.option.inMoz(true)) { + warning( + "W118", + state.tokens.curr, + "for each", + ); + } + } + advance("("); + funct["(comparray)"].setState("define"); + res.left = expression(130); + if ( + _.contains( + ["in", "of"], + state.tokens.next.value, + ) + ) { + advance(); + } else { + error("E045", state.tokens.curr); + } + funct["(comparray)"].setState("generate"); + expression(10); + + advance(")"); + if (state.tokens.next.value === "if") { + advance("if"); + advance("("); + funct["(comparray)"].setState("filter"); + res.filter = expression(10); + advance(")"); + } + + if (!reversed) { + funct["(comparray)"].setState("use"); + res.right = expression(10); + } + + advance("]"); + funct["(comparray)"].unstack(); + return res; + } + + prefix( + "[", + function () { + var blocktype = lookupBlockType(true); + if (blocktype.isCompArray) { + if (!state.option.inESNext()) { + warning( + "W119", + state.tokens.curr, + "array comprehension", + ); + } + return comprehensiveArrayExpression(); + } else if ( + blocktype.isDestAssign && + !state.option.inESNext() + ) { + warning( + "W104", + state.tokens.curr, + "destructuring assignment", + ); + } + var b = + state.tokens.curr.line !== + state.tokens.next.line; + this.first = []; + if (b) { + indent += state.option.indent; + if ( + state.tokens.next.from === + indent + state.option.indent + ) { + indent += state.option.indent; + } + } + while (state.tokens.next.id !== "(end)") { + while (state.tokens.next.id === ",") { + if (!state.option.inES5()) + warning("W070"); + advance(","); + } + if (state.tokens.next.id === "]") { + break; + } + if ( + b && + state.tokens.curr.line !== + state.tokens.next.line + ) { + indentation(); + } + this.first.push(expression(10)); + if (state.tokens.next.id === ",") { + comma({ allowTrailing: true }); + if ( + state.tokens.next.id === "]" && + !state.option.inES5(true) + ) { + warning("W070", state.tokens.curr); + break; + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("]", this); + return this; + }, + 160, + ); + + function property_name() { + var id = optionalidentifier(false, true); + + if (!id) { + if (state.tokens.next.id === "(string)") { + id = state.tokens.next.value; + advance(); + } else if ( + state.tokens.next.id === "(number)" + ) { + id = state.tokens.next.value.toString(); + advance(); + } + } + + if (id === "hasOwnProperty") { + warning("W001"); + } + + return id; + } + + function functionparams(parsed) { + var curr, next; + var params = []; + var ident; + var tokens = []; + var t; + var pastDefault = false; + + if (parsed) { + if (Array.isArray(parsed)) { + for (var i in parsed) { + curr = parsed[i]; + if (_.contains(["{", "["], curr.id)) { + for (t in curr.left) { + t = tokens[t]; + if (t && t.id) { + params.push(t.id); + addlabel(t.id, { + type: "unused", + token: t.token, + }); + } + } + } else if (curr.value === "...") { + if (!state.option.inESNext()) { + warning( + "W104", + curr, + "spread/rest operator", + ); + } + continue; + } else if (curr.value !== ",") { + params.push(curr.value); + addlabel(curr.value, { + type: "unused", + token: curr, + }); + } + } + return params; + } else { + if (parsed.identifier === true) { + addlabel(parsed.value, { + type: "unused", + token: parsed, + }); + return [parsed]; + } + } + } + + next = state.tokens.next; + + advance("("); + nospace(); + + if (state.tokens.next.id === ")") { + advance(")"); + return; + } + + for (;;) { + if ( + _.contains(["{", "["], state.tokens.next.id) + ) { + tokens = destructuringExpression(); + for (t in tokens) { + t = tokens[t]; + if (t.id) { + params.push(t.id); + addlabel(t.id, { + type: "unused", + token: t.token, + }); + } + } + } else if (state.tokens.next.value === "...") { + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.next, + "spread/rest operator", + ); + } + advance("..."); + nospace(); + ident = identifier(true); + params.push(ident); + addlabel(ident, { + type: "unused", + token: state.tokens.curr, + }); + } else { + ident = identifier(true); + params.push(ident); + addlabel(ident, { + type: "unused", + token: state.tokens.curr, + }); + } + + // it is a syntax error to have a regular argument after a default argument + if (pastDefault) { + if (state.tokens.next.id !== "=") { + error("E051", state.tokens.current); + } + } + if (state.tokens.next.id === "=") { + if (!state.option.inESNext()) { + warning( + "W119", + state.tokens.next, + "default parameters", + ); + } + advance("="); + pastDefault = true; + expression(10); + } + if (state.tokens.next.id === ",") { + comma(); + } else { + advance(")", next); + nospace( + state.tokens.prev, + state.tokens.curr, + ); + return params; + } + } + } + + function setprop(funct, name, values) { + if (!funct["(properties)"][name]) { + funct["(properties)"][name] = { unused: false }; + } + + _.extend(funct["(properties)"][name], values); + } + + function getprop(funct, name, prop) { + if (!funct["(properties)"][name]) return null; + + return funct["(properties)"][name][prop] || null; + } + + function functor(name, token, scope, overwrites) { + var funct = { + "(name)": name, + "(breakage)": 0, + "(loopage)": 0, + "(scope)": scope, + "(tokens)": {}, + "(properties)": {}, + + "(catch)": false, + "(global)": false, + + "(line)": null, + "(character)": null, + "(metrics)": null, + "(statement)": null, + "(context)": null, + "(blockscope)": null, + "(comparray)": null, + "(generator)": null, + "(params)": null, + }; + + if (token) { + _.extend(funct, { + "(line)": token.line, + "(character)": token.character, + "(metrics)": createMetrics(token), + }); + } + + _.extend(funct, overwrites); + + if (funct["(context)"]) { + funct["(blockscope)"] = + funct["(context)"]["(blockscope)"]; + funct["(comparray)"] = + funct["(context)"]["(comparray)"]; + } + + return funct; + } + + function doFunction( + name, + statement, + generator, + fatarrowparams, + ) { + var f; + var oldOption = state.option; + var oldIgnored = state.ignored; + var oldScope = scope; + + state.option = Object.create(state.option); + state.ignored = Object.create(state.ignored); + scope = Object.create(scope); + + funct = functor( + name || '"' + anonname + '"', + state.tokens.next, + scope, + { + "(statement)": statement, + "(context)": funct, + "(generator)": generator ? true : null, + }, + ); + + f = funct; + state.tokens.curr.funct = funct; + + functions.push(funct); + + if (name) { + addlabel(name, { type: "function" }); + } + + funct["(params)"] = functionparams(fatarrowparams); + funct["(metrics)"].verifyMaxParametersPerFunction( + funct["(params)"], + ); + + // So we parse fat-arrow functions after we encounter =>. So basically + // doFunction is called with the left side of => as its last argument. + // This means that the parser, at that point, had already added its + // arguments to the undefs array and here we undo that. + + JSHINT.undefs = _.filter( + JSHINT.undefs, + function (item) { + return !_.contains( + _.union(fatarrowparams), + item[2], + ); + }, + ); + + block( + false, + true, + true, + fatarrowparams ? true : false, + ); + + if ( + !state.option.noyield && + generator && + funct["(generator)"] !== "yielded" + ) { + warning("W124", state.tokens.curr); + } + + funct["(metrics)"].verifyMaxStatementsPerFunction(); + funct["(metrics)"].verifyMaxComplexityPerFunction(); + funct["(unusedOption)"] = state.option.unused; + + scope = oldScope; + state.option = oldOption; + state.ignored = oldIgnored; + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = + state.tokens.curr.character; + + _.map(Object.keys(funct), function (key) { + if (key[0] === "(") return; + funct["(blockscope)"].unshadow(key); + }); + + funct = funct["(context)"]; + + return f; + } + + function createMetrics(functionStartToken) { + return { + statementCount: 0, + nestedBlockDepth: -1, + ComplexityCount: 1, + + verifyMaxStatementsPerFunction: function () { + if ( + state.option.maxstatements && + this.statementCount > + state.option.maxstatements + ) { + warning( + "W071", + functionStartToken, + this.statementCount, + ); + } + }, + + verifyMaxParametersPerFunction: function ( + params, + ) { + params = params || []; + + if ( + state.option.maxparams && + params.length > state.option.maxparams + ) { + warning( + "W072", + functionStartToken, + params.length, + ); + } + }, + + verifyMaxNestedBlockDepthPerFunction: + function () { + if ( + state.option.maxdepth && + this.nestedBlockDepth > 0 && + this.nestedBlockDepth === + state.option.maxdepth + 1 + ) { + warning( + "W073", + null, + this.nestedBlockDepth, + ); + } + }, + + verifyMaxComplexityPerFunction: function () { + var max = state.option.maxcomplexity; + var cc = this.ComplexityCount; + if (max && cc > max) { + warning("W074", functionStartToken, cc); + } + }, + }; + } + + function increaseComplexityCount() { + funct["(metrics)"].ComplexityCount += 1; + } + + // Parse assignments that were found instead of conditionals. + // For example: if (a = 1) { ... } + + function checkCondAssignment(expr) { + var id, paren; + if (expr) { + id = expr.id; + paren = expr.paren; + if ( + id === "," && + (expr = expr.exprs[expr.exprs.length - 1]) + ) { + id = expr.id; + paren = paren || expr.paren; + } + } + switch (id) { + case "=": + case "+=": + case "-=": + case "*=": + case "%=": + case "&=": + case "|=": + case "^=": + case "/=": + if (!paren && !state.option.boss) { + warning("W084"); + } + } + } + + (function (x) { + x.nud = function (isclassdef) { + var b, f, i, p, t, g; + var props = {}; // All properties, including accessors + var tag = ""; + + function saveProperty(name, tkn) { + if (props[name] && _.has(props, name)) + warning("W075", state.tokens.next, i); + else props[name] = {}; + + props[name].basic = true; + props[name].basictkn = tkn; + } + + function saveSetter(name, tkn) { + if (props[name] && _.has(props, name)) { + if ( + props[name].basic || + props[name].setter + ) + warning( + "W075", + state.tokens.next, + i, + ); + } else { + props[name] = {}; + } + + props[name].setter = true; + props[name].setterToken = tkn; + } + + function saveGetter(name) { + if (props[name] && _.has(props, name)) { + if ( + props[name].basic || + props[name].getter + ) + warning( + "W075", + state.tokens.next, + i, + ); + } else { + props[name] = {}; + } + + props[name].getter = true; + props[name].getterToken = state.tokens.curr; + } + + b = + state.tokens.curr.line !== + state.tokens.next.line; + if (b) { + indent += state.option.indent; + if ( + state.tokens.next.from === + indent + state.option.indent + ) { + indent += state.option.indent; + } + } + + for (;;) { + if (state.tokens.next.id === "}") { + break; + } + + if (b) { + indentation(); + } + + if ( + isclassdef && + state.tokens.next.value === "static" + ) { + advance("static"); + tag = "static "; + } + + if ( + state.tokens.next.value === "get" && + peek().id !== ":" + ) { + advance("get"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + + // ES6 allows for get() {...} and set() {...} method + // definition shorthand syntax, so we don't produce an error + // if the esnext option is enabled. + if (!i && !state.option.inESNext()) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error( + "E049", + state.tokens.next, + "class getter method", + i, + ); + } + + // We don't want to save this getter unless it's an actual getter + // and not an ES6 concise method + if (i) { + saveGetter(tag + i); + } + + t = state.tokens.next; + adjacent( + state.tokens.curr, + state.tokens.next, + ); + f = doFunction(); + p = f["(params)"]; + + // Don't warn about getter/setter pairs if this is an ES6 concise method + if (i && p) { + warning("W076", t, p[0], i); + } + + adjacent( + state.tokens.curr, + state.tokens.next, + ); + } else if ( + state.tokens.next.value === "set" && + peek().id !== ":" + ) { + advance("set"); + + if (!state.option.inES5(!isclassdef)) { + error("E034"); + } + + i = property_name(); + + // ES6 allows for get() {...} and set() {...} method + // definition shorthand syntax, so we don't produce an error + // if the esnext option is enabled. + if (!i && !state.option.inESNext()) { + error("E035"); + } + + // It is a Syntax Error if PropName of MethodDefinition is + // "constructor" and SpecialMethod of MethodDefinition is true. + if (isclassdef && i === "constructor") { + error( + "E049", + state.tokens.next, + "class setter method", + i, + ); + } + + // We don't want to save this getter unless it's an actual getter + // and not an ES6 concise method + if (i) { + saveSetter( + tag + i, + state.tokens.next, + ); + } + + t = state.tokens.next; + adjacent( + state.tokens.curr, + state.tokens.next, + ); + f = doFunction(); + p = f["(params)"]; + + // Don't warn about getter/setter pairs if this is an ES6 concise method + if (i && (!p || p.length !== 1)) { + warning("W077", t, i); + } + } else { + g = false; + if ( + state.tokens.next.value === "*" && + state.tokens.next.type === + "(punctuator)" + ) { + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.next, + "generator functions", + ); + } + advance("*"); + g = true; + } + i = property_name(); + saveProperty( + tag + i, + state.tokens.next, + ); + + if (typeof i !== "string") { + break; + } + + if (state.tokens.next.value === "(") { + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.curr, + "concise methods", + ); + } + doFunction(i, undefined, g); + } else if (!isclassdef) { + advance(":"); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + expression(10); + } + } + // It is a Syntax Error if PropName of MethodDefinition is "prototype". + if (isclassdef && i === "prototype") { + error( + "E049", + state.tokens.next, + "class method", + i, + ); + } + + countMember(i); + if (isclassdef) { + tag = ""; + continue; + } + if (state.tokens.next.id === ",") { + comma({ + allowTrailing: true, + property: true, + }); + if (state.tokens.next.id === ",") { + warning("W070", state.tokens.curr); + } else if ( + state.tokens.next.id === "}" && + !state.option.inES5(true) + ) { + warning("W070", state.tokens.curr); + } + } else { + break; + } + } + if (b) { + indent -= state.option.indent; + indentation(); + } + advance("}", this); + + // Check for lonely setters if in the ES5 mode. + if (state.option.inES5()) { + for (var name in props) { + if ( + _.has(props, name) && + props[name].setter && + !props[name].getter + ) { + warning( + "W078", + props[name].setterToken, + ); + } + } + } + return this; + }; + x.fud = function () { + error("E036", state.tokens.curr); + }; + })(delim("{")); + + function destructuringExpression() { + var id, ids; + var identifiers = []; + if (!state.option.inESNext()) { + warning( + "W104", + state.tokens.curr, + "destructuring expression", + ); + } + var nextInnerDE = function () { + var ident; + if ( + _.contains( + ["[", "{"], + state.tokens.next.value, + ) + ) { + ids = destructuringExpression(); + for (var id in ids) { + id = ids[id]; + identifiers.push({ + id: id.id, + token: id.token, + }); + } + } else if (state.tokens.next.value === ",") { + identifiers.push({ + id: null, + token: state.tokens.curr, + }); + } else if (state.tokens.next.value === "(") { + advance("("); + nextInnerDE(); + advance(")"); + } else { + ident = identifier(); + if (ident) + identifiers.push({ + id: ident, + token: state.tokens.curr, + }); + } + }; + if (state.tokens.next.value === "[") { + advance("["); + nextInnerDE(); + while (state.tokens.next.value !== "]") { + advance(","); + nextInnerDE(); + } + advance("]"); + } else if (state.tokens.next.value === "{") { + advance("{"); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ + id: id, + token: state.tokens.curr, + }); + } + while (state.tokens.next.value !== "}") { + advance(","); + id = identifier(); + if (state.tokens.next.value === ":") { + advance(":"); + nextInnerDE(); + } else { + identifiers.push({ + id: id, + token: state.tokens.curr, + }); + } + } + advance("}"); + } + return identifiers; + } + + function destructuringExpressionMatch(tokens, value) { + var first = value.first; + + if (!first) return; + + _.zip( + tokens, + Array.isArray(first) ? first : [first], + ).forEach(function (val) { + var token = val[0]; + var value = val[1]; + + if (token && value) token.first = value; + else if (token && token.first && !value) + warning( + "W080", + token.first, + token.first.value, + ); + }); + } + + var conststatement = stmt("const", function (prefix) { + var tokens; + var value; + var lone; // State variable to know if it is a lone identifier, or a destructuring statement. + + if (!state.option.inESNext()) + warning("W104", state.tokens.curr, "const"); + + this.first = []; + for (;;) { + var names = []; + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if ( + _.contains( + ["{", "["], + state.tokens.next.value, + ) + ) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ + { + id: identifier(), + token: state.tokens.curr, + }, + ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if (funct[t.id] === "const") { + warning("E011", null, t.id); + } + if ( + funct["(global)"] && + predefined[t.id] === false + ) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, { + token: t.token, + type: "const", + unused: true, + }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id !== "=") { + warning( + "E012", + state.tokens.curr, + state.tokens.curr.value, + ); + } + + if (state.tokens.next.id === "=") { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("="); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if (state.tokens.next.id === "undefined") { + warning( + "W080", + state.tokens.prev, + state.tokens.prev.value, + ); + } + if ( + peek(0).id === "=" && + state.tokens.next.identifier + ) { + warning( + "W120", + state.tokens.next, + state.tokens.next.value, + ); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch( + names, + value, + ); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + + conststatement.exps = true; + var varstatement = stmt("var", function (prefix) { + // JavaScript does not have block scope. It only has function scope. So, + // declaring a variable in a block can have unexpected consequences. + var tokens, lone, value; + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if ( + _.contains( + ["{", "["], + state.tokens.next.value, + ) + ) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ + { + id: identifier(), + token: state.tokens.curr, + }, + ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if ( + state.option.inESNext() && + funct[t.id] === "const" + ) { + warning("E011", null, t.id); + } + if ( + funct["(global)"] && + predefined[t.id] === false + ) { + warning("W079", t.token, t.id); + } + if (t.id) { + addlabel(t.id, { + type: "unused", + token: t.token, + }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("="); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if (state.tokens.next.id === "undefined") { + warning( + "W080", + state.tokens.prev, + state.tokens.prev.value, + ); + } + if ( + peek(0).id === "=" && + state.tokens.next.identifier + ) { + warning( + "W120", + state.tokens.next, + state.tokens.next.value, + ); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch( + names, + value, + ); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + return this; + }); + varstatement.exps = true; + + var letstatement = stmt("let", function (prefix) { + var tokens, lone, value, letblock; + + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "let"); + } + + if (state.tokens.next.value === "(") { + if (!state.option.inMoz(true)) { + warning( + "W118", + state.tokens.next, + "let block", + ); + } + advance("("); + funct["(blockscope)"].stack(); + letblock = true; + } else if (funct["(nolet)"]) { + error("E048", state.tokens.curr); + } + + if (funct["(onevar)"] && state.option.onevar) { + warning("W081"); + } else if (!funct["(global)"]) { + funct["(onevar)"] = true; + } + + this.first = []; + for (;;) { + var names = []; + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if ( + _.contains( + ["{", "["], + state.tokens.next.value, + ) + ) { + tokens = destructuringExpression(); + lone = false; + } else { + tokens = [ + { + id: identifier(), + token: state.tokens.curr.value, + }, + ]; + lone = true; + } + for (var t in tokens) { + if (tokens.hasOwnProperty(t)) { + t = tokens[t]; + if ( + state.option.inESNext() && + funct[t.id] === "const" + ) { + warning("E011", null, t.id); + } + if ( + funct["(global)"] && + predefined[t.id] === false + ) { + warning("W079", t.token, t.id); + } + if (t.id && !funct["(nolet)"]) { + addlabel(t.id, { + type: "unused", + token: t.token, + islet: true, + }); + names.push(t.token); + } + } + } + if (prefix) { + break; + } + + this.first = this.first.concat(names); + + if (state.tokens.next.id === "=") { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("="); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + if (state.tokens.next.id === "undefined") { + warning( + "W080", + state.tokens.prev, + state.tokens.prev.value, + ); + } + if ( + peek(0).id === "=" && + state.tokens.next.identifier + ) { + warning( + "W120", + state.tokens.next, + state.tokens.next.value, + ); + } + value = expression(10); + if (lone) { + tokens[0].first = value; + } else { + destructuringExpressionMatch( + names, + value, + ); + } + } + + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + if (letblock) { + advance(")"); + block(true, true); + this.block = true; + funct["(blockscope)"].unstack(); + } + + return this; + }); + letstatement.exps = true; + + blockstmt("class", function () { + return classdef.call(this, true); + }); + + function classdef(stmt) { + /*jshint validthis:true */ + if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "class"); + } + if (stmt) { + // BindingIdentifier + this.name = identifier(); + addlabel(this.name, { + type: "unused", + token: state.tokens.curr, + }); + } else if ( + state.tokens.next.identifier && + state.tokens.next.value !== "extends" + ) { + // BindingIdentifier(opt) + this.name = identifier(); + } + classtail(this); + return this; + } + + function classtail(c) { + var strictness = state.directive["use strict"]; + + // ClassHeritage(opt) + if (state.tokens.next.value === "extends") { + advance("extends"); + c.heritage = expression(10); + } + + // A ClassBody is always strict code. + state.directive["use strict"] = true; + advance("{"); + // ClassBody(opt) + c.body = state.syntax["{"].nud(true); + state.directive["use strict"] = strictness; + } + + blockstmt("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + advance("*"); + if (state.option.inESNext(true)) { + generator = true; + } else { + warning( + "W119", + state.tokens.curr, + "function*", + ); + } + } + if (inblock) { + warning("W082", state.tokens.curr); + } + var i = identifier(); + if (funct[i] === "const") { + warning("E011", null, i); + } + adjacent(state.tokens.curr, state.tokens.next); + addlabel(i, { + type: "unction", + token: state.tokens.curr, + }); + + doFunction(i, { statement: true }, generator); + if ( + state.tokens.next.id === "(" && + state.tokens.next.line === + state.tokens.curr.line + ) { + error("E039"); + } + return this; + }); + + prefix("function", function () { + var generator = false; + if (state.tokens.next.value === "*") { + if (!state.option.inESNext()) { + warning( + "W119", + state.tokens.curr, + "function*", + ); + } + advance("*"); + generator = true; + } + var i = optionalidentifier(); + if (i || state.option.gcl) { + adjacent(state.tokens.curr, state.tokens.next); + } else { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + } + doFunction(i, undefined, generator); + if (!state.option.loopfunc && funct["(loopage)"]) { + warning("W083"); + } + return this; + }); + + blockstmt("if", function () { + var t = state.tokens.next; + increaseComplexityCount(); + state.condition = true; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + state.condition = false; + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + if (state.tokens.next.id === "else") { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("else"); + if ( + state.tokens.next.id === "if" || + state.tokens.next.id === "switch" + ) { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt("try", function () { + var b; + + function doCatch() { + var oldScope = scope; + var e; + + advance("catch"); + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("("); + + scope = Object.create(oldScope); + + e = state.tokens.next.value; + if (state.tokens.next.type !== "(identifier)") { + e = null; + warning("E030", state.tokens.next, e); + } + + advance(); + + funct = functor( + "(catch)", + state.tokens.next, + scope, + { + "(context)": funct, + "(breakage)": funct["(breakage)"], + "(loopage)": funct["(loopage)"], + "(statement)": false, + "(catch)": true, + }, + ); + + if (e) { + addlabel(e, { type: "exception" }); + } + + if (state.tokens.next.value === "if") { + if (!state.option.inMoz(true)) { + warning( + "W118", + state.tokens.curr, + "catch filter", + ); + } + advance("if"); + expression(0); + } + + advance(")"); + + state.tokens.curr.funct = funct; + functions.push(funct); + + block(false); + + scope = oldScope; + + funct["(last)"] = state.tokens.curr.line; + funct["(lastcharacter)"] = + state.tokens.curr.character; + funct = funct["(context)"]; + } + + block(true); + + while (state.tokens.next.id === "catch") { + increaseComplexityCount(); + if (b && !state.option.inMoz(true)) { + warning( + "W118", + state.tokens.next, + "multiple catch blocks", + ); + } + doCatch(); + b = true; + } + + if (state.tokens.next.id === "finally") { + advance("finally"); + block(true); + return; + } + + if (!b) { + error( + "E021", + state.tokens.next, + "catch", + state.tokens.next.value, + ); + } + + return this; + }); + + blockstmt("while", function () { + var t = state.tokens.next; + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }).labelled = true; + + blockstmt("with", function () { + var t = state.tokens.next; + if (state.directive["use strict"]) { + error("E010", state.tokens.curr); + } else if (!state.option.withstmt) { + warning("W085", state.tokens.curr); + } + + advance("("); + nonadjacent(this, t); + nospace(); + expression(0); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + + return this; + }); + + blockstmt("switch", function () { + var t = state.tokens.next; + var g = false; + var noindent = false; + + funct["(breakage)"] += 1; + advance("("); + nonadjacent(this, t); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + nonadjacent(state.tokens.curr, state.tokens.next); + t = state.tokens.next; + advance("{"); + nonadjacent(state.tokens.curr, state.tokens.next); + + if (state.tokens.next.from === indent) + noindent = true; + + if (!noindent) indent += state.option.indent; + + this.cases = []; + + for (;;) { + switch (state.tokens.next.id) { + case "case": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "case": + case "continue": + case "return": + case "switch": + case "throw": + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if ( + !reg.fallsThrough.test( + state.lines[ + state.tokens.next + .line - 2 + ], + ) + ) { + warning( + "W086", + state.tokens.curr, + "case", + ); + } + } + indentation(); + advance("case"); + this.cases.push(expression(20)); + increaseComplexityCount(); + g = true; + advance(":"); + funct["(verb)"] = "case"; + break; + case "default": + switch (funct["(verb)"]) { + case "yield": + case "break": + case "continue": + case "return": + case "throw": + break; + default: + // Do not display a warning if 'default' is the first statement or if + // there is a special /* falls through */ comment. + if (this.cases.length) { + if ( + !reg.fallsThrough.test( + state.lines[ + state.tokens + .next.line - + 2 + ], + ) + ) { + warning( + "W086", + state.tokens.curr, + "default", + ); + } + } + } + indentation(); + advance("default"); + g = true; + advance(":"); + break; + case "}": + if (!noindent) + indent -= state.option.indent; + indentation(); + advance("}", t); + funct["(breakage)"] -= 1; + funct["(verb)"] = undefined; + return; + case "(end)": + error("E023", state.tokens.next, "}"); + return; + default: + indent += state.option.indent; + if (g) { + switch (state.tokens.curr.id) { + case ",": + error("E040"); + return; + case ":": + g = false; + statements(); + break; + default: + error( + "E025", + state.tokens.curr, + ); + return; + } + } else { + if (state.tokens.curr.id === ":") { + advance(":"); + error( + "E024", + state.tokens.curr, + ":", + ); + statements(); + } else { + error( + "E021", + state.tokens.next, + "case", + state.tokens.next.value, + ); + return; + } + } + indent -= state.option.indent; + } + } + }).labelled = true; + + stmt("debugger", function () { + if (!state.option.debug) { + warning("W087", this); + } + return this; + }).exps = true; + + (function () { + var x = stmt("do", function () { + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + + this.first = block(true, true); + advance("while"); + var t = state.tokens.next; + nonadjacent(state.tokens.curr, t); + advance("("); + nospace(); + checkCondAssignment(expression(0)); + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + })(); + + blockstmt("for", function () { + var s, + t = state.tokens.next; + var letscope = false; + var foreachtok = null; + + if (t.value === "each") { + foreachtok = t; + advance("each"); + if (!state.option.inMoz(true)) { + warning( + "W118", + state.tokens.curr, + "for each", + ); + } + } + + funct["(breakage)"] += 1; + funct["(loopage)"] += 1; + increaseComplexityCount(); + advance("("); + nonadjacent(this, t); + nospace(); + + // what kind of for(â€Ļ) statement it is? for(â€Ļofâ€Ļ)? for(â€Ļinâ€Ļ)? for(â€Ļ;â€Ļ;â€Ļ)? + var nextop; // contains the token of the "in" or "of" operator + var i = 0; + var inof = ["in", "of"]; + do { + nextop = peek(i); + ++i; + } while ( + !_.contains(inof, nextop.value) && + nextop.value !== ";" && + nextop.type !== "(end)" + ); + + // if we're in a for (â€Ļ in|of â€Ļ) statement + if (_.contains(inof, nextop.value)) { + if ( + !state.option.inESNext() && + nextop.value === "of" + ) { + error("W104", nextop, "for of"); + } + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call( + state.syntax["var"].fud, + true, + ); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call( + state.syntax["let"].fud, + true, + ); + } else { + switch (funct[state.tokens.next.value]) { + case "unused": + funct[state.tokens.next.value] = + "var"; + break; + case "var": + break; + default: + if ( + !funct["(blockscope)"].getlabel( + state.tokens.next.value, + ) + ) + warning( + "W088", + state.tokens.next, + state.tokens.next.value, + ); + } + advance(); + } + advance(nextop.value); + expression(20); + advance(")", t); + s = block(true, true); + if ( + state.option.forin && + s && + (s.length > 1 || + typeof s[0] !== "object" || + s[0].value !== "if") + ) { + warning("W089", this); + } + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + } else { + if (foreachtok) { + error("E045", foreachtok); + } + if (state.tokens.next.id !== ";") { + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call( + state.syntax["var"].fud, + ); + } else if (state.tokens.next.id === "let") { + advance("let"); + // create a new block scope + letscope = true; + funct["(blockscope)"].stack(); + state.syntax["let"].fud.call( + state.syntax["let"].fud, + ); + } else { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id !== ";") { + checkCondAssignment(expression(0)); + } + nolinebreak(state.tokens.curr); + advance(";"); + if (state.tokens.next.id === ";") { + error("E021", state.tokens.next, ")", ";"); + } + if (state.tokens.next.id !== ")") { + for (;;) { + expression(0, "for"); + if (state.tokens.next.id !== ",") { + break; + } + comma(); + } + } + advance(")", t); + nospace(state.tokens.prev, state.tokens.curr); + block(true, true); + funct["(breakage)"] -= 1; + funct["(loopage)"] -= 1; + } + // unstack loop blockscope + if (letscope) { + funct["(blockscope)"].unstack(); + } + return this; + }).labelled = true; + + stmt("break", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) nolinebreak(this); + + if ( + state.tokens.next.id !== ";" && + !state.tokens.next.reach + ) { + if ( + state.tokens.curr.line === + state.tokens.next.line + ) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } + reachable("break"); + return this; + }).exps = true; + + stmt("continue", function () { + var v = state.tokens.next.value; + + if (funct["(breakage)"] === 0) + warning("W052", state.tokens.next, this.value); + + if (!state.option.asi) nolinebreak(this); + + if ( + state.tokens.next.id !== ";" && + !state.tokens.next.reach + ) { + if ( + state.tokens.curr.line === + state.tokens.next.line + ) { + if (funct[v] !== "label") { + warning("W090", state.tokens.next, v); + } else if (scope[v] !== funct) { + warning("W091", state.tokens.next, v); + } + this.first = state.tokens.next; + advance(); + } + } else if (!funct["(loopage)"]) { + warning("W052", state.tokens.next, this.value); + } + reachable("continue"); + return this; + }).exps = true; + + stmt("return", function () { + if (this.line === state.tokens.next.line) { + if ( + state.tokens.next.id !== ";" && + !state.tokens.next.reach + ) { + nonadjacent( + state.tokens.curr, + state.tokens.next, + ); + this.first = expression(0); + + if ( + this.first && + this.first.type === "(punctuator)" && + this.first.value === "=" && + !this.first.paren && + !state.option.boss + ) { + warningAt( + "W093", + this.first.line, + this.first.character, + ); + } + } + } else { + if ( + state.tokens.next.type === "(punctuator)" && + ["[", "{", "+", "-"].indexOf( + state.tokens.next.value, + ) > -1 + ) { + nolinebreak(this); // always warn (Line breaking error) + } + } + reachable("return"); + return this; + }).exps = true; + + (function (x) { + x.exps = true; + x.lbp = 25; + })( + prefix("yield", function () { + var prev = state.tokens.prev; + if ( + state.option.inESNext(true) && + !funct["(generator)"] + ) { + error("E046", state.tokens.curr, "yield"); + } else if (!state.option.inESNext()) { + warning("W104", state.tokens.curr, "yield"); + } + funct["(generator)"] = "yielded"; + if ( + this.line === state.tokens.next.line || + !state.option.inMoz(true) + ) { + if ( + state.tokens.next.id !== ";" && + !state.tokens.next.reach && + state.tokens.next.nud + ) { + nobreaknonadjacent( + state.tokens.curr, + state.tokens.next, + ); + this.first = expression(10); + + if ( + this.first.type === + "(punctuator)" && + this.first.value === "=" && + !this.first.paren && + !state.option.boss + ) { + warningAt( + "W093", + this.first.line, + this.first.character, + ); + } + } + + if ( + state.option.inMoz(true) && + state.tokens.next.id !== ")" && + (prev.lbp > 30 || + (!prev.assign && !isEndOfExpr()) || + prev.id === "yield") + ) { + error("E050", this); + } + } else if (!state.option.asi) { + nolinebreak(this); // always warn (Line breaking error) + } + return this; + }), + ); + + stmt("throw", function () { + nolinebreak(this); + nonadjacent(state.tokens.curr, state.tokens.next); + this.first = expression(20); + reachable("throw"); + return this; + }).exps = true; + + stmt("import", function () { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "import"); + } + + if (state.tokens.next.identifier) { + this.name = identifier(); + addlabel(this.name, { + type: "unused", + token: state.tokens.curr, + }); + } else { + advance("{"); + for (;;) { + var importName; + if (state.tokens.next.type === "default") { + importName = "default"; + advance("default"); + } else { + importName = identifier(); + } + if (state.tokens.next.value === "as") { + advance("as"); + importName = identifier(); + } + addlabel(importName, { + type: "unused", + token: state.tokens.curr, + }); + + if (state.tokens.next.value === ",") { + advance(","); + } else if ( + state.tokens.next.value === "}" + ) { + advance("}"); + break; + } else { + error( + "E024", + state.tokens.next, + state.tokens.next.value, + ); + break; + } + } + } + + advance("from"); + advance("(string)"); + return this; + }).exps = true; + + stmt("export", function () { + if (!state.option.inESNext()) { + warning("W119", state.tokens.curr, "export"); + } + + if (state.tokens.next.type === "default") { + advance("default"); + if ( + state.tokens.next.id === "function" || + state.tokens.next.id === "class" + ) { + this.block = true; + } + this.exportee = expression(10); + + return this; + } + + if (state.tokens.next.value === "{") { + advance("{"); + for (;;) { + identifier(); + + if (state.tokens.next.value === ",") { + advance(","); + } else if ( + state.tokens.next.value === "}" + ) { + advance("}"); + break; + } else { + error( + "E024", + state.tokens.next, + state.tokens.next.value, + ); + break; + } + } + return this; + } + + if (state.tokens.next.id === "var") { + advance("var"); + state.syntax["var"].fud.call( + state.syntax["var"].fud, + ); + } else if (state.tokens.next.id === "let") { + advance("let"); + state.syntax["let"].fud.call( + state.syntax["let"].fud, + ); + } else if (state.tokens.next.id === "const") { + advance("const"); + state.syntax["const"].fud.call( + state.syntax["const"].fud, + ); + } else if (state.tokens.next.id === "function") { + this.block = true; + advance("function"); + state.syntax["function"].fud(); + } else if (state.tokens.next.id === "class") { + this.block = true; + advance("class"); + state.syntax["class"].fud(); + } else { + error( + "E024", + state.tokens.next, + state.tokens.next.value, + ); + } + + return this; + }).exps = true; + + // Future Reserved Words + + FutureReservedWord("abstract"); + FutureReservedWord("boolean"); + FutureReservedWord("byte"); + FutureReservedWord("char"); + FutureReservedWord("class", { + es5: true, + nud: classdef, + }); + FutureReservedWord("double"); + FutureReservedWord("enum", { es5: true }); + FutureReservedWord("export", { es5: true }); + FutureReservedWord("extends", { es5: true }); + FutureReservedWord("final"); + FutureReservedWord("float"); + FutureReservedWord("goto"); + FutureReservedWord("implements", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("import", { es5: true }); + FutureReservedWord("int"); + FutureReservedWord("interface", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("long"); + FutureReservedWord("native"); + FutureReservedWord("package", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("private", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("protected", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("public", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("short"); + FutureReservedWord("static", { + es5: true, + strictOnly: true, + }); + FutureReservedWord("super", { es5: true }); + FutureReservedWord("synchronized"); + FutureReservedWord("throws"); + FutureReservedWord("transient"); + FutureReservedWord("volatile"); + + // this function is used to determine whether a squarebracket or a curlybracket + // expression is a comprehension array, destructuring assignment or a json value. + + var lookupBlockType = function () { + var pn, pn1; + var i = -1; + var bracketStack = 0; + var ret = {}; + if (_.contains(["[", "{"], state.tokens.curr.value)) + bracketStack += 1; + do { + pn = i === -1 ? state.tokens.next : peek(i); + pn1 = peek(i + 1); + i = i + 1; + if (_.contains(["[", "{"], pn.value)) { + bracketStack += 1; + } else if (_.contains(["]", "}"], pn.value)) { + bracketStack -= 1; + } + if ( + pn.identifier && + pn.value === "for" && + bracketStack === 1 + ) { + ret.isCompArray = true; + ret.notJson = true; + break; + } + if ( + _.contains(["}", "]"], pn.value) && + pn1.value === "=" && + bracketStack === 0 + ) { + ret.isDestAssign = true; + ret.notJson = true; + break; + } + if (pn.value === ";") { + ret.isBlock = true; + ret.notJson = true; + } + } while ( + bracketStack > 0 && + pn.id !== "(end)" && + i < 15 + ); + return ret; + }; + + // Check whether this function has been reached for a destructuring assign with undeclared values + function destructuringAssignOrJsonValue() { + // lookup for the assignment (esnext only) + // if it has semicolons, it is a block, so go parse it as a block + // or it's not a block, but there are assignments, check for undeclared variables + + var block = lookupBlockType(); + if (block.notJson) { + if ( + !state.option.inESNext() && + block.isDestAssign + ) { + warning( + "W104", + state.tokens.curr, + "destructuring assignment", + ); + } + statements(); + // otherwise parse json value + } else { + state.option.laxbreak = true; + state.jsonMode = true; + jsonValue(); + } + } + + // array comprehension parsing function + // parses and defines the three states of the list comprehension in order + // to avoid defining global variables, but keeping them to the list comprehension scope + // only. The order of the states are as follows: + // * "use" which will be the returned iterative part of the list comprehension + // * "define" which will define the variables local to the list comprehension + // * "filter" which will help filter out values + + var arrayComprehension = function () { + var CompArray = function () { + this.mode = "use"; + this.variables = []; + }; + var _carrays = []; + var _current; + function declare(v) { + var l = _current.variables.filter( + function (elt) { + // if it has, change its undef state + if (elt.value === v) { + elt.undef = false; + return v; + } + }, + ).length; + return l !== 0; + } + function use(v) { + var l = _current.variables.filter( + function (elt) { + // and if it has been defined + if (elt.value === v && !elt.undef) { + if (elt.unused === true) { + elt.unused = false; + } + return v; + } + }, + ).length; + // otherwise we warn about it + return l === 0; + } + return { + stack: function () { + _current = new CompArray(); + _carrays.push(_current); + }, + unstack: function () { + _current.variables.filter(function (v) { + if (v.unused) + warning("W098", v.token, v.value); + if (v.undef) + isundef( + v.funct, + "W117", + v.token, + v.value, + ); + }); + _carrays.splice(-1, 1); + _current = _carrays[_carrays.length - 1]; + }, + setState: function (s) { + if ( + _.contains( + [ + "use", + "define", + "generate", + "filter", + ], + s, + ) + ) + _current.mode = s; + }, + check: function (v) { + if (!_current) { + return; + } + // When we are in "use" state of the list comp, we enqueue that var + if (_current && _current.mode === "use") { + if (use(v)) { + _current.variables.push({ + funct: funct, + token: state.tokens.curr, + value: v, + undef: true, + unused: false, + }); + } + return true; + // When we are in "define" state of the list comp, + } else if ( + _current && + _current.mode === "define" + ) { + // check if the variable has been used previously + if (!declare(v)) { + _current.variables.push({ + funct: funct, + token: state.tokens.curr, + value: v, + undef: false, + unused: true, + }); + } + return true; + // When we are in the "generate" state of the list comp, + } else if ( + _current && + _current.mode === "generate" + ) { + isundef( + funct, + "W117", + state.tokens.curr, + v, + ); + return true; + // When we are in "filter" state, + } else if ( + _current && + _current.mode === "filter" + ) { + // we check whether current variable has been declared + if (use(v)) { + // if not we warn about it + isundef( + funct, + "W117", + state.tokens.curr, + v, + ); + } + return true; + } + return false; + }, + }; + }; + + // Parse JSON + + function jsonValue() { + function jsonObject() { + var o = {}, + t = state.tokens.next; + advance("{"); + if (state.tokens.next.id !== "}") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error( + "E026", + state.tokens.next, + t.line, + ); + } else if ( + state.tokens.next.id === "}" + ) { + warning("W094", state.tokens.curr); + break; + } else if ( + state.tokens.next.id === "," + ) { + error("E028", state.tokens.next); + } else if ( + state.tokens.next.id !== "(string)" + ) { + warning( + "W095", + state.tokens.next, + state.tokens.next.value, + ); + } + if ( + o[state.tokens.next.value] === true + ) { + warning( + "W075", + state.tokens.next, + state.tokens.next.value, + ); + } else if ( + (state.tokens.next.value === + "__proto__" && + !state.option.proto) || + (state.tokens.next.value === + "__iterator__" && + !state.option.iterator) + ) { + warning( + "W096", + state.tokens.next, + state.tokens.next.value, + ); + } else { + o[state.tokens.next.value] = true; + } + advance(); + advance(":"); + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("}"); + } + + function jsonArray() { + var t = state.tokens.next; + advance("["); + if (state.tokens.next.id !== "]") { + for (;;) { + if (state.tokens.next.id === "(end)") { + error( + "E027", + state.tokens.next, + t.line, + ); + } else if ( + state.tokens.next.id === "]" + ) { + warning("W094", state.tokens.curr); + break; + } else if ( + state.tokens.next.id === "," + ) { + error("E028", state.tokens.next); + } + jsonValue(); + if (state.tokens.next.id !== ",") { + break; + } + advance(","); + } + } + advance("]"); + } + + switch (state.tokens.next.id) { + case "{": + jsonObject(); + break; + case "[": + jsonArray(); + break; + case "true": + case "false": + case "null": + case "(number)": + case "(string)": + advance(); + break; + case "-": + advance("-"); + if ( + state.tokens.curr.character !== + state.tokens.next.from + ) { + warning("W011", state.tokens.curr); + } + adjacent( + state.tokens.curr, + state.tokens.next, + ); + advance("(number)"); + break; + default: + error("E003", state.tokens.next); + } + } + + var blockScope = function () { + var _current = {}; + var _variables = [_current]; + + function _checkBlockLabels() { + for (var t in _current) { + if (_current[t]["(type)"] === "unused") { + if (state.option.unused) { + var tkn = _current[t]["(token)"]; + var line = tkn.line; + var chr = tkn.character; + warningAt("W098", line, chr, t); + } + } + } + } + + return { + stack: function () { + _current = {}; + _variables.push(_current); + }, + + unstack: function () { + _checkBlockLabels(); + _variables.splice(_variables.length - 1, 1); + _current = _.last(_variables); + }, + + getlabel: function (l) { + for ( + var i = _variables.length - 1; + i >= 0; + --i + ) { + if ( + _.has(_variables[i], l) && + !_variables[i][l]["(shadowed)"] + ) { + return _variables[i]; + } + } + }, + + shadow: function (name) { + for ( + var i = _variables.length - 1; + i >= 0; + i-- + ) { + if (_.has(_variables[i], name)) { + _variables[i][name]["(shadowed)"] = + true; + } + } + }, + + unshadow: function (name) { + for ( + var i = _variables.length - 1; + i >= 0; + i-- + ) { + if (_.has(_variables[i], name)) { + _variables[i][name]["(shadowed)"] = + false; + } + } + }, + + current: { + has: function (t) { + return _.has(_current, t); + }, + + add: function (t, type, tok) { + _current[t] = { + "(type)": type, + "(token)": tok, + "(shadowed)": false, + }; + }, + }, + }; + }; + + // The actual JSHINT function itself. + var itself = function (s, o, g) { + var i, k, x; + var optionKeys; + var newOptionObj = {}; + var newIgnoredObj = {}; + + o = _.clone(o); + state.reset(); + + if (o && o.scope) { + JSHINT.scope = o.scope; + } else { + JSHINT.errors = []; + JSHINT.undefs = []; + JSHINT.internals = []; + JSHINT.blacklist = {}; + JSHINT.scope = "(main)"; + } + + predefined = Object.create(null); + combine(predefined, vars.ecmaIdentifiers); + combine(predefined, vars.reservedVars); + + combine(predefined, g || {}); + + declared = Object.create(null); + exported = Object.create(null); + + function each(obj, cb) { + if (!obj) return; + + if ( + !Array.isArray(obj) && + typeof obj === "object" + ) + obj = Object.keys(obj); + + obj.forEach(cb); + } + + if (o) { + each(o.predef || null, function (item) { + var slice, prop; + + if (item[0] === "-") { + slice = item.slice(1); + JSHINT.blacklist[slice] = slice; + } else { + prop = Object.getOwnPropertyDescriptor( + o.predef, + item, + ); + predefined[item] = prop + ? prop.value + : false; + } + }); + + each(o.exported || null, function (item) { + exported[item] = true; + }); + + delete o.predef; + delete o.exported; + + optionKeys = Object.keys(o); + for (x = 0; x < optionKeys.length; x++) { + if (/^-W\d{3}$/g.test(optionKeys[x])) { + newIgnoredObj[optionKeys[x].slice(1)] = + true; + } else { + newOptionObj[optionKeys[x]] = + o[optionKeys[x]]; + + if ( + optionKeys[x] === "newcap" && + o[optionKeys[x]] === false + ) + newOptionObj["(explicitNewcap)"] = + true; + + if (optionKeys[x] === "indent") + newOptionObj["(explicitIndent)"] = + o[optionKeys[x]] === false + ? false + : true; + } + } + } + + state.option = newOptionObj; + state.ignored = newIgnoredObj; + + state.option.indent = state.option.indent || 4; + state.option.maxerr = state.option.maxerr || 50; + + indent = 1; + global = Object.create(predefined); + scope = global; + + funct = functor("(global)", null, scope, { + "(global)": true, + "(blockscope)": blockScope(), + "(comparray)": arrayComprehension(), + "(metrics)": createMetrics(state.tokens.next), + }); + + functions = [funct]; + urls = []; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + warnings = 0; + unuseds = []; + + if (!isString(s) && !Array.isArray(s)) { + errorAt("E004", 0); + return false; + } + + api = { + get isJSON() { + return state.jsonMode; + }, + + getOption: function (name) { + return state.option[name] || null; + }, + + getCache: function (name) { + return state.cache[name]; + }, + + setCache: function (name, value) { + state.cache[name] = value; + }, + + warn: function (code, data) { + warningAt.apply( + null, + [code, data.line, data.char].concat( + data.data, + ), + ); + }, + + on: function (names, listener) { + names.split(" ").forEach( + function (name) { + emitter.on(name, listener); + }.bind(this), + ); + }, + }; + + emitter.removeAllListeners(); + (extraModules || []).forEach(function (func) { + func(api); + }); + + state.tokens.prev = + state.tokens.curr = + state.tokens.next = + state.syntax["(begin)"]; + + lex = new Lexer(s); + + lex.on("warning", function (ev) { + warningAt.apply( + null, + [ev.code, ev.line, ev.character].concat( + ev.data, + ), + ); + }); + + lex.on("error", function (ev) { + errorAt.apply( + null, + [ev.code, ev.line, ev.character].concat( + ev.data, + ), + ); + }); + + lex.on("fatal", function (ev) { + quit("E041", ev.line, ev.from); + }); + + lex.on("Identifier", function (ev) { + emitter.emit("Identifier", ev); + }); + + lex.on("String", function (ev) { + emitter.emit("String", ev); + }); + + lex.on("Number", function (ev) { + emitter.emit("Number", ev); + }); + + lex.start(); + + // Check options + for (var name in o) { + if (_.has(o, name)) { + checkOption(name, state.tokens.curr); + } + } + + assume(); + + // combine the passed globals after we've assumed all our options + combine(predefined, g || {}); + + //reset values + comma.first = true; + + try { + advance(); + switch (state.tokens.next.id) { + case "{": + case "[": + destructuringAssignOrJsonValue(); + break; + default: + directives(); + + if (state.directive["use strict"]) { + if ( + !state.option.globalstrict && + !( + state.option.node || + state.option.phantom + ) + ) { + warning( + "W097", + state.tokens.prev, + ); + } + } + + statements(); + } + advance( + state.tokens.next && + state.tokens.next.value !== "." + ? "(end)" + : undefined, + ); + funct["(blockscope)"].unstack(); + + var markDefined = function (name, context) { + do { + if (typeof context[name] === "string") { + // JSHINT marks unused variables as 'unused' and + // unused function declaration as 'unction'. This + // code changes such instances back 'var' and + // 'closure' so that the code in JSHINT.data() + // doesn't think they're unused. + + if (context[name] === "unused") + context[name] = "var"; + else if ( + context[name] === "unction" + ) + context[name] = "closure"; + + return true; + } + + context = context["(context)"]; + } while (context); + + return false; + }; + + var clearImplied = function (name, line) { + if (!implied[name]) return; + + var newImplied = []; + for ( + var i = 0; + i < implied[name].length; + i += 1 + ) { + if (implied[name][i] !== line) + newImplied.push(implied[name][i]); + } + + if (newImplied.length === 0) + delete implied[name]; + else implied[name] = newImplied; + }; + + var warnUnused = function ( + name, + tkn, + type, + unused_opt, + ) { + var line = tkn.line; + var chr = tkn.character; + + if (unused_opt === undefined) { + unused_opt = state.option.unused; + } + + if (unused_opt === true) { + unused_opt = "last-param"; + } + + var warnable_types = { + vars: ["var"], + "last-param": ["var", "param"], + strict: ["var", "param", "last-param"], + }; + + if (unused_opt) { + if ( + warnable_types[unused_opt] && + warnable_types[unused_opt].indexOf( + type, + ) !== -1 + ) { + warningAt("W098", line, chr, name); + } + } + + unuseds.push({ + name: name, + line: line, + character: chr, + }); + }; + + var checkUnused = function (func, key) { + var type = func[key]; + var tkn = func["(tokens)"][key]; + + if (key.charAt(0) === "(") return; + + if ( + type !== "unused" && + type !== "unction" && + type !== "const" + ) + return; + + // Params are checked separately from other variables. + if ( + func["(params)"] && + func["(params)"].indexOf(key) !== -1 + ) + return; + + // Variable is in global scope and defined as exported. + if ( + func["(global)"] && + _.has(exported, key) + ) + return; + + // Is this constant unused? + if ( + type === "const" && + !getprop(func, key, "unused") + ) + return; + + warnUnused(key, tkn, "var"); + }; + + // Check queued 'x is not defined' instances to see if they're still undefined. + for (i = 0; i < JSHINT.undefs.length; i += 1) { + k = JSHINT.undefs[i].slice(0); + + if (markDefined(k[2].value, k[0])) { + clearImplied(k[2].value, k[2].line); + } else if (state.option.undef) { + warning.apply(warning, k.slice(1)); + } + } + + functions.forEach(function (func) { + if (func["(unusedOption)"] === false) { + return; + } + + for (var key in func) { + if (_.has(func, key)) { + checkUnused(func, key); + } + } + + if (!func["(params)"]) return; + + var params = func["(params)"].slice(); + var param = params.pop(); + var type, unused_opt; + + while (param) { + type = func[param]; + unused_opt = + func["(unusedOption)"] || + state.option.unused; + unused_opt = + unused_opt === true + ? "last-param" + : unused_opt; + + // 'undefined' is a special case for (function (window, undefined) { ... })(); + // patterns. + + if (param === "undefined") return; + + if ( + type === "unused" || + type === "unction" + ) { + warnUnused( + param, + func["(tokens)"][param], + "param", + func["(unusedOption)"], + ); + } else if ( + unused_opt === "last-param" + ) { + return; + } + + param = params.pop(); + } + }); + + for (var key in declared) { + if ( + _.has(declared, key) && + !_.has(global, key) && + !_.has(exported, key) + ) { + warnUnused(key, declared[key], "var"); + } + } + } catch (err) { + if (err && err.name === "JSHintError") { + var nt = state.tokens.next || {}; + JSHINT.errors.push( + { + scope: "(main)", + raw: err.raw, + code: err.code, + reason: err.message, + line: err.line || nt.line, + character: err.character || nt.from, + }, + null, + ); + } else { + throw err; + } + } + + // Loop over the listed "internals", and check them as well. + + if (JSHINT.scope === "(main)") { + o = o || {}; + + for ( + i = 0; + i < JSHINT.internals.length; + i += 1 + ) { + k = JSHINT.internals[i]; + o.scope = k.elem; + itself(k.value, o, g); + } + } + + return JSHINT.errors.length === 0; + }; + + // Modules. + itself.addModule = function (func) { + extraModules.push(func); + }; + + itself.addModule(style.register); + + // Data summary. + itself.data = function () { + var data = { + functions: [], + options: state.option, + }; + + var implieds = []; + var members = []; + var fu, f, i, j, n, globals; + + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (state.jsonMode) { + data.json = true; + } + + for (n in implied) { + if (_.has(implied, n)) { + implieds.push({ + name: n, + line: implied[n], + }); + } + } + + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + + fu.name = f["(name)"]; + fu.param = f["(params)"]; + fu.line = f["(line)"]; + fu.character = f["(character)"]; + fu.last = f["(last)"]; + fu.lastcharacter = f["(lastcharacter)"]; + + fu.metrics = { + complexity: f["(metrics)"].ComplexityCount, + parameters: (f["(params)"] || []).length, + statements: f["(metrics)"].statementCount, + }; + + data.functions.push(fu); + } + + if (unuseds.length > 0) { + data.unused = unuseds; + } + + members = []; + for (n in member) { + if (typeof member[n] === "number") { + data.member = member; + break; + } + } + + return data; + }; + + itself.jshint = itself; + + return itself; + })(); + + // Make JSHINT a Node module, if possible. + if (typeof exports === "object" && exports) { + exports.JSHINT = JSHINT; + } + }, + { + "./lex.js": 14, + "./messages.js": 15, + "./reg.js": 16, + "./state.js": 17, + "./style.js": 18, + "./vars.js": 19, + "console-browserify": 10, + events: 5, + underscore: 11, + }, + ], + 14: [ + function (require, module, exports) { + /* + * Lexical analysis and token construction. + */ + + "use strict"; + + var _ = require("underscore"); + var events = require("events"); + var reg = require("./reg.js"); + var state = require("./state.js").state; + + var unicodeData = require("../data/ascii-identifier-data.js"); + var asciiIdentifierStartTable = + unicodeData.asciiIdentifierStartTable; + var asciiIdentifierPartTable = + unicodeData.asciiIdentifierPartTable; + var nonAsciiIdentifierStartTable = require("../data/non-ascii-identifier-start.js"); + var nonAsciiIdentifierPartTable = require("../data/non-ascii-identifier-part-only.js"); + + // Some of these token types are from JavaScript Parser API + // while others are specific to JSHint parser. + // JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API + + var Token = { + Identifier: 1, + Punctuator: 2, + NumericLiteral: 3, + StringLiteral: 4, + Comment: 5, + Keyword: 6, + NullLiteral: 7, + BooleanLiteral: 8, + RegExp: 9, + }; + + // Object that handles postponed lexing verifications that checks the parsed + // environment state. + + function asyncTrigger() { + var _checks = []; + + return { + push: function (fn) { + _checks.push(fn); + }, + + check: function () { + for ( + var check = 0; + check < _checks.length; + ++check + ) { + _checks[check](); + } + + _checks.splice(0, _checks.length); + }, + }; + } + + /* + * Lexer for JSHint. + * + * This object does a char-by-char scan of the provided source code + * and produces a sequence of tokens. + * + * var lex = new Lexer("var i = 0;"); + * lex.start(); + * lex.token(); // returns the next token + * + * You have to use the token() method to move the lexer forward + * but you don't have to use its return value to get tokens. In addition + * to token() method returning the next token, the Lexer object also + * emits events. + * + * lex.on("Identifier", function (data) { + * if (data.name.indexOf("_") >= 0) { + * // Produce a warning. + * } + * }); + * + * Note that the token() method returns tokens in a JSLint-compatible + * format while the event emitter uses a slightly modified version of + * Mozilla's JavaScript Parser API. Eventually, we will move away from + * JSLint format. + */ + function Lexer(source) { + var lines = source; + + if (typeof lines === "string") { + lines = lines + .replace(/\r\n/g, "\n") + .replace(/\r/g, "\n") + .split("\n"); + } + + // If the first line is a shebang (#!), make it a blank and move on. + // Shebangs are used by Node scripts. + + if (lines[0] && lines[0].substr(0, 2) === "#!") { + if (lines[0].indexOf("node") !== -1) { + state.option.node = true; + } + lines[0] = ""; + } + + this.emitter = new events.EventEmitter(); + this.source = source; + this.setLines(lines); + this.prereg = true; + + this.line = 0; + this.char = 1; + this.from = 1; + this.input = ""; + this.inComment = false; + + for (var i = 0; i < state.option.indent; i += 1) { + state.tab += " "; + } + } + + Lexer.prototype = { + _lines: [], + + getLines: function () { + this._lines = state.lines; + return this._lines; + }, + + setLines: function (val) { + this._lines = val; + state.lines = this._lines; + }, + + /* + * Return the next i character without actually moving the + * char pointer. + */ + peek: function (i) { + return this.input.charAt(i || 0); + }, + + /* + * Move the char pointer forward i times. + */ + skip: function (i) { + i = i || 1; + this.char += i; + this.input = this.input.slice(i); + }, + + /* + * Subscribe to a token event. The API for this method is similar + * Underscore.js i.e. you can subscribe to multiple events with + * one call: + * + * lex.on("Identifier Number", function (data) { + * // ... + * }); + */ + on: function (names, listener) { + names.split(" ").forEach( + function (name) { + this.emitter.on(name, listener); + }.bind(this), + ); + }, + + /* + * Trigger a token event. All arguments will be passed to each + * listener. + */ + trigger: function () { + this.emitter.emit.apply( + this.emitter, + Array.prototype.slice.call(arguments), + ); + }, + + /* + * Postpone a token event. the checking condition is set as + * last parameter, and the trigger function is called in a + * stored callback. To be later called using the check() function + * by the parser. This avoids parser's peek() to give the lexer + * a false context. + */ + triggerAsync: function (type, args, checks, fn) { + checks.push( + function () { + if (fn()) { + this.trigger(type, args); + } + }.bind(this), + ); + }, + + /* + * Extract a punctuator out of the next sequence of characters + * or return 'null' if its not possible. + * + * This method's implementation was heavily influenced by the + * scanPunctuator function in the Esprima parser's source code. + */ + scanPunctuator: function () { + var ch1 = this.peek(); + var ch2, ch3, ch4; + + switch (ch1) { + // Most common single-character punctuators + case ".": + if (/^[0-9]$/.test(this.peek(1))) { + return null; + } + if ( + this.peek(1) === "." && + this.peek(2) === "." + ) { + return { + type: Token.Punctuator, + value: "...", + }; + } + /* falls through */ + case "(": + case ")": + case ";": + case ",": + case "{": + case "}": + case "[": + case "]": + case ":": + case "~": + case "?": + return { + type: Token.Punctuator, + value: ch1, + }; + + // A pound sign (for Node shebangs) + case "#": + return { + type: Token.Punctuator, + value: ch1, + }; + + // We're at the end of input + case "": + return null; + } + + // Peek more characters + + ch2 = this.peek(1); + ch3 = this.peek(2); + ch4 = this.peek(3); + + // 4-character punctuator: >>>= + + if ( + ch1 === ">" && + ch2 === ">" && + ch3 === ">" && + ch4 === "=" + ) { + return { + type: Token.Punctuator, + value: ">>>=", + }; + } + + // 3-character punctuators: === !== >>> <<= >>= + + if (ch1 === "=" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "===", + }; + } + + if (ch1 === "!" && ch2 === "=" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "!==", + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === ">") { + return { + type: Token.Punctuator, + value: ">>>", + }; + } + + if (ch1 === "<" && ch2 === "<" && ch3 === "=") { + return { + type: Token.Punctuator, + value: "<<=", + }; + } + + if (ch1 === ">" && ch2 === ">" && ch3 === "=") { + return { + type: Token.Punctuator, + value: ">>=", + }; + } + + // Fat arrow punctuator + if (ch1 === "=" && ch2 === ">") { + return { + type: Token.Punctuator, + value: ch1 + ch2, + }; + } + + // 2-character punctuators: <= >= == != ++ -- << >> && || + // += -= *= %= &= |= ^= (but not /=, see below) + if (ch1 === ch2 && "+-<>&|".indexOf(ch1) >= 0) { + return { + type: Token.Punctuator, + value: ch1 + ch2, + }; + } + + if ("<>=!+-*%&|^".indexOf(ch1) >= 0) { + if (ch2 === "=") { + return { + type: Token.Punctuator, + value: ch1 + ch2, + }; + } + + return { + type: Token.Punctuator, + value: ch1, + }; + } + + // Special case: /=. We need to make sure that this is an + // operator and not a regular expression. + + if (ch1 === "/") { + if ( + ch2 === "=" && + /\/=(?!(\S*\/[gim]?))/.test(this.input) + ) { + // /= is not a part of a regular expression, return it as a + // punctuator. + return { + type: Token.Punctuator, + value: "/=", + }; + } + + return { + type: Token.Punctuator, + value: "/", + }; + } + + return null; + }, + + /* + * Extract a comment out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since comments can + * span across multiple lines this method has to move the char + * pointer. + * + * In addition to normal JavaScript comments (// and /*) this method + * also recognizes JSHint- and JSLint-specific comments such as + * /*jshint, /*jslint, /*globals and so on. + */ + scanComments: function () { + var ch1 = this.peek(); + var ch2 = this.peek(1); + var rest = this.input.substr(2); + var startLine = this.line; + var startChar = this.char; + + // Create a comment token object and make sure it + // has all the data JSHint needs to work with special + // comments. + + function commentToken(label, body, opt) { + var special = [ + "jshint", + "jslint", + "members", + "member", + "globals", + "global", + "exported", + ]; + var isSpecial = false; + var value = label + body; + var commentType = "plain"; + opt = opt || {}; + + if (opt.isMultiline) { + value += "*/"; + } + + special.forEach(function (str) { + if (isSpecial) { + return; + } + + // Don't recognize any special comments other than jshint for single-line + // comments. This introduced many problems with legit comments. + if (label === "//" && str !== "jshint") { + return; + } + + if (body.substr(0, str.length) === str) { + isSpecial = true; + label = label + str; + body = body.substr(str.length); + } + + if ( + !isSpecial && + body.charAt(0) === " " && + body.substr(1, str.length) === str + ) { + isSpecial = true; + label = label + " " + str; + body = body.substr(str.length + 1); + } + + if (!isSpecial) { + return; + } + + switch (str) { + case "member": + commentType = "members"; + break; + case "global": + commentType = "globals"; + break; + default: + commentType = str; + } + }); + + return { + type: Token.Comment, + commentType: commentType, + value: value, + body: body, + isSpecial: isSpecial, + isMultiline: opt.isMultiline || false, + isMalformed: opt.isMalformed || false, + }; + } + + // End of unbegun comment. Raise an error and skip that input. + if (ch1 === "*" && ch2 === "/") { + this.trigger("error", { + code: "E018", + line: startLine, + character: startChar, + }); + + this.skip(2); + return null; + } + + // Comments must start either with // or /* + if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) { + return null; + } + + // One-line comment + if (ch2 === "/") { + this.skip(this.input.length); // Skip to the EOL. + return commentToken("//", rest); + } + + var body = ""; + + /* Multi-line comment */ + if (ch2 === "*") { + this.inComment = true; + this.skip(2); + + while ( + this.peek() !== "*" || + this.peek(1) !== "/" + ) { + if (this.peek() === "") { + // End of Line + body += "\n"; + + // If we hit EOF and our comment is still unclosed, + // trigger an error and end the comment implicitly. + if (!this.nextLine()) { + this.trigger("error", { + code: "E017", + line: startLine, + character: startChar, + }); + + this.inComment = false; + return commentToken("/*", body, { + isMultiline: true, + isMalformed: true, + }); + } + } else { + body += this.peek(); + this.skip(); + } + } + + this.skip(2); + this.inComment = false; + return commentToken("/*", body, { + isMultiline: true, + }); + } + }, + + /* + * Extract a keyword out of the next sequence of characters or + * return 'null' if its not possible. + */ + scanKeyword: function () { + var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec( + this.input, + ); + var keywords = [ + "if", + "in", + "do", + "var", + "for", + "new", + "try", + "let", + "this", + "else", + "case", + "void", + "with", + "enum", + "while", + "break", + "catch", + "throw", + "const", + "yield", + "class", + "super", + "return", + "typeof", + "delete", + "switch", + "export", + "import", + "default", + "finally", + "extends", + "function", + "continue", + "debugger", + "instanceof", + ]; + + if (result && keywords.indexOf(result[0]) >= 0) { + return { + type: Token.Keyword, + value: result[0], + }; + } + + return null; + }, + + /* + * Extract a JavaScript identifier out of the next sequence of + * characters or return 'null' if its not possible. In addition, + * to Identifier this method can also produce BooleanLiteral + * (true/false) and NullLiteral (null). + */ + scanIdentifier: function () { + var id = ""; + var index = 0; + var type, char; + + function isNonAsciiIdentifierStart(code) { + return ( + nonAsciiIdentifierStartTable.indexOf(code) > + -1 + ); + } + + function isNonAsciiIdentifierPart(code) { + return ( + isNonAsciiIdentifierStart(code) || + nonAsciiIdentifierPartTable.indexOf(code) > + -1 + ); + } + + function isHexDigit(str) { + return /^[0-9a-fA-F]$/.test(str); + } + + var readUnicodeEscapeSequence = function () { + /*jshint validthis:true */ + index += 1; + + if (this.peek(index) !== "u") { + return null; + } + + var ch1 = this.peek(index + 1); + var ch2 = this.peek(index + 2); + var ch3 = this.peek(index + 3); + var ch4 = this.peek(index + 4); + var code; + + if ( + isHexDigit(ch1) && + isHexDigit(ch2) && + isHexDigit(ch3) && + isHexDigit(ch4) + ) { + code = parseInt(ch1 + ch2 + ch3 + ch4, 16); + + if ( + asciiIdentifierPartTable[code] || + isNonAsciiIdentifierPart(code) + ) { + index += 5; + return "\\u" + ch1 + ch2 + ch3 + ch4; + } + + return null; + } + + return null; + }.bind(this); + + var getIdentifierStart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (asciiIdentifierStartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isNonAsciiIdentifierStart(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + var getIdentifierPart = function () { + /*jshint validthis:true */ + var chr = this.peek(index); + var code = chr.charCodeAt(0); + + if (code === 92) { + return readUnicodeEscapeSequence(); + } + + if (code < 128) { + if (asciiIdentifierPartTable[code]) { + index += 1; + return chr; + } + + return null; + } + + if (isNonAsciiIdentifierPart(code)) { + index += 1; + return chr; + } + + return null; + }.bind(this); + + char = getIdentifierStart(); + if (char === null) { + return null; + } + + id = char; + for (;;) { + char = getIdentifierPart(); + + if (char === null) { + break; + } + + id += char; + } + + switch (id) { + case "true": + case "false": + type = Token.BooleanLiteral; + break; + case "null": + type = Token.NullLiteral; + break; + default: + type = Token.Identifier; + } + + return { + type: type, + value: id, + }; + }, + + /* + * Extract a numeric literal out of the next sequence of + * characters or return 'null' if its not possible. This method + * supports all numeric literals described in section 7.8.3 + * of the EcmaScript 5 specification. + * + * This method's implementation was heavily influenced by the + * scanNumericLiteral function in the Esprima parser's source code. + */ + scanNumericLiteral: function () { + var index = 0; + var value = ""; + var length = this.input.length; + var char = this.peek(index); + var bad; + + function isDecimalDigit(str) { + return /^[0-9]$/.test(str); + } + + function isOctalDigit(str) { + return /^[0-7]$/.test(str); + } + + function isHexDigit(str) { + return /^[0-9a-fA-F]$/.test(str); + } + + function isIdentifierStart(ch) { + return ( + ch === "$" || + ch === "_" || + ch === "\\" || + (ch >= "a" && ch <= "z") || + (ch >= "A" && ch <= "Z") + ); + } + + // Numbers must start either with a decimal digit or a point. + + if (char !== "." && !isDecimalDigit(char)) { + return null; + } + + if (char !== ".") { + value = this.peek(index); + index += 1; + char = this.peek(index); + + if (value === "0") { + // Base-16 numbers. + if (char === "x" || char === "X") { + index += 1; + value += char; + + while (index < length) { + char = this.peek(index); + if (!isHexDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (value.length <= 2) { + // 0x + return { + type: Token.NumericLiteral, + value: value, + isMalformed: true, + }; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 16, + isMalformed: false, + }; + } + + // Base-8 numbers. + if (isOctalDigit(char)) { + index += 1; + value += char; + bad = false; + + while (index < length) { + char = this.peek(index); + + // Numbers like '019' (note the 9) are not valid octals + // but we still parse them and mark as malformed. + + if (isDecimalDigit(char)) { + bad = true; + } else if (!isOctalDigit(char)) { + break; + } + value += char; + index += 1; + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 8, + isMalformed: false, + }; + } + + // Decimal numbers that start with '0' such as '09' are illegal + // but we still parse them and return as malformed. + + if (isDecimalDigit(char)) { + index += 1; + value += char; + } + } + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Decimal digits. + + if (char === ".") { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } + + // Exponent part. + + if (char === "e" || char === "E") { + value += char; + index += 1; + char = this.peek(index); + + if (char === "+" || char === "-") { + value += this.peek(index); + index += 1; + } + + char = this.peek(index); + if (isDecimalDigit(char)) { + value += char; + index += 1; + + while (index < length) { + char = this.peek(index); + if (!isDecimalDigit(char)) { + break; + } + value += char; + index += 1; + } + } else { + return null; + } + } + + if (index < length) { + char = this.peek(index); + if (isIdentifierStart(char)) { + return null; + } + } + + return { + type: Token.NumericLiteral, + value: value, + base: 10, + isMalformed: !isFinite(value), + }; + }, + + /* + * Extract a string out of the next sequence of characters and/or + * lines or return 'null' if its not possible. Since strings can + * span across multiple lines this method has to move the char + * pointer. + * + * This method recognizes pseudo-multiline JavaScript strings: + * + * var str = "hello\ + * world"; + */ + scanStringLiteral: function (checks) { + /*jshint loopfunc:true */ + var quote = this.peek(); + + // String must start with a quote. + if (quote !== '"' && quote !== "'") { + return null; + } + + // In JSON strings must always use double quotes. + this.triggerAsync( + "warning", + { + code: "W108", + line: this.line, + character: this.char, // +1? + }, + checks, + function () { + return state.jsonMode && quote !== '"'; + }, + ); + + var value = ""; + var startLine = this.line; + var startChar = this.char; + var allowNewLine = false; + + this.skip(); + + while (this.peek() !== quote) { + while (this.peek() === "") { + // End Of Line + + // If an EOL is not preceded by a backslash, show a warning + // and proceed like it was a legit multi-line string where + // author simply forgot to escape the newline symbol. + // + // Another approach is to implicitly close a string on EOL + // but it generates too many false positives. + + if (!allowNewLine) { + this.trigger("warning", { + code: "W112", + line: this.line, + character: this.char, + }); + } else { + allowNewLine = false; + + // Otherwise show a warning if multistr option was not set. + // For JSON, show warning no matter what. + + this.triggerAsync( + "warning", + { + code: "W043", + line: this.line, + character: this.char, + }, + checks, + function () { + return !state.option.multistr; + }, + ); + + this.triggerAsync( + "warning", + { + code: "W042", + line: this.line, + character: this.char, + }, + checks, + function () { + return ( + state.jsonMode && + state.option.multistr + ); + }, + ); + } + + // If we get an EOF inside of an unclosed string, show an + // error and implicitly close it at the EOF point. + + if (!this.nextLine()) { + this.trigger("error", { + code: "E029", + line: startLine, + character: startChar, + }); + + return { + type: Token.StringLiteral, + value: value, + isUnclosed: true, + quote: quote, + }; + } + } + + allowNewLine = false; + var char = this.peek(); + var jump = 1; // A length of a jump, after we're done + // parsing this character. + + if (char < " ") { + // Warn about a control character in a string. + this.trigger("warning", { + code: "W113", + line: this.line, + character: this.char, + data: [""], + }); + } + + // Special treatment for some escaped characters. + + if (char === "\\") { + this.skip(); + char = this.peek(); + + switch (char) { + case "'": + this.triggerAsync( + "warning", + { + code: "W114", + line: this.line, + character: this.char, + data: ["\\'"], + }, + checks, + function () { + return state.jsonMode; + }, + ); + break; + case "b": + char = "\\b"; + break; + case "f": + char = "\\f"; + break; + case "n": + char = "\\n"; + break; + case "r": + char = "\\r"; + break; + case "t": + char = "\\t"; + break; + case "0": + char = "\\0"; + + // Octal literals fail in strict mode. + // Check if the number is between 00 and 07. + var n = parseInt(this.peek(1), 10); + this.triggerAsync( + "warning", + { + code: "W115", + line: this.line, + character: this.char, + }, + checks, + function () { + return ( + n >= 0 && + n <= 7 && + state.directive[ + "use strict" + ] + ); + }, + ); + break; + case "u": + char = String.fromCharCode( + parseInt( + this.input.substr(1, 4), + 16, + ), + ); + jump = 5; + break; + case "v": + this.triggerAsync( + "warning", + { + code: "W114", + line: this.line, + character: this.char, + data: ["\\v"], + }, + checks, + function () { + return state.jsonMode; + }, + ); + + char = "\v"; + break; + case "x": + var x = parseInt( + this.input.substr(1, 2), + 16, + ); + + this.triggerAsync( + "warning", + { + code: "W114", + line: this.line, + character: this.char, + data: ["\\x-"], + }, + checks, + function () { + return state.jsonMode; + }, + ); + + char = String.fromCharCode(x); + jump = 3; + break; + case "\\": + char = "\\\\"; + break; + case '"': + char = '\\"'; + break; + case "/": + break; + case "": + allowNewLine = true; + char = ""; + break; + case "!": + if ( + value.slice( + value.length - 2, + ) === "<" + ) { + break; + } + + /*falls through */ + default: + // Weird escaping. + this.trigger("warning", { + code: "W044", + line: this.line, + character: this.char, + }); + } + } + + value += char; + this.skip(jump); + } + + this.skip(); + return { + type: Token.StringLiteral, + value: value, + isUnclosed: false, + quote: quote, + }; + }, + + /* + * Extract a regular expression out of the next sequence of + * characters and/or lines or return 'null' if its not possible. + * + * This method is platform dependent: it accepts almost any + * regular expression values but then tries to compile and run + * them using system's RegExp object. This means that there are + * rare edge cases where one JavaScript engine complains about + * your regular expression while others don't. + */ + scanRegExp: function () { + var index = 0; + var length = this.input.length; + var char = this.peek(); + var value = char; + var body = ""; + var flags = []; + var malformed = false; + var isCharSet = false; + var terminated; + + var scanUnexpectedChars = function () { + // Unexpected control character + if (char < " ") { + malformed = true; + this.trigger("warning", { + code: "W048", + line: this.line, + character: this.char, + }); + } + + // Unexpected escaped character + if (char === "<") { + malformed = true; + this.trigger("warning", { + code: "W049", + line: this.line, + character: this.char, + data: [char], + }); + } + }.bind(this); + + // Regular expressions must start with '/' + if (!this.prereg || char !== "/") { + return null; + } + + index += 1; + terminated = false; + + // Try to get everything in between slashes. A couple of + // cases aside (see scanUnexpectedChars) we don't really + // care whether the resulting expression is valid or not. + // We will check that later using the RegExp object. + + while (index < length) { + char = this.peek(index); + value += char; + body += char; + + if (isCharSet) { + if (char === "]") { + if ( + this.peek(index - 1) !== "\\" || + this.peek(index - 2) === "\\" + ) { + isCharSet = false; + } + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + } + + index += 1; + continue; + } + + if (char === "\\") { + index += 1; + char = this.peek(index); + body += char; + value += char; + + scanUnexpectedChars(); + + if (char === "/") { + index += 1; + continue; + } + + if (char === "[") { + index += 1; + continue; + } + } + + if (char === "[") { + isCharSet = true; + index += 1; + continue; + } + + if (char === "/") { + body = body.substr(0, body.length - 1); + terminated = true; + index += 1; + break; + } + + index += 1; + } + + // A regular expression that was never closed is an + // error from which we cannot recover. + + if (!terminated) { + this.trigger("error", { + code: "E015", + line: this.line, + character: this.from, + }); + + return void this.trigger("fatal", { + line: this.line, + from: this.from, + }); + } + + // Parse flags (if any). + + while (index < length) { + char = this.peek(index); + if (!/[gim]/.test(char)) { + break; + } + flags.push(char); + value += char; + index += 1; + } + + // Check regular expression for correctness. + + try { + new RegExp(body, flags.join("")); + } catch (err) { + malformed = true; + this.trigger("error", { + code: "E016", + line: this.line, + character: this.char, + data: [err.message], // Platform dependent! + }); + } + + return { + type: Token.RegExp, + value: value, + flags: flags, + isMalformed: malformed, + }; + }, + + /* + * Scan for any occurrence of mixed tabs and spaces. If smarttabs option + * is on, ignore tabs followed by spaces. + * + * Tabs followed by one space followed by a block comment are allowed. + */ + scanMixedSpacesAndTabs: function () { + var at, match; + + if (state.option.smarttabs) { + // Negative look-behind for "//" + match = this.input.match(/(\/\/|^\s?\*)? \t/); + at = match && !match[1] ? 0 : -1; + } else { + at = this.input.search(/ \t|\t [^\*]/); + } + + return at; + }, + + /* + * Scan for any occurrence of non-breaking spaces. Non-breaking spaces + * can be mistakenly typed on OS X with option-space. Non UTF-8 web + * pages with non-breaking pages produce syntax errors. + */ + scanNonBreakingSpaces: function () { + return state.option.nonbsp + ? this.input.search(/(\u00A0)/) + : -1; + }, + + /* + * Scan for characters that get silently deleted by one or more browsers. + */ + scanUnsafeChars: function () { + return this.input.search(reg.unsafeChars); + }, + + /* + * Produce the next raw token or return 'null' if no tokens can be matched. + * This method skips over all space characters. + */ + next: function (checks) { + this.from = this.char; + + // Move to the next non-space character. + var start; + if (/\s/.test(this.peek())) { + start = this.char; + + while (/\s/.test(this.peek())) { + this.from += 1; + this.skip(); + } + + if (this.peek() === "") { + // EOL + if ( + !/^\s*$/.test( + this.getLines()[this.line - 1], + ) && + state.option.trailing + ) { + this.trigger("warning", { + code: "W102", + line: this.line, + character: start, + }); + } + } + } + + // Methods that work with multi-line structures and move the + // character pointer. + + var match = + this.scanComments() || + this.scanStringLiteral(checks); + + if (match) { + return match; + } + + // Methods that don't move the character pointer. + + match = + this.scanRegExp() || + this.scanPunctuator() || + this.scanKeyword() || + this.scanIdentifier() || + this.scanNumericLiteral(); + + if (match) { + this.skip(match.value.length); + return match; + } + + // No token could be matched, give up. + + return null; + }, + + /* + * Switch to the next line and reset all char pointers. Once + * switched, this method also checks for mixed spaces and tabs + * and other minor warnings. + */ + nextLine: function () { + var char; + + if (this.line >= this.getLines().length) { + return false; + } + + this.input = this.getLines()[this.line]; + this.line += 1; + this.char = 1; + this.from = 1; + + var inputTrimmed = this.input.trim(); + + var startsWith = function () { + return _.some(arguments, function (prefix) { + return inputTrimmed.indexOf(prefix) === 0; + }); + }; + + var endsWith = function () { + return _.some(arguments, function (suffix) { + return ( + inputTrimmed.indexOf( + suffix, + inputTrimmed.length - suffix.length, + ) !== -1 + ); + }); + }; + + // If we are ignoring linter errors, replace the input with empty string + // if it doesn't already at least start or end a multi-line comment + if (state.ignoreLinterErrors === true) { + if ( + !startsWith("/*", "//") && + !endsWith("*/") + ) { + this.input = ""; + } + } + + char = this.scanNonBreakingSpaces(); + if (char >= 0) { + this.trigger("warning", { + code: "W125", + line: this.line, + character: char + 1, + }); + } + + char = this.scanMixedSpacesAndTabs(); + if (char >= 0) { + this.trigger("warning", { + code: "W099", + line: this.line, + character: char + 1, + }); + } + + this.input = this.input.replace(/\t/g, state.tab); + char = this.scanUnsafeChars(); + + if (char >= 0) { + this.trigger("warning", { + code: "W100", + line: this.line, + character: char, + }); + } + + // If there is a limit on line length, warn when lines get too + // long. + + if ( + state.option.maxlen && + state.option.maxlen < this.input.length + ) { + var inComment = + this.inComment || + startsWith.call(inputTrimmed, "//") || + startsWith.call(inputTrimmed, "/*"); + + var shouldTriggerError = + !inComment || + !reg.maxlenException.test(inputTrimmed); + + if (shouldTriggerError) { + this.trigger("warning", { + code: "W101", + line: this.line, + character: this.input.length, + }); + } + } + + return true; + }, + + /* + * This is simply a synonym for nextLine() method with a friendlier + * public name. + */ + start: function () { + this.nextLine(); + }, + + /* + * Produce the next token. This function is called by advance() to get + * the next token. It returns a token in a JSLint-compatible format. + */ + token: function () { + /*jshint loopfunc:true */ + var checks = asyncTrigger(); + var token; + + function isReserved(token, isProperty) { + if (!token.reserved) { + return false; + } + var meta = token.meta; + + if ( + meta && + meta.isFutureReservedWord && + state.option.inES5() + ) { + // ES3 FutureReservedWord in an ES5 environment. + if (!meta.es5) { + return false; + } + + // Some ES5 FutureReservedWord identifiers are active only + // within a strict mode environment. + if (meta.strictOnly) { + if ( + !state.option.strict && + !state.directive["use strict"] + ) { + return false; + } + } + + if (isProperty) { + return false; + } + } + + return true; + } + + // Produce a token object. + var create = function (type, value, isProperty) { + /*jshint validthis:true */ + var obj; + + if (type !== "(endline)" && type !== "(end)") { + this.prereg = false; + } + + if (type === "(punctuator)") { + switch (value) { + case ".": + case ")": + case "~": + case "#": + case "]": + this.prereg = false; + break; + default: + this.prereg = true; + } + + obj = Object.create( + state.syntax[value] || + state.syntax["(error)"], + ); + } + + if (type === "(identifier)") { + if ( + value === "return" || + value === "case" || + value === "typeof" + ) { + this.prereg = true; + } + + if (_.has(state.syntax, value)) { + obj = Object.create( + state.syntax[value] || + state.syntax["(error)"], + ); + + // If this can't be a reserved keyword, reset the object. + if ( + !isReserved( + obj, + isProperty && + type === "(identifier)", + ) + ) { + obj = null; + } + } + } + + if (!obj) { + obj = Object.create(state.syntax[type]); + } + + obj.identifier = type === "(identifier)"; + obj.type = obj.type || type; + obj.value = value; + obj.line = this.line; + obj.character = this.char; + obj.from = this.from; + + if (isProperty && obj.identifier) { + obj.isProperty = isProperty; + } + + obj.check = checks.check; + + return obj; + }.bind(this); + + for (;;) { + if (!this.input.length) { + return create( + this.nextLine() ? "(endline)" : "(end)", + "", + ); + } + + token = this.next(checks); + + if (!token) { + if (this.input.length) { + // Unexpected character. + this.trigger("error", { + code: "E024", + line: this.line, + character: this.char, + data: [this.peek()], + }); + + this.input = ""; + } + + continue; + } + + switch (token.type) { + case Token.StringLiteral: + this.triggerAsync( + "String", + { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + quote: token.quote, + }, + checks, + function () { + return true; + }, + ); + + return create("(string)", token.value); + case Token.Identifier: + this.trigger("Identifier", { + line: this.line, + char: this.char, + from: this.form, + name: token.value, + isProperty: + state.tokens.curr.id === ".", + }); + + /* falls through */ + case Token.Keyword: + case Token.NullLiteral: + case Token.BooleanLiteral: + return create( + "(identifier)", + token.value, + state.tokens.curr.id === ".", + ); + + case Token.NumericLiteral: + if (token.isMalformed) { + this.trigger("warning", { + code: "W045", + line: this.line, + character: this.char, + data: [token.value], + }); + } + + this.triggerAsync( + "warning", + { + code: "W114", + line: this.line, + character: this.char, + data: ["0x-"], + }, + checks, + function () { + return ( + token.base === 16 && + state.jsonMode + ); + }, + ); + + this.triggerAsync( + "warning", + { + code: "W115", + line: this.line, + character: this.char, + }, + checks, + function () { + return ( + state.directive[ + "use strict" + ] && token.base === 8 + ); + }, + ); + + this.trigger("Number", { + line: this.line, + char: this.char, + from: this.from, + value: token.value, + base: token.base, + isMalformed: token.malformed, + }); + + return create("(number)", token.value); + + case Token.RegExp: + return create("(regexp)", token.value); + + case Token.Comment: + state.tokens.curr.comment = true; + + if (token.isSpecial) { + return { + id: "(comment)", + value: token.value, + body: token.body, + type: token.commentType, + isSpecial: token.isSpecial, + line: this.line, + character: this.char, + from: this.from, + }; + } + + break; + + case "": + break; + + default: + return create( + "(punctuator)", + token.value, + ); + } + } + }, + }; + + exports.Lexer = Lexer; + }, + { + "../data/ascii-identifier-data.js": 1, + "../data/non-ascii-identifier-part-only.js": 2, + "../data/non-ascii-identifier-start.js": 3, + "./reg.js": 16, + "./state.js": 17, + events: 5, + underscore: 11, + }, + ], + 15: [ + function (require, module, exports) { + "use strict"; + + var _ = require("underscore"); + + var errors = { + // JSHint options + E001: "Bad option: '{a}'.", + E002: "Bad option value.", + + // JSHint input + E003: "Expected a JSON value.", + E004: "Input is neither a string nor an array of strings.", + E005: "Input is empty.", + E006: "Unexpected early end of program.", + + // Strict mode + E007: 'Missing "use strict" statement.', + E008: "Strict violation.", + E009: "Option 'validthis' can't be used in a global scope.", + E010: "'with' is not allowed in strict mode.", + + // Constants + E011: "const '{a}' has already been declared.", + E012: "const '{a}' is initialized to 'undefined'.", + E013: "Attempting to override '{a}' which is a constant.", + + // Regular expressions + E014: "A regular expression literal can be confused with '/='.", + E015: "Unclosed regular expression.", + E016: "Invalid regular expression.", + + // Tokens + E017: "Unclosed comment.", + E018: "Unbegun comment.", + E019: "Unmatched '{a}'.", + E020: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + E021: "Expected '{a}' and instead saw '{b}'.", + E022: "Line breaking error '{a}'.", + E023: "Missing '{a}'.", + E024: "Unexpected '{a}'.", + E025: "Missing ':' on a case clause.", + E026: "Missing '}' to match '{' from line {a}.", + E027: "Missing ']' to match '[' from line {a}.", + E028: "Illegal comma.", + E029: "Unclosed string.", + + // Everything else + E030: "Expected an identifier and instead saw '{a}'.", + E031: "Bad assignment.", // FIXME: Rephrase + E032: "Expected a small integer or 'false' and instead saw '{a}'.", + E033: "Expected an operator and instead saw '{a}'.", + E034: "get/set are ES5 features.", + E035: "Missing property name.", + E036: "Expected to see a statement and instead saw a block.", + E037: null, + E038: null, + E039: "Function declarations are not invocable. Wrap the whole function invocation in parens.", + E040: "Each value should have its own case label.", + E041: "Unrecoverable syntax error.", + E042: "Stopping.", + E043: "Too many errors.", + E044: null, + E045: "Invalid for each loop.", + E046: "A yield statement shall be within a generator function (with syntax: `function*`)", + E047: null, // Vacant + E048: "Let declaration not directly within block.", + E049: "A {a} cannot be named '{b}'.", + E050: "Mozilla requires the yield expression to be parenthesized here.", + E051: "Regular parameters cannot come after default parameters.", + }; + + var warnings = { + W001: "'hasOwnProperty' is a really bad name.", + W002: "Value of '{a}' may be overwritten in IE 8 and earlier.", + W003: "'{a}' was used before it was defined.", + W004: "'{a}' is already defined.", + W005: "A dot following a number can be confused with a decimal point.", + W006: "Confusing minuses.", + W007: "Confusing pluses.", + W008: "A leading decimal point can be confused with a dot: '{a}'.", + W009: "The array literal notation [] is preferable.", + W010: "The object literal notation {} is preferable.", + W011: "Unexpected space after '{a}'.", + W012: "Unexpected space before '{a}'.", + W013: "Missing space after '{a}'.", + W014: "Bad line breaking before '{a}'.", + W015: "Expected '{a}' to have an indentation at {b} instead at {c}.", + W016: "Unexpected use of '{a}'.", + W017: "Bad operand.", + W018: "Confusing use of '{a}'.", + W019: "Use the isNaN function to compare with NaN.", + W020: "Read only.", + W021: "'{a}' is a function.", + W022: "Do not assign to the exception parameter.", + W023: "Expected an identifier in an assignment and instead saw a function invocation.", + W024: "Expected an identifier and instead saw '{a}' (a reserved word).", + W025: "Missing name in function declaration.", + W026: "Inner functions should be listed at the top of the outer function.", + W027: "Unreachable '{a}' after '{b}'.", + W028: "Label '{a}' on {b} statement.", + W030: "Expected an assignment or function call and instead saw an expression.", + W031: "Do not use 'new' for side effects.", + W032: "Unnecessary semicolon.", + W033: "Missing semicolon.", + W034: 'Unnecessary directive "{a}".', + W035: "Empty block.", + W036: "Unexpected /*member '{a}'.", + W037: "'{a}' is a statement label.", + W038: "'{a}' used out of scope.", + W039: "'{a}' is not allowed.", + W040: "Possible strict violation.", + W041: "Use '{a}' to compare with '{b}'.", + W042: "Avoid EOL escaping.", + W043: "Bad escaping of EOL. Use option multistr if needed.", + W044: "Bad or unnecessary escaping.", + W045: "Bad number '{a}'.", + W046: "Don't use extra leading zeros '{a}'.", + W047: "A trailing decimal point can be confused with a dot: '{a}'.", + W048: "Unexpected control character in regular expression.", + W049: "Unexpected escaped character '{a}' in regular expression.", + W050: "JavaScript URL.", + W051: "Variables should not be deleted.", + W052: "Unexpected '{a}'.", + W053: "Do not use {a} as a constructor.", + W054: "The Function constructor is a form of eval.", + W055: "A constructor name should start with an uppercase letter.", + W056: "Bad constructor.", + W057: "Weird construction. Is 'new' necessary?", + W058: "Missing '()' invoking a constructor.", + W059: "Avoid arguments.{a}.", + W060: "document.write can be a form of eval.", + W061: "eval can be harmful.", + W062: + "Wrap an immediate function invocation in parens " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself.", + W063: "Math is not a function.", + W064: "Missing 'new' prefix when invoking a constructor.", + W065: "Missing radix parameter.", + W066: "Implied eval. Consider passing a function instead of a string.", + W067: "Bad invocation.", + W068: "Wrapping non-IIFE function literals in parens is unnecessary.", + W069: "['{a}'] is better written in dot notation.", + W070: "Extra comma. (it breaks older versions of IE)", + W071: "This function has too many statements. ({a})", + W072: "This function has too many parameters. ({a})", + W073: "Blocks are nested too deeply. ({a})", + W074: "This function's cyclomatic complexity is too high. ({a})", + W075: "Duplicate key '{a}'.", + W076: "Unexpected parameter '{a}' in get {b} function.", + W077: "Expected a single parameter in set {a} function.", + W078: "Setter is defined without getter.", + W079: "Redefinition of '{a}'.", + W080: "It's not necessary to initialize '{a}' to 'undefined'.", + W081: "Too many var statements.", + W082: + "Function declarations should not be placed in blocks. " + + "Use a function expression or move the statement to the top of " + + "the outer function.", + W083: "Don't make functions within a loop.", + W084: "Expected a conditional expression and instead saw an assignment.", + W085: "Don't use 'with'.", + W086: "Expected a 'break' statement before '{a}'.", + W087: "Forgotten 'debugger' statement?", + W088: "Creating global 'for' variable. Should be 'for (var {a} ...'.", + W089: + "The body of a for in should be wrapped in an if statement to filter " + + "unwanted properties from the prototype.", + W090: "'{a}' is not a statement label.", + W091: "'{a}' is out of scope.", + W093: "Did you mean to return a conditional instead of an assignment?", + W094: "Unexpected comma.", + W095: "Expected a string and instead saw {a}.", + W096: "The '{a}' key may produce unexpected results.", + W097: 'Use the function form of "use strict".', + W098: "'{a}' is defined but never used.", + W099: "Mixed spaces and tabs.", + W100: "This character may get silently deleted by one or more browsers.", + W101: "Line is too long.", + W102: "Trailing whitespace.", + W103: "The '{a}' property is deprecated.", + W104: "'{a}' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).", + W105: "Unexpected {a} in '{b}'.", + W106: "Identifier '{a}' is not in camel case.", + W107: "Script URL.", + W108: "Strings must use doublequote.", + W109: "Strings must use singlequote.", + W110: "Mixed double and single quotes.", + W112: "Unclosed string.", + W113: "Control character in string: {a}.", + W114: "Avoid {a}.", + W115: "Octal literals are not allowed in strict mode.", + W116: "Expected '{a}' and instead saw '{b}'.", + W117: "'{a}' is not defined.", + W118: "'{a}' is only available in Mozilla JavaScript extensions (use moz option).", + W119: "'{a}' is only available in ES6 (use esnext option).", + W120: "You might be leaking a variable ({a}) here.", + W121: "Extending prototype of native object: '{a}'.", + W122: "Invalid typeof value '{a}'", + W123: "'{a}' is already defined in outer scope.", + W124: "A generator function shall contain a yield statement.", + W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp", + }; + + var info = { + I001: "Comma warnings can be turned off with 'laxcomma'.", + I002: "Reserved words as properties can be used under the 'es5' option.", + I003: "ES5 option is now set per default", + }; + + exports.errors = {}; + exports.warnings = {}; + exports.info = {}; + + _.each(errors, function (desc, code) { + exports.errors[code] = { code: code, desc: desc }; + }); + + _.each(warnings, function (desc, code) { + exports.warnings[code] = { code: code, desc: desc }; + }); + + _.each(info, function (desc, code) { + exports.info[code] = { code: code, desc: desc }; + }); + }, + { underscore: 11 }, + ], + 16: [ + function (require, module, exports) { + /* + * Regular expressions. Some of these are stupidly long. + */ + + /*jshint maxlen:1000 */ + + "use string"; + + // Unsafe comment or string (ax) + exports.unsafeString = /@cc|<\/?|script|\]\s*\]|<\s*!|</i; + + // Unsafe characters that are silently deleted by one or more browsers (cx) + exports.unsafeChars = + /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + // Characters in strings that need escaping (nx and nxg) + exports.needEsc = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; + + exports.needEscGlobal = + /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + // Star slash (lx) + exports.starSlash = /\*\//; + + // Identifier (ix) + exports.identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; + + // JavaScript URL (jx) + exports.javascriptURL = + /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + // Catches /* falls through */ comments (ft) + exports.fallsThrough = + /^\s*\/\*\s*falls?\sthrough\s*\*\/\s*$/; + + // very conservative rule (eg: only one space between the start of the comment and the first character) + // to relax the maxlen option + exports.maxlenException = /^(?:(?:\/\/|\/\*|\*) ?)?[^ ]+$/; + }, + {}, + ], + 17: [ + function (require, module, exports) { + "use strict"; + + var state = { + syntax: {}, + + reset: function () { + this.tokens = { + prev: null, + next: null, + curr: null, + }; + + this.option = {}; + this.ignored = {}; + this.directive = {}; + this.jsonMode = false; + this.jsonWarnings = []; + this.lines = []; + this.tab = ""; + this.cache = {}; // Node.JS doesn't have Map. Sniff. + this.ignoreLinterErrors = false; // Blank out non-multi-line-commented + // lines when ignoring linter errors + }, + }; + + exports.state = state; + }, + {}, + ], + 18: [ + function (require, module, exports) { + "use strict"; + + exports.register = function (linter) { + // Check for properties named __proto__. This special property was + // deprecated and then re-introduced for ES6. + + linter.on("Identifier", function style_scanProto(data) { + if (linter.getOption("proto")) { + return; + } + + if (data.name === "__proto__") { + linter.warn("W103", { + line: data.line, + char: data.char, + data: [data.name], + }); + } + }); + + // Check for properties named __iterator__. This is a special property + // available only in browsers with JavaScript 1.7 implementation. + + linter.on( + "Identifier", + function style_scanIterator(data) { + if (linter.getOption("iterator")) { + return; + } + + if (data.name === "__iterator__") { + linter.warn("W104", { + line: data.line, + char: data.char, + data: [data.name], + }); + } + }, + ); + + // Check for dangling underscores. + + linter.on( + "Identifier", + function style_scanDangling(data) { + if (!linter.getOption("nomen")) { + return; + } + + // Underscore.js + if (data.name === "_") { + return; + } + + // In Node, __dirname and __filename should be ignored. + if (linter.getOption("node")) { + if ( + /^(__dirname|__filename)$/.test( + data.name, + ) && + !data.isProperty + ) { + return; + } + } + + if (/^(_+.*|.*_+)$/.test(data.name)) { + linter.warn("W105", { + line: data.line, + char: data.from, + data: ["dangling '_'", data.name], + }); + } + }, + ); + + // Check that all identifiers are using camelCase notation. + // Exceptions: names like MY_VAR and _myVar. + + linter.on( + "Identifier", + function style_scanCamelCase(data) { + if (!linter.getOption("camelcase")) { + return; + } + + if ( + data.name + .replace(/^_+|_+$/g, "") + .indexOf("_") > -1 && + !data.name.match(/^[A-Z0-9_]*$/) + ) { + linter.warn("W106", { + line: data.line, + char: data.from, + data: [data.name], + }); + } + }, + ); + + // Enforce consistency in style of quoting. + + linter.on("String", function style_scanQuotes(data) { + var quotmark = linter.getOption("quotmark"); + var code; + + if (!quotmark) { + return; + } + + // If quotmark is set to 'single' warn about all double-quotes. + + if (quotmark === "single" && data.quote !== "'") { + code = "W109"; + } + + // If quotmark is set to 'double' warn about all single-quotes. + + if (quotmark === "double" && data.quote !== '"') { + code = "W108"; + } + + // If quotmark is set to true, remember the first quotation style + // and then warn about all others. + + if (quotmark === true) { + if (!linter.getCache("quotmark")) { + linter.setCache("quotmark", data.quote); + } + + if ( + linter.getCache("quotmark") !== data.quote + ) { + code = "W110"; + } + } + + if (code) { + linter.warn(code, { + line: data.line, + char: data.char, + }); + } + }); + + linter.on("Number", function style_scanNumbers(data) { + if (data.value.charAt(0) === ".") { + // Warn about a leading decimal point. + linter.warn("W008", { + line: data.line, + char: data.char, + data: [data.value], + }); + } + + if ( + data.value.substr(data.value.length - 1) === "." + ) { + // Warn about a trailing decimal point. + linter.warn("W047", { + line: data.line, + char: data.char, + data: [data.value], + }); + } + + if (/^00+/.test(data.value)) { + // Multiple leading zeroes. + linter.warn("W046", { + line: data.line, + char: data.char, + data: [data.value], + }); + } + }); + + // Warn about script URLs. + + linter.on( + "String", + function style_scanJavaScriptURLs(data) { + var re = + /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; + + if (linter.getOption("scripturl")) { + return; + } + + if (re.test(data.value)) { + linter.warn("W107", { + line: data.line, + char: data.char, + }); + } + }, + ); + }; + }, + {}, + ], + 19: [ + function (require, module, exports) { + // jshint -W001 + + "use strict"; + + // Identifiers provided by the ECMAScript standard. + + exports.reservedVars = { + arguments: false, + NaN: false, + }; + + exports.ecmaIdentifiers = { + Array: false, + Boolean: false, + Date: false, + decodeURI: false, + decodeURIComponent: false, + encodeURI: false, + encodeURIComponent: false, + Error: false, + eval: false, + EvalError: false, + Function: false, + hasOwnProperty: false, + isFinite: false, + isNaN: false, + JSON: false, + Math: false, + Map: false, + Number: false, + Object: false, + parseInt: false, + parseFloat: false, + RangeError: false, + ReferenceError: false, + RegExp: false, + Set: false, + String: false, + SyntaxError: false, + TypeError: false, + URIError: false, + WeakMap: false, + }; + + // Global variables commonly provided by a web browser environment. + + exports.browser = { + Audio: false, + Blob: false, + addEventListener: false, + applicationCache: false, + atob: false, + blur: false, + btoa: false, + CanvasGradient: false, + CanvasPattern: false, + CanvasRenderingContext2D: false, + clearInterval: false, + clearTimeout: false, + close: false, + closed: false, + CustomEvent: false, + DOMParser: false, + defaultStatus: false, + document: false, + Element: false, + ElementTimeControl: false, + event: false, + FileReader: false, + FormData: false, + focus: false, + frames: false, + getComputedStyle: false, + HTMLElement: false, + HTMLAnchorElement: false, + HTMLBaseElement: false, + HTMLBlockquoteElement: false, + HTMLBodyElement: false, + HTMLBRElement: false, + HTMLButtonElement: false, + HTMLCanvasElement: false, + HTMLDirectoryElement: false, + HTMLDivElement: false, + HTMLDListElement: false, + HTMLFieldSetElement: false, + HTMLFontElement: false, + HTMLFormElement: false, + HTMLFrameElement: false, + HTMLFrameSetElement: false, + HTMLHeadElement: false, + HTMLHeadingElement: false, + HTMLHRElement: false, + HTMLHtmlElement: false, + HTMLIFrameElement: false, + HTMLImageElement: false, + HTMLInputElement: false, + HTMLIsIndexElement: false, + HTMLLabelElement: false, + HTMLLayerElement: false, + HTMLLegendElement: false, + HTMLLIElement: false, + HTMLLinkElement: false, + HTMLMapElement: false, + HTMLMenuElement: false, + HTMLMetaElement: false, + HTMLModElement: false, + HTMLObjectElement: false, + HTMLOListElement: false, + HTMLOptGroupElement: false, + HTMLOptionElement: false, + HTMLParagraphElement: false, + HTMLParamElement: false, + HTMLPreElement: false, + HTMLQuoteElement: false, + HTMLScriptElement: false, + HTMLSelectElement: false, + HTMLStyleElement: false, + HTMLTableCaptionElement: false, + HTMLTableCellElement: false, + HTMLTableColElement: false, + HTMLTableElement: false, + HTMLTableRowElement: false, + HTMLTableSectionElement: false, + HTMLTextAreaElement: false, + HTMLTitleElement: false, + HTMLUListElement: false, + HTMLVideoElement: false, + history: false, + Image: false, + length: false, + localStorage: false, + location: false, + matchMedia: false, + MessageChannel: false, + MessageEvent: false, + MessagePort: false, + MouseEvent: false, + moveBy: false, + moveTo: false, + MutationObserver: false, + name: false, + Node: false, + NodeFilter: false, + navigator: false, + onbeforeunload: true, + onblur: true, + onerror: true, + onfocus: true, + onload: true, + onresize: true, + onunload: true, + open: false, + openDatabase: false, + opener: false, + Option: false, + parent: false, + print: false, + removeEventListener: false, + resizeBy: false, + resizeTo: false, + screen: false, + scroll: false, + scrollBy: false, + scrollTo: false, + sessionStorage: false, + setInterval: false, + setTimeout: false, + SharedWorker: false, + status: false, + SVGAElement: false, + SVGAltGlyphDefElement: false, + SVGAltGlyphElement: false, + SVGAltGlyphItemElement: false, + SVGAngle: false, + SVGAnimateColorElement: false, + SVGAnimateElement: false, + SVGAnimateMotionElement: false, + SVGAnimateTransformElement: false, + SVGAnimatedAngle: false, + SVGAnimatedBoolean: false, + SVGAnimatedEnumeration: false, + SVGAnimatedInteger: false, + SVGAnimatedLength: false, + SVGAnimatedLengthList: false, + SVGAnimatedNumber: false, + SVGAnimatedNumberList: false, + SVGAnimatedPathData: false, + SVGAnimatedPoints: false, + SVGAnimatedPreserveAspectRatio: false, + SVGAnimatedRect: false, + SVGAnimatedString: false, + SVGAnimatedTransformList: false, + SVGAnimationElement: false, + SVGCSSRule: false, + SVGCircleElement: false, + SVGClipPathElement: false, + SVGColor: false, + SVGColorProfileElement: false, + SVGColorProfileRule: false, + SVGComponentTransferFunctionElement: false, + SVGCursorElement: false, + SVGDefsElement: false, + SVGDescElement: false, + SVGDocument: false, + SVGElement: false, + SVGElementInstance: false, + SVGElementInstanceList: false, + SVGEllipseElement: false, + SVGExternalResourcesRequired: false, + SVGFEBlendElement: false, + SVGFEColorMatrixElement: false, + SVGFEComponentTransferElement: false, + SVGFECompositeElement: false, + SVGFEConvolveMatrixElement: false, + SVGFEDiffuseLightingElement: false, + SVGFEDisplacementMapElement: false, + SVGFEDistantLightElement: false, + SVGFEFloodElement: false, + SVGFEFuncAElement: false, + SVGFEFuncBElement: false, + SVGFEFuncGElement: false, + SVGFEFuncRElement: false, + SVGFEGaussianBlurElement: false, + SVGFEImageElement: false, + SVGFEMergeElement: false, + SVGFEMergeNodeElement: false, + SVGFEMorphologyElement: false, + SVGFEOffsetElement: false, + SVGFEPointLightElement: false, + SVGFESpecularLightingElement: false, + SVGFESpotLightElement: false, + SVGFETileElement: false, + SVGFETurbulenceElement: false, + SVGFilterElement: false, + SVGFilterPrimitiveStandardAttributes: false, + SVGFitToViewBox: false, + SVGFontElement: false, + SVGFontFaceElement: false, + SVGFontFaceFormatElement: false, + SVGFontFaceNameElement: false, + SVGFontFaceSrcElement: false, + SVGFontFaceUriElement: false, + SVGForeignObjectElement: false, + SVGGElement: false, + SVGGlyphElement: false, + SVGGlyphRefElement: false, + SVGGradientElement: false, + SVGHKernElement: false, + SVGICCColor: false, + SVGImageElement: false, + SVGLangSpace: false, + SVGLength: false, + SVGLengthList: false, + SVGLineElement: false, + SVGLinearGradientElement: false, + SVGLocatable: false, + SVGMPathElement: false, + SVGMarkerElement: false, + SVGMaskElement: false, + SVGMatrix: false, + SVGMetadataElement: false, + SVGMissingGlyphElement: false, + SVGNumber: false, + SVGNumberList: false, + SVGPaint: false, + SVGPathElement: false, + SVGPathSeg: false, + SVGPathSegArcAbs: false, + SVGPathSegArcRel: false, + SVGPathSegClosePath: false, + SVGPathSegCurvetoCubicAbs: false, + SVGPathSegCurvetoCubicRel: false, + SVGPathSegCurvetoCubicSmoothAbs: false, + SVGPathSegCurvetoCubicSmoothRel: false, + SVGPathSegCurvetoQuadraticAbs: false, + SVGPathSegCurvetoQuadraticRel: false, + SVGPathSegCurvetoQuadraticSmoothAbs: false, + SVGPathSegCurvetoQuadraticSmoothRel: false, + SVGPathSegLinetoAbs: false, + SVGPathSegLinetoHorizontalAbs: false, + SVGPathSegLinetoHorizontalRel: false, + SVGPathSegLinetoRel: false, + SVGPathSegLinetoVerticalAbs: false, + SVGPathSegLinetoVerticalRel: false, + SVGPathSegList: false, + SVGPathSegMovetoAbs: false, + SVGPathSegMovetoRel: false, + SVGPatternElement: false, + SVGPoint: false, + SVGPointList: false, + SVGPolygonElement: false, + SVGPolylineElement: false, + SVGPreserveAspectRatio: false, + SVGRadialGradientElement: false, + SVGRect: false, + SVGRectElement: false, + SVGRenderingIntent: false, + SVGSVGElement: false, + SVGScriptElement: false, + SVGSetElement: false, + SVGStopElement: false, + SVGStringList: false, + SVGStylable: false, + SVGStyleElement: false, + SVGSwitchElement: false, + SVGSymbolElement: false, + SVGTRefElement: false, + SVGTSpanElement: false, + SVGTests: false, + SVGTextContentElement: false, + SVGTextElement: false, + SVGTextPathElement: false, + SVGTextPositioningElement: false, + SVGTitleElement: false, + SVGTransform: false, + SVGTransformList: false, + SVGTransformable: false, + SVGURIReference: false, + SVGUnitTypes: false, + SVGUseElement: false, + SVGVKernElement: false, + SVGViewElement: false, + SVGViewSpec: false, + SVGZoomAndPan: false, + TimeEvent: false, + top: false, + URL: false, + WebSocket: false, + window: false, + Worker: false, + XMLHttpRequest: false, + XMLSerializer: false, + XPathEvaluator: false, + XPathException: false, + XPathExpression: false, + XPathNamespace: false, + XPathNSResolver: false, + XPathResult: false, + }; + + exports.devel = { + alert: false, + confirm: false, + console: false, + Debug: false, + opera: false, + prompt: false, + }; + + exports.worker = { + importScripts: true, + postMessage: true, + self: true, + }; + + // Widely adopted global names that are not part of ECMAScript standard + exports.nonstandard = { + escape: false, + unescape: false, + }; + + // Globals provided by popular JavaScript environments. + + exports.couch = { + require: false, + respond: false, + getRow: false, + emit: false, + send: false, + start: false, + sum: false, + log: false, + exports: false, + module: false, + provides: false, + }; + + exports.node = { + __filename: false, + __dirname: false, + GLOBAL: false, + global: false, + module: false, + require: false, + + // These globals are writeable because Node allows the following + // usage pattern: var Buffer = require("buffer").Buffer; + + Buffer: true, + console: true, + exports: true, + process: true, + setTimeout: true, + clearTimeout: true, + setInterval: true, + clearInterval: true, + setImmediate: true, // v0.9.1+ + clearImmediate: true, // v0.9.1+ + }; + + exports.phantom = { + phantom: true, + require: true, + WebPage: true, + console: true, // in examples, but undocumented + exports: true, // v1.7+ + }; + + exports.rhino = { + defineClass: false, + deserialize: false, + gc: false, + help: false, + importPackage: false, + java: false, + load: false, + loadClass: false, + print: false, + quit: false, + readFile: false, + readUrl: false, + runCommand: false, + seal: false, + serialize: false, + spawn: false, + sync: false, + toint32: false, + version: false, + }; + + exports.shelljs = { + target: false, + echo: false, + exit: false, + cd: false, + pwd: false, + ls: false, + find: false, + cp: false, + rm: false, + mv: false, + mkdir: false, + test: false, + cat: false, + sed: false, + grep: false, + which: false, + dirs: false, + pushd: false, + popd: false, + env: false, + exec: false, + chmod: false, + config: false, + error: false, + tempdir: false, + }; + + exports.typed = { + ArrayBuffer: false, + ArrayBufferView: false, + DataView: false, + Float32Array: false, + Float64Array: false, + Int16Array: false, + Int32Array: false, + Int8Array: false, + Uint16Array: false, + Uint32Array: false, + Uint8Array: false, + Uint8ClampedArray: false, + }; + + exports.wsh = { + ActiveXObject: true, + Enumerator: true, + GetObject: true, + ScriptEngine: true, + ScriptEngineBuildVersion: true, + ScriptEngineMajorVersion: true, + ScriptEngineMinorVersion: true, + VBArray: true, + WSH: true, + WScript: true, + XDomainRequest: true, + }; + + // Globals provided by popular JavaScript libraries. + + exports.dojo = { + dojo: false, + dijit: false, + dojox: false, + define: false, + require: false, + }; + + exports.jquery = { + $: false, + jQuery: false, + }; + + exports.mootools = { + $: false, + $$: false, + Asset: false, + Browser: false, + Chain: false, + Class: false, + Color: false, + Cookie: false, + Core: false, + Document: false, + DomReady: false, + DOMEvent: false, + DOMReady: false, + Drag: false, + Element: false, + Elements: false, + Event: false, + Events: false, + Fx: false, + Group: false, + Hash: false, + HtmlTable: false, + Iframe: false, + IframeShim: false, + InputValidator: false, + instanceOf: false, + Keyboard: false, + Locale: false, + Mask: false, + MooTools: false, + Native: false, + Options: false, + OverText: false, + Request: false, + Scroller: false, + Slick: false, + Slider: false, + Sortables: false, + Spinner: false, + Swiff: false, + Tips: false, + Type: false, + typeOf: false, + URI: false, + Window: false, + }; + + exports.prototypejs = { + $: false, + $$: false, + $A: false, + $F: false, + $H: false, + $R: false, + $break: false, + $continue: false, + $w: false, + Abstract: false, + Ajax: false, + Class: false, + Enumerable: false, + Element: false, + Event: false, + Field: false, + Form: false, + Hash: false, + Insertion: false, + ObjectRange: false, + PeriodicalExecuter: false, + Position: false, + Prototype: false, + Selector: false, + Template: false, + Toggle: false, + Try: false, + Autocompleter: false, + Builder: false, + Control: false, + Draggable: false, + Draggables: false, + Droppables: false, + Effect: false, + Sortable: false, + SortableObserver: false, + Sound: false, + Scriptaculous: false, + }; + + exports.yui = { + YUI: false, + Y: false, + YUI_config: false, + }; + }, + {}, + ], + }, + {}, + ["nr+AlQ"], + ); + JSHINT = require("jshint").JSHINT; + if (typeof exports === "object" && exports) exports.JSHINT = JSHINT; })(); - -process.title = 'browser'; -process.browser = true; -process.env = {}; -process.argv = []; - -process.binding = function (name) { - throw new Error('process.binding is not supported'); -} - -// TODO(shtylman) -process.cwd = function () { return '/' }; -process.chdir = function (dir) { - throw new Error('process.chdir is not supported'); -}; - -},{}],8:[function(require,module,exports){ -module.exports = function isBuffer(arg) { - return arg && typeof arg === 'object' - && typeof arg.copy === 'function' - && typeof arg.fill === 'function' - && typeof arg.readUInt8 === 'function'; -} -},{}],9:[function(require,module,exports){ -var process=require("__browserify_process"),global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};// Copyright Joyent, Inc. and other Node contributors. -// -// 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. -// -// 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. - -var formatRegExp = /%[sdj%]/g; -exports.format = function(f) { - if (!isString(f)) { - var objects = []; - for (var i = 0; i < arguments.length; i++) { - objects.push(inspect(arguments[i])); - } - return objects.join(' '); - } - - var i = 1; - var args = arguments; - var len = args.length; - var str = String(f).replace(formatRegExp, function(x) { - if (x === '%%') return '%'; - if (i >= len) return x; - switch (x) { - case '%s': return String(args[i++]); - case '%d': return Number(args[i++]); - case '%j': - try { - return JSON.stringify(args[i++]); - } catch (_) { - return '[Circular]'; - } - default: - return x; - } - }); - for (var x = args[i]; i < len; x = args[++i]) { - if (isNull(x) || !isObject(x)) { - str += ' ' + x; - } else { - str += ' ' + inspect(x); - } - } - return str; -}; - - -// Mark that a method should not be used. -// Returns a modified function which warns once by default. -// If --no-deprecation is set, then it is a no-op. -exports.deprecate = function(fn, msg) { - // Allow for deprecating things in the process of starting up. - if (isUndefined(global.process)) { - return function() { - return exports.deprecate(fn, msg).apply(this, arguments); - }; - } - - if (process.noDeprecation === true) { - return fn; - } - - var warned = false; - function deprecated() { - if (!warned) { - if (process.throwDeprecation) { - throw new Error(msg); - } else if (process.traceDeprecation) { - console.trace(msg); - } else { - console.error(msg); - } - warned = true; - } - return fn.apply(this, arguments); - } - - return deprecated; -}; - - -var debugs = {}; -var debugEnviron; -exports.debuglog = function(set) { - if (isUndefined(debugEnviron)) - debugEnviron = process.env.NODE_DEBUG || ''; - set = set.toUpperCase(); - if (!debugs[set]) { - if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { - var pid = process.pid; - debugs[set] = function() { - var msg = exports.format.apply(exports, arguments); - console.error('%s %d: %s', set, pid, msg); - }; - } else { - debugs[set] = function() {}; - } - } - return debugs[set]; -}; - - -/** - * Echos the value of a value. Tries to print the value out - * in the best way possible given the different types. - * - * @param {Object} obj The object to print out. - * @param {Object} opts Optional options object that alters the output. - */ -/* legacy: obj, showHidden, depth, colors*/ -function inspect(obj, opts) { - // default options - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - // legacy... - if (arguments.length >= 3) ctx.depth = arguments[2]; - if (arguments.length >= 4) ctx.colors = arguments[3]; - if (isBoolean(opts)) { - // legacy... - ctx.showHidden = opts; - } else if (opts) { - // got an "options" object - exports._extend(ctx, opts); - } - // set default options - if (isUndefined(ctx.showHidden)) ctx.showHidden = false; - if (isUndefined(ctx.depth)) ctx.depth = 2; - if (isUndefined(ctx.colors)) ctx.colors = false; - if (isUndefined(ctx.customInspect)) ctx.customInspect = true; - if (ctx.colors) ctx.stylize = stylizeWithColor; - return formatValue(ctx, obj, ctx.depth); -} -exports.inspect = inspect; - - -// http://en.wikipedia.org/wiki/ANSI_escape_code#graphics -inspect.colors = { - 'bold' : [1, 22], - 'italic' : [3, 23], - 'underline' : [4, 24], - 'inverse' : [7, 27], - 'white' : [37, 39], - 'grey' : [90, 39], - 'black' : [30, 39], - 'blue' : [34, 39], - 'cyan' : [36, 39], - 'green' : [32, 39], - 'magenta' : [35, 39], - 'red' : [31, 39], - 'yellow' : [33, 39] -}; - -// Don't use 'blue' not visible on cmd.exe -inspect.styles = { - 'special': 'cyan', - 'number': 'yellow', - 'boolean': 'yellow', - 'undefined': 'grey', - 'null': 'bold', - 'string': 'green', - 'date': 'magenta', - // "name": intentionally not styling - 'regexp': 'red' -}; - - -function stylizeWithColor(str, styleType) { - var style = inspect.styles[styleType]; - - if (style) { - return '\u001b[' + inspect.colors[style][0] + 'm' + str + - '\u001b[' + inspect.colors[style][1] + 'm'; - } else { - return str; - } -} - - -function stylizeNoColor(str, styleType) { - return str; -} - - -function arrayToHash(array) { - var hash = {}; - - array.forEach(function(val, idx) { - hash[val] = true; - }); - - return hash; -} - - -function formatValue(ctx, value, recurseTimes) { - // Provide a hook for user-specified inspect functions. - // Check that value is an object with an inspect function on it - if (ctx.customInspect && - value && - isFunction(value.inspect) && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes, ctx); - if (!isString(ret)) { - ret = formatValue(ctx, ret, recurseTimes); - } - return ret; - } - - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // Look up the keys of the object. - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - if (ctx.showHidden) { - keys = Object.getOwnPropertyNames(value); - } - - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx - if (isError(value) - && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { - return formatError(value); - } - - // Some type of object without properties can be shortcutted. - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); -} - - -function formatPrimitive(ctx, value) { - if (isUndefined(value)) - return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) - return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) - return ctx.stylize('' + value, 'boolean'); - // For some reason typeof null is "object", so special case here. - if (isNull(value)) - return ctx.stylize('null', 'null'); -} - - -function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; -} - - -function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; -} - - -function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } - } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (isUndefined(name)) { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; -} - - -function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; -} - - -// NOTE: These type checking functions intentionally don't use `instanceof` -// because it is fragile and can be easily faked with `Object.create()`. -function isArray(ar) { - return Array.isArray(ar); -} -exports.isArray = isArray; - -function isBoolean(arg) { - return typeof arg === 'boolean'; -} -exports.isBoolean = isBoolean; - -function isNull(arg) { - return arg === null; -} -exports.isNull = isNull; - -function isNullOrUndefined(arg) { - return arg == null; -} -exports.isNullOrUndefined = isNullOrUndefined; - -function isNumber(arg) { - return typeof arg === 'number'; -} -exports.isNumber = isNumber; - -function isString(arg) { - return typeof arg === 'string'; -} -exports.isString = isString; - -function isSymbol(arg) { - return typeof arg === 'symbol'; -} -exports.isSymbol = isSymbol; - -function isUndefined(arg) { - return arg === void 0; -} -exports.isUndefined = isUndefined; - -function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; -} -exports.isRegExp = isRegExp; - -function isObject(arg) { - return typeof arg === 'object' && arg !== null; -} -exports.isObject = isObject; - -function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; -} -exports.isDate = isDate; - -function isError(e) { - return isObject(e) && - (objectToString(e) === '[object Error]' || e instanceof Error); -} -exports.isError = isError; - -function isFunction(arg) { - return typeof arg === 'function'; -} -exports.isFunction = isFunction; - -function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; -} -exports.isPrimitive = isPrimitive; - -exports.isBuffer = require('./support/isBuffer'); - -function objectToString(o) { - return Object.prototype.toString.call(o); -} - - -function pad(n) { - return n < 10 ? '0' + n.toString(10) : n.toString(10); -} - - -var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', - 'Oct', 'Nov', 'Dec']; - -// 26 Feb 16:19:34 -function timestamp() { - var d = new Date(); - var time = [pad(d.getHours()), - pad(d.getMinutes()), - pad(d.getSeconds())].join(':'); - return [d.getDate(), months[d.getMonth()], time].join(' '); -} - - -// log is just a thin wrapper to console.log that prepends a timestamp -exports.log = function() { - console.log('%s - %s', timestamp(), exports.format.apply(exports, arguments)); -}; - - -/** - * Inherit the prototype methods from one constructor into another. - * - * The Function.prototype.inherits from lang.js rewritten as a standalone - * function (not on Function.prototype). NOTE: If this file is to be loaded - * during bootstrapping this function needs to be rewritten using some native - * functions as prototype setup using normal JavaScript does not work as - * expected during bootstrapping (see mirror.js in r114903). - * - * @param {function} ctor Constructor function which needs to inherit the - * prototype. - * @param {function} superCtor Constructor function to inherit prototype from. - */ -exports.inherits = require('inherits'); - -exports._extend = function(origin, add) { - // Don't do anything if add isn't an object - if (!add || !isObject(add)) return origin; - - var keys = Object.keys(add); - var i = keys.length; - while (i--) { - origin[keys[i]] = add[keys[i]]; - } - return origin; -}; - -function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); -} - -},{"./support/isBuffer":8,"__browserify_process":7,"inherits":6}],10:[function(require,module,exports){ -var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};/*global window, global*/ -var util = require("util") -var assert = require("assert") - -var slice = Array.prototype.slice -var console -var times = {} - -if (typeof global !== "undefined" && global.console) { - console = global.console -} else if (typeof window !== "undefined" && window.console) { - console = window.console -} else { - console = window.console = {} -} - -var functions = [ - [log, "log"] - , [info, "info"] - , [warn, "warn"] - , [error, "error"] - , [time, "time"] - , [timeEnd, "timeEnd"] - , [trace, "trace"] - , [dir, "dir"] - , [assert, "assert"] -] - -for (var i = 0; i < functions.length; i++) { - var tuple = functions[i] - var f = tuple[0] - var name = tuple[1] - - if (!console[name]) { - console[name] = f - } -} - -module.exports = console - -function log() {} - -function info() { - console.log.apply(console, arguments) -} - -function warn() { - console.log.apply(console, arguments) -} - -function error() { - console.warn.apply(console, arguments) -} - -function time(label) { - times[label] = Date.now() -} - -function timeEnd(label) { - var time = times[label] - if (!time) { - throw new Error("No such label: " + label) - } - - var duration = Date.now() - time - console.log(label + ": " + duration + "ms") -} - -function trace() { - var err = new Error() - err.name = "Trace" - err.message = util.format.apply(null, arguments) - console.error(err.stack) -} - -function dir(object) { - console.log(util.inspect(object) + "\n") -} - -function assert(expression) { - if (!expression) { - var arr = slice.call(arguments, 1) - assert.ok(false, util.format.apply(null, arr)) - } -} - -},{"assert":4,"util":9}],11:[function(require,module,exports){ -// Underscore.js 1.4.4 -// http://underscorejs.org -// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. -// Underscore may be freely distributed under the MIT license. - -(function() { - - // Baseline setup - // -------------- - - // Establish the root object, `window` in the browser, or `global` on the server. - var root = this; - - // Save the previous value of the `_` variable. - var previousUnderscore = root._; - - // Establish the object that gets returned to break out of a loop iteration. - var breaker = {}; - - // Save bytes in the minified (but not gzipped) version: - var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; - - // Create quick reference variables for speed access to core prototypes. - var push = ArrayProto.push, - slice = ArrayProto.slice, - concat = ArrayProto.concat, - toString = ObjProto.toString, - hasOwnProperty = ObjProto.hasOwnProperty; - - // All **ECMAScript 5** native function implementations that we hope to use - // are declared here. - var - nativeForEach = ArrayProto.forEach, - nativeMap = ArrayProto.map, - nativeReduce = ArrayProto.reduce, - nativeReduceRight = ArrayProto.reduceRight, - nativeFilter = ArrayProto.filter, - nativeEvery = ArrayProto.every, - nativeSome = ArrayProto.some, - nativeIndexOf = ArrayProto.indexOf, - nativeLastIndexOf = ArrayProto.lastIndexOf, - nativeIsArray = Array.isArray, - nativeKeys = Object.keys, - nativeBind = FuncProto.bind; - - // Create a safe reference to the Underscore object for use below. - var _ = function(obj) { - if (obj instanceof _) return obj; - if (!(this instanceof _)) return new _(obj); - this._wrapped = obj; - }; - - // Export the Underscore object for **Node.js**, with - // backwards-compatibility for the old `require()` API. If we're in - // the browser, add `_` as a global object via a string identifier, - // for Closure Compiler "advanced" mode. - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = _; - } - exports._ = _; - } else { - root._ = _; - } - - // Current version. - _.VERSION = '1.4.4'; - - // Collection Functions - // -------------------- - - // The cornerstone, an `each` implementation, aka `forEach`. - // Handles objects with the built-in `forEach`, arrays, and raw objects. - // Delegates to **ECMAScript 5**'s native `forEach` if available. - var each = _.each = _.forEach = function(obj, iterator, context) { - if (obj == null) return; - if (nativeForEach && obj.forEach === nativeForEach) { - obj.forEach(iterator, context); - } else if (obj.length === +obj.length) { - for (var i = 0, l = obj.length; i < l; i++) { - if (iterator.call(context, obj[i], i, obj) === breaker) return; - } - } else { - for (var key in obj) { - if (_.has(obj, key)) { - if (iterator.call(context, obj[key], key, obj) === breaker) return; - } - } - } - }; - - // Return the results of applying the iterator to each element. - // Delegates to **ECMAScript 5**'s native `map` if available. - _.map = _.collect = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); - each(obj, function(value, index, list) { - results[results.length] = iterator.call(context, value, index, list); - }); - return results; - }; - - var reduceError = 'Reduce of empty array with no initial value'; - - // **Reduce** builds up a single result from a list of values, aka `inject`, - // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. - _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduce && obj.reduce === nativeReduce) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); - } - each(obj, function(value, index, list) { - if (!initial) { - memo = value; - initial = true; - } else { - memo = iterator.call(context, memo, value, index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; - - // The right-associative version of reduce, also known as `foldr`. - // Delegates to **ECMAScript 5**'s native `reduceRight` if available. - _.reduceRight = _.foldr = function(obj, iterator, memo, context) { - var initial = arguments.length > 2; - if (obj == null) obj = []; - if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { - if (context) iterator = _.bind(iterator, context); - return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); - } - var length = obj.length; - if (length !== +length) { - var keys = _.keys(obj); - length = keys.length; - } - each(obj, function(value, index, list) { - index = keys ? keys[--length] : --length; - if (!initial) { - memo = obj[index]; - initial = true; - } else { - memo = iterator.call(context, memo, obj[index], index, list); - } - }); - if (!initial) throw new TypeError(reduceError); - return memo; - }; - - // Return the first value which passes a truth test. Aliased as `detect`. - _.find = _.detect = function(obj, iterator, context) { - var result; - any(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) { - result = value; - return true; - } - }); - return result; - }; - - // Return all the elements that pass a truth test. - // Delegates to **ECMAScript 5**'s native `filter` if available. - // Aliased as `select`. - _.filter = _.select = function(obj, iterator, context) { - var results = []; - if (obj == null) return results; - if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); - each(obj, function(value, index, list) { - if (iterator.call(context, value, index, list)) results[results.length] = value; - }); - return results; - }; - - // Return all the elements for which a truth test fails. - _.reject = function(obj, iterator, context) { - return _.filter(obj, function(value, index, list) { - return !iterator.call(context, value, index, list); - }, context); - }; - - // Determine whether all of the elements match a truth test. - // Delegates to **ECMAScript 5**'s native `every` if available. - // Aliased as `all`. - _.every = _.all = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = true; - if (obj == null) return result; - if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); - each(obj, function(value, index, list) { - if (!(result = result && iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if at least one element in the object matches a truth test. - // Delegates to **ECMAScript 5**'s native `some` if available. - // Aliased as `any`. - var any = _.some = _.any = function(obj, iterator, context) { - iterator || (iterator = _.identity); - var result = false; - if (obj == null) return result; - if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); - each(obj, function(value, index, list) { - if (result || (result = iterator.call(context, value, index, list))) return breaker; - }); - return !!result; - }; - - // Determine if the array or object contains a given value (using `===`). - // Aliased as `include`. - _.contains = _.include = function(obj, target) { - if (obj == null) return false; - if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; - return any(obj, function(value) { - return value === target; - }); - }; - - // Invoke a method (with arguments) on every item in a collection. - _.invoke = function(obj, method) { - var args = slice.call(arguments, 2); - var isFunc = _.isFunction(method); - return _.map(obj, function(value) { - return (isFunc ? method : value[method]).apply(value, args); - }); - }; - - // Convenience version of a common use case of `map`: fetching a property. - _.pluck = function(obj, key) { - return _.map(obj, function(value){ return value[key]; }); - }; - - // Convenience version of a common use case of `filter`: selecting only objects - // containing specific `key:value` pairs. - _.where = function(obj, attrs, first) { - if (_.isEmpty(attrs)) return first ? null : []; - return _[first ? 'find' : 'filter'](obj, function(value) { - for (var key in attrs) { - if (attrs[key] !== value[key]) return false; - } - return true; - }); - }; - - // Convenience version of a common use case of `find`: getting the first object - // containing specific `key:value` pairs. - _.findWhere = function(obj, attrs) { - return _.where(obj, attrs, true); - }; - - // Return the maximum element or (element-based computation). - // Can't optimize arrays of integers longer than 65,535 elements. - // See: https://bugs.webkit.org/show_bug.cgi?id=80797 - _.max = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.max.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return -Infinity; - var result = {computed : -Infinity, value: -Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed >= result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Return the minimum element (or element-based computation). - _.min = function(obj, iterator, context) { - if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { - return Math.min.apply(Math, obj); - } - if (!iterator && _.isEmpty(obj)) return Infinity; - var result = {computed : Infinity, value: Infinity}; - each(obj, function(value, index, list) { - var computed = iterator ? iterator.call(context, value, index, list) : value; - computed < result.computed && (result = {value : value, computed : computed}); - }); - return result.value; - }; - - // Shuffle an array. - _.shuffle = function(obj) { - var rand; - var index = 0; - var shuffled = []; - each(obj, function(value) { - rand = _.random(index++); - shuffled[index - 1] = shuffled[rand]; - shuffled[rand] = value; - }); - return shuffled; - }; - - // An internal function to generate lookup iterators. - var lookupIterator = function(value) { - return _.isFunction(value) ? value : function(obj){ return obj[value]; }; - }; - - // Sort the object's values by a criterion produced by an iterator. - _.sortBy = function(obj, value, context) { - var iterator = lookupIterator(value); - return _.pluck(_.map(obj, function(value, index, list) { - return { - value : value, - index : index, - criteria : iterator.call(context, value, index, list) - }; - }).sort(function(left, right) { - var a = left.criteria; - var b = right.criteria; - if (a !== b) { - if (a > b || a === void 0) return 1; - if (a < b || b === void 0) return -1; - } - return left.index < right.index ? -1 : 1; - }), 'value'); - }; - - // An internal function used for aggregate "group by" operations. - var group = function(obj, value, context, behavior) { - var result = {}; - var iterator = lookupIterator(value || _.identity); - each(obj, function(value, index) { - var key = iterator.call(context, value, index, obj); - behavior(result, key, value); - }); - return result; - }; - - // Groups the object's values by a criterion. Pass either a string attribute - // to group by, or a function that returns the criterion. - _.groupBy = function(obj, value, context) { - return group(obj, value, context, function(result, key, value) { - (_.has(result, key) ? result[key] : (result[key] = [])).push(value); - }); - }; - - // Counts instances of an object that group by a certain criterion. Pass - // either a string attribute to count by, or a function that returns the - // criterion. - _.countBy = function(obj, value, context) { - return group(obj, value, context, function(result, key) { - if (!_.has(result, key)) result[key] = 0; - result[key]++; - }); - }; - - // Use a comparator function to figure out the smallest index at which - // an object should be inserted so as to maintain order. Uses binary search. - _.sortedIndex = function(array, obj, iterator, context) { - iterator = iterator == null ? _.identity : lookupIterator(iterator); - var value = iterator.call(context, obj); - var low = 0, high = array.length; - while (low < high) { - var mid = (low + high) >>> 1; - iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; - } - return low; - }; - - // Safely convert anything iterable into a real, live array. - _.toArray = function(obj) { - if (!obj) return []; - if (_.isArray(obj)) return slice.call(obj); - if (obj.length === +obj.length) return _.map(obj, _.identity); - return _.values(obj); - }; - - // Return the number of elements in an object. - _.size = function(obj) { - if (obj == null) return 0; - return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; - }; - - // Array Functions - // --------------- - - // Get the first element of an array. Passing **n** will return the first N - // values in the array. Aliased as `head` and `take`. The **guard** check - // allows it to work with `_.map`. - _.first = _.head = _.take = function(array, n, guard) { - if (array == null) return void 0; - return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; - }; - - // Returns everything but the last entry of the array. Especially useful on - // the arguments object. Passing **n** will return all the values in - // the array, excluding the last N. The **guard** check allows it to work with - // `_.map`. - _.initial = function(array, n, guard) { - return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); - }; - - // Get the last element of an array. Passing **n** will return the last N - // values in the array. The **guard** check allows it to work with `_.map`. - _.last = function(array, n, guard) { - if (array == null) return void 0; - if ((n != null) && !guard) { - return slice.call(array, Math.max(array.length - n, 0)); - } else { - return array[array.length - 1]; - } - }; - - // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. - // Especially useful on the arguments object. Passing an **n** will return - // the rest N values in the array. The **guard** - // check allows it to work with `_.map`. - _.rest = _.tail = _.drop = function(array, n, guard) { - return slice.call(array, (n == null) || guard ? 1 : n); - }; - - // Trim out all falsy values from an array. - _.compact = function(array) { - return _.filter(array, _.identity); - }; - - // Internal implementation of a recursive `flatten` function. - var flatten = function(input, shallow, output) { - each(input, function(value) { - if (_.isArray(value)) { - shallow ? push.apply(output, value) : flatten(value, shallow, output); - } else { - output.push(value); - } - }); - return output; - }; - - // Return a completely flattened version of an array. - _.flatten = function(array, shallow) { - return flatten(array, shallow, []); - }; - - // Return a version of the array that does not contain the specified value(s). - _.without = function(array) { - return _.difference(array, slice.call(arguments, 1)); - }; - - // Produce a duplicate-free version of the array. If the array has already - // been sorted, you have the option of using a faster algorithm. - // Aliased as `unique`. - _.uniq = _.unique = function(array, isSorted, iterator, context) { - if (_.isFunction(isSorted)) { - context = iterator; - iterator = isSorted; - isSorted = false; - } - var initial = iterator ? _.map(array, iterator, context) : array; - var results = []; - var seen = []; - each(initial, function(value, index) { - if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { - seen.push(value); - results.push(array[index]); - } - }); - return results; - }; - - // Produce an array that contains the union: each distinct element from all of - // the passed-in arrays. - _.union = function() { - return _.uniq(concat.apply(ArrayProto, arguments)); - }; - - // Produce an array that contains every item shared between all the - // passed-in arrays. - _.intersection = function(array) { - var rest = slice.call(arguments, 1); - return _.filter(_.uniq(array), function(item) { - return _.every(rest, function(other) { - return _.indexOf(other, item) >= 0; - }); - }); - }; - - // Take the difference between one array and a number of other arrays. - // Only the elements present in just the first array will remain. - _.difference = function(array) { - var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); - return _.filter(array, function(value){ return !_.contains(rest, value); }); - }; - - // Zip together multiple lists into a single array -- elements that share - // an index go together. - _.zip = function() { - var args = slice.call(arguments); - var length = _.max(_.pluck(args, 'length')); - var results = new Array(length); - for (var i = 0; i < length; i++) { - results[i] = _.pluck(args, "" + i); - } - return results; - }; - - // Converts lists into objects. Pass either a single array of `[key, value]` - // pairs, or two parallel arrays of the same length -- one of keys, and one of - // the corresponding values. - _.object = function(list, values) { - if (list == null) return {}; - var result = {}; - for (var i = 0, l = list.length; i < l; i++) { - if (values) { - result[list[i]] = values[i]; - } else { - result[list[i][0]] = list[i][1]; - } - } - return result; - }; - - // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), - // we need this function. Return the position of the first occurrence of an - // item in an array, or -1 if the item is not included in the array. - // Delegates to **ECMAScript 5**'s native `indexOf` if available. - // If the array is large and already in sort order, pass `true` - // for **isSorted** to use binary search. - _.indexOf = function(array, item, isSorted) { - if (array == null) return -1; - var i = 0, l = array.length; - if (isSorted) { - if (typeof isSorted == 'number') { - i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); - } else { - i = _.sortedIndex(array, item); - return array[i] === item ? i : -1; - } - } - if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); - for (; i < l; i++) if (array[i] === item) return i; - return -1; - }; - - // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. - _.lastIndexOf = function(array, item, from) { - if (array == null) return -1; - var hasIndex = from != null; - if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { - return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); - } - var i = (hasIndex ? from : array.length); - while (i--) if (array[i] === item) return i; - return -1; - }; - - // Generate an integer Array containing an arithmetic progression. A port of - // the native Python `range()` function. See - // [the Python documentation](http://docs.python.org/library/functions.html#range). - _.range = function(start, stop, step) { - if (arguments.length <= 1) { - stop = start || 0; - start = 0; - } - step = arguments[2] || 1; - - var len = Math.max(Math.ceil((stop - start) / step), 0); - var idx = 0; - var range = new Array(len); - - while(idx < len) { - range[idx++] = start; - start += step; - } - - return range; - }; - - // Function (ahem) Functions - // ------------------ - - // Create a function bound to a given object (assigning `this`, and arguments, - // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if - // available. - _.bind = function(func, context) { - if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); - var args = slice.call(arguments, 2); - return function() { - return func.apply(context, args.concat(slice.call(arguments))); - }; - }; - - // Partially apply a function by creating a version that has had some of its - // arguments pre-filled, without changing its dynamic `this` context. - _.partial = function(func) { - var args = slice.call(arguments, 1); - return function() { - return func.apply(this, args.concat(slice.call(arguments))); - }; - }; - - // Bind all of an object's methods to that object. Useful for ensuring that - // all callbacks defined on an object belong to it. - _.bindAll = function(obj) { - var funcs = slice.call(arguments, 1); - if (funcs.length === 0) funcs = _.functions(obj); - each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); - return obj; - }; - - // Memoize an expensive function by storing its results. - _.memoize = function(func, hasher) { - var memo = {}; - hasher || (hasher = _.identity); - return function() { - var key = hasher.apply(this, arguments); - return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); - }; - }; - - // Delays a function for the given number of milliseconds, and then calls - // it with the arguments supplied. - _.delay = function(func, wait) { - var args = slice.call(arguments, 2); - return setTimeout(function(){ return func.apply(null, args); }, wait); - }; - - // Defers a function, scheduling it to run after the current call stack has - // cleared. - _.defer = function(func) { - return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); - }; - - // Returns a function, that, when invoked, will only be triggered at most once - // during a given window of time. - _.throttle = function(func, wait) { - var context, args, timeout, result; - var previous = 0; - var later = function() { - previous = new Date; - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }; - - // Returns a function, that, as long as it continues to be invoked, will not - // be triggered. The function will be called after it stops being called for - // N milliseconds. If `immediate` is passed, trigger the function on the - // leading edge, instead of the trailing. - _.debounce = function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments; - var later = function() { - timeout = null; - if (!immediate) result = func.apply(context, args); - }; - var callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) result = func.apply(context, args); - return result; - }; - }; - - // Returns a function that will be executed at most one time, no matter how - // often you call it. Useful for lazy initialization. - _.once = function(func) { - var ran = false, memo; - return function() { - if (ran) return memo; - ran = true; - memo = func.apply(this, arguments); - func = null; - return memo; - }; - }; - - // Returns the first function passed as an argument to the second, - // allowing you to adjust arguments, run code before and after, and - // conditionally execute the original function. - _.wrap = function(func, wrapper) { - return function() { - var args = [func]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; - }; - - // Returns a function that is the composition of a list of functions, each - // consuming the return value of the function that follows. - _.compose = function() { - var funcs = arguments; - return function() { - var args = arguments; - for (var i = funcs.length - 1; i >= 0; i--) { - args = [funcs[i].apply(this, args)]; - } - return args[0]; - }; - }; - - // Returns a function that will only be executed after being called N times. - _.after = function(times, func) { - if (times <= 0) return func(); - return function() { - if (--times < 1) { - return func.apply(this, arguments); - } - }; - }; - - // Object Functions - // ---------------- - - // Retrieve the names of an object's properties. - // Delegates to **ECMAScript 5**'s native `Object.keys` - _.keys = nativeKeys || function(obj) { - if (obj !== Object(obj)) throw new TypeError('Invalid object'); - var keys = []; - for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; - return keys; - }; - - // Retrieve the values of an object's properties. - _.values = function(obj) { - var values = []; - for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); - return values; - }; - - // Convert an object into a list of `[key, value]` pairs. - _.pairs = function(obj) { - var pairs = []; - for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); - return pairs; - }; - - // Invert the keys and values of an object. The values must be serializable. - _.invert = function(obj) { - var result = {}; - for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; - return result; - }; - - // Return a sorted list of the function names available on the object. - // Aliased as `methods` - _.functions = _.methods = function(obj) { - var names = []; - for (var key in obj) { - if (_.isFunction(obj[key])) names.push(key); - } - return names.sort(); - }; - - // Extend a given object with all the properties in passed-in object(s). - _.extend = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - obj[prop] = source[prop]; - } - } - }); - return obj; - }; - - // Return a copy of the object only containing the whitelisted properties. - _.pick = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - each(keys, function(key) { - if (key in obj) copy[key] = obj[key]; - }); - return copy; - }; - - // Return a copy of the object without the blacklisted properties. - _.omit = function(obj) { - var copy = {}; - var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); - for (var key in obj) { - if (!_.contains(keys, key)) copy[key] = obj[key]; - } - return copy; - }; - - // Fill in a given object with default properties. - _.defaults = function(obj) { - each(slice.call(arguments, 1), function(source) { - if (source) { - for (var prop in source) { - if (obj[prop] == null) obj[prop] = source[prop]; - } - } - }); - return obj; - }; - - // Create a (shallow-cloned) duplicate of an object. - _.clone = function(obj) { - if (!_.isObject(obj)) return obj; - return _.isArray(obj) ? obj.slice() : _.extend({}, obj); - }; - - // Invokes interceptor with the obj, and then returns obj. - // The primary purpose of this method is to "tap into" a method chain, in - // order to perform operations on intermediate results within the chain. - _.tap = function(obj, interceptor) { - interceptor(obj); - return obj; - }; - - // Internal recursive comparison function for `isEqual`. - var eq = function(a, b, aStack, bStack) { - // Identical objects are equal. `0 === -0`, but they aren't identical. - // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. - if (a === b) return a !== 0 || 1 / a == 1 / b; - // A strict comparison is necessary because `null == undefined`. - if (a == null || b == null) return a === b; - // Unwrap any wrapped objects. - if (a instanceof _) a = a._wrapped; - if (b instanceof _) b = b._wrapped; - // Compare `[[Class]]` names. - var className = toString.call(a); - if (className != toString.call(b)) return false; - switch (className) { - // Strings, numbers, dates, and booleans are compared by value. - case '[object String]': - // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is - // equivalent to `new String("5")`. - return a == String(b); - case '[object Number]': - // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for - // other numeric values. - return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); - case '[object Date]': - case '[object Boolean]': - // Coerce dates and booleans to numeric primitive values. Dates are compared by their - // millisecond representations. Note that invalid dates with millisecond representations - // of `NaN` are not equivalent. - return +a == +b; - // RegExps are compared by their source patterns and flags. - case '[object RegExp]': - return a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; - } - if (typeof a != 'object' || typeof b != 'object') return false; - // Assume equality for cyclic structures. The algorithm for detecting cyclic - // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. - var length = aStack.length; - while (length--) { - // Linear search. Performance is inversely proportional to the number of - // unique nested structures. - if (aStack[length] == a) return bStack[length] == b; - } - // Add the first object to the stack of traversed objects. - aStack.push(a); - bStack.push(b); - var size = 0, result = true; - // Recursively compare objects and arrays. - if (className == '[object Array]') { - // Compare array lengths to determine if a deep comparison is necessary. - size = a.length; - result = size == b.length; - if (result) { - // Deep compare the contents, ignoring non-numeric properties. - while (size--) { - if (!(result = eq(a[size], b[size], aStack, bStack))) break; - } - } - } else { - // Objects with different constructors are not equivalent, but `Object`s - // from different frames are. - var aCtor = a.constructor, bCtor = b.constructor; - if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && - _.isFunction(bCtor) && (bCtor instanceof bCtor))) { - return false; - } - // Deep compare objects. - for (var key in a) { - if (_.has(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; - } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (_.has(b, key) && !(size--)) break; - } - result = !size; - } - } - // Remove the first object from the stack of traversed objects. - aStack.pop(); - bStack.pop(); - return result; - }; - - // Perform a deep comparison to check if two objects are equal. - _.isEqual = function(a, b) { - return eq(a, b, [], []); - }; - - // Is a given array, string, or object empty? - // An "empty" object has no enumerable own-properties. - _.isEmpty = function(obj) { - if (obj == null) return true; - if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; - for (var key in obj) if (_.has(obj, key)) return false; - return true; - }; - - // Is a given value a DOM element? - _.isElement = function(obj) { - return !!(obj && obj.nodeType === 1); - }; - - // Is a given value an array? - // Delegates to ECMA5's native Array.isArray - _.isArray = nativeIsArray || function(obj) { - return toString.call(obj) == '[object Array]'; - }; - - // Is a given variable an object? - _.isObject = function(obj) { - return obj === Object(obj); - }; - - // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. - each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { - _['is' + name] = function(obj) { - return toString.call(obj) == '[object ' + name + ']'; - }; - }); - - // Define a fallback version of the method in browsers (ahem, IE), where - // there isn't any inspectable "Arguments" type. - if (!_.isArguments(arguments)) { - _.isArguments = function(obj) { - return !!(obj && _.has(obj, 'callee')); - }; - } - - // Optimize `isFunction` if appropriate. - if (typeof (/./) !== 'function') { - _.isFunction = function(obj) { - return typeof obj === 'function'; - }; - } - - // Is a given object a finite number? - _.isFinite = function(obj) { - return isFinite(obj) && !isNaN(parseFloat(obj)); - }; - - // Is the given value `NaN`? (NaN is the only number which does not equal itself). - _.isNaN = function(obj) { - return _.isNumber(obj) && obj != +obj; - }; - - // Is a given value a boolean? - _.isBoolean = function(obj) { - return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; - }; - - // Is a given value equal to null? - _.isNull = function(obj) { - return obj === null; - }; - - // Is a given variable undefined? - _.isUndefined = function(obj) { - return obj === void 0; - }; - - // Shortcut function for checking if an object has a given property directly - // on itself (in other words, not on a prototype). - _.has = function(obj, key) { - return hasOwnProperty.call(obj, key); - }; - - // Utility Functions - // ----------------- - - // Run Underscore.js in *noConflict* mode, returning the `_` variable to its - // previous owner. Returns a reference to the Underscore object. - _.noConflict = function() { - root._ = previousUnderscore; - return this; - }; - - // Keep the identity function around for default iterators. - _.identity = function(value) { - return value; - }; - - // Run a function **n** times. - _.times = function(n, iterator, context) { - var accum = Array(n); - for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); - return accum; - }; - - // Return a random integer between min and max (inclusive). - _.random = function(min, max) { - if (max == null) { - max = min; - min = 0; - } - return min + Math.floor(Math.random() * (max - min + 1)); - }; - - // List of HTML entities for escaping. - var entityMap = { - escape: { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''', - '/': '/' - } - }; - entityMap.unescape = _.invert(entityMap.escape); - - // Regexes containing the keys and values listed immediately above. - var entityRegexes = { - escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), - unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') - }; - - // Functions for escaping and unescaping strings to/from HTML interpolation. - _.each(['escape', 'unescape'], function(method) { - _[method] = function(string) { - if (string == null) return ''; - return ('' + string).replace(entityRegexes[method], function(match) { - return entityMap[method][match]; - }); - }; - }); - - // If the value of the named property is a function then invoke it; - // otherwise, return it. - _.result = function(object, property) { - if (object == null) return null; - var value = object[property]; - return _.isFunction(value) ? value.call(object) : value; - }; - - // Add your own custom functions to the Underscore object. - _.mixin = function(obj) { - each(_.functions(obj), function(name){ - var func = _[name] = obj[name]; - _.prototype[name] = function() { - var args = [this._wrapped]; - push.apply(args, arguments); - return result.call(this, func.apply(_, args)); - }; - }); - }; - - // Generate a unique integer id (unique within the entire client session). - // Useful for temporary DOM ids. - var idCounter = 0; - _.uniqueId = function(prefix) { - var id = ++idCounter + ''; - return prefix ? prefix + id : id; - }; - - // By default, Underscore uses ERB-style template delimiters, change the - // following template settings to use alternative delimiters. - _.templateSettings = { - evaluate : /<%([\s\S]+?)%>/g, - interpolate : /<%=([\s\S]+?)%>/g, - escape : /<%-([\s\S]+?)%>/g - }; - - // When customizing `templateSettings`, if you don't want to define an - // interpolation, evaluation or escaping regex, we need one that is - // guaranteed not to match. - var noMatch = /(.)^/; - - // Certain characters need to be escaped so that they can be put into a - // string literal. - var escapes = { - "'": "'", - '\\': '\\', - '\r': 'r', - '\n': 'n', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; - - var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; - - // JavaScript micro-templating, similar to John Resig's implementation. - // Underscore templating handles arbitrary delimiters, preserves whitespace, - // and correctly escapes quotes within interpolated code. - _.template = function(text, data, settings) { - var render; - settings = _.defaults({}, settings, _.templateSettings); - - // Combine delimiters into one regular expression via alternation. - var matcher = new RegExp([ - (settings.escape || noMatch).source, - (settings.interpolate || noMatch).source, - (settings.evaluate || noMatch).source - ].join('|') + '|$', 'g'); - - // Compile the template source, escaping string literals appropriately. - var index = 0; - var source = "__p+='"; - text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { - source += text.slice(index, offset) - .replace(escaper, function(match) { return '\\' + escapes[match]; }); - - if (escape) { - source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; - } - if (interpolate) { - source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; - } - if (evaluate) { - source += "';\n" + evaluate + "\n__p+='"; - } - index = offset + match.length; - return match; - }); - source += "';\n"; - - // If a variable is not specified, place data values in local scope. - if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; - - source = "var __t,__p='',__j=Array.prototype.join," + - "print=function(){__p+=__j.call(arguments,'');};\n" + - source + "return __p;\n"; - - try { - render = new Function(settings.variable || 'obj', '_', source); - } catch (e) { - e.source = source; - throw e; - } - - if (data) return render(data, _); - var template = function(data) { - return render.call(this, data, _); - }; - - // Provide the compiled function source as a convenience for precompilation. - template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; - - return template; - }; - - // Add a "chain" function, which will delegate to the wrapper. - _.chain = function(obj) { - return _(obj).chain(); - }; - - // OOP - // --------------- - // If Underscore is called as a function, it returns a wrapped object that - // can be used OO-style. This wrapper holds altered versions of all the - // underscore functions. Wrapped objects may be chained. - - // Helper function to continue chaining intermediate results. - var result = function(obj) { - return this._chain ? _(obj).chain() : obj; - }; - - // Add all of the Underscore functions to the wrapper object. - _.mixin(_); - - // Add all mutator Array functions to the wrapper. - each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - var obj = this._wrapped; - method.apply(obj, arguments); - if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; - return result.call(this, obj); - }; - }); - - // Add all accessor Array functions to the wrapper. - each(['concat', 'join', 'slice'], function(name) { - var method = ArrayProto[name]; - _.prototype[name] = function() { - return result.call(this, method.apply(this._wrapped, arguments)); - }; - }); - - _.extend(_.prototype, { - - // Start chaining a wrapped Underscore object. - chain: function() { - this._chain = true; - return this; - }, - - // Extracts the result from a wrapped and chained object. - value: function() { - return this._wrapped; - } - - }); - -}).call(this); - -},{}],"jshint":[function(require,module,exports){ -module.exports=require('nr+AlQ'); -},{}],"nr+AlQ":[function(require,module,exports){ -/*! - * JSHint, by JSHint Community. - * - * This file (and this file only) was licensed under the same slightly modified - * MIT license that JSLint is. After a relicensing in 2020 this is now MIT License (Expat). - * Relicensing: https://jshint.com/relicensing-2020/ - * License-Url: https://github.com/jshint/jshint/blob/main/LICENSE - * - * Copyright 2012 Anton Kovalyov (http://jshint.com) - * - * 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. - * - * 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. - * - */ - -/*jshint quotmark:double */ -/*global console:true */ -/*exported console */ - -var _ = require("underscore"); -var events = require("events"); -var vars = require("./vars.js"); -var messages = require("./messages.js"); -var Lexer = require("./lex.js").Lexer; -var reg = require("./reg.js"); -var state = require("./state.js").state; -var style = require("./style.js"); - -// We need this module here because environments such as IE and Rhino -// don't necessarily expose the 'console' API and browserify uses -// it to log things. It's a sad state of affair, really. -var console = require("console-browserify"); - -// We build the application inside a function so that we produce only a singleton -// variable. That function will be invoked immediately, and its return value is -// the JSHINT function itself. - -var JSHINT = (function () { - "use strict"; - - var anonname, // The guessed name for anonymous functions. - api, // Extension API - - // These are operators that should not be used with the ! operator. - bang = { - "<" : true, - "<=" : true, - "==" : true, - "===": true, - "!==": true, - "!=" : true, - ">" : true, - ">=" : true, - "+" : true, - "-" : true, - "*" : true, - "/" : true, - "%" : true - }, - - // These are the JSHint boolean options. - boolOptions = { - asi : true, // if automatic semicolon insertion should be tolerated - bitwise : true, // if bitwise operators should not be allowed - boss : true, // if advanced usage of assignments should be allowed - browser : true, // if the standard browser globals should be predefined - camelcase : true, // if identifiers should be required in camel case - couch : true, // if CouchDB globals should be predefined - curly : true, // if curly braces around all blocks should be required - debug : true, // if debugger statements should be allowed - devel : true, // if logging globals should be predefined (console, alert, etc.) - dojo : true, // if Dojo Toolkit globals should be predefined - eqeqeq : true, // if === should be required - eqnull : true, // if == null comparisons should be tolerated - notypeof : true, // if should report typos in typeof comparisons - es3 : true, // if ES3 syntax should be allowed - es5 : true, // if ES5 syntax should be allowed (is now set per default) - esnext : true, // if es.next specific syntax should be allowed - moz : true, // if mozilla specific syntax should be allowed - evil : true, // if eval should be allowed - expr : true, // if ExpressionStatement should be allowed as Programs - forin : true, // if for in statements must filter - funcscope : true, // if only function scope should be used for scope tests - gcl : true, // if JSHint should be compatible with Google Closure Linter - globalstrict: true, // if global "use strict"; should be allowed (also enables 'strict') - immed : true, // if immediate invocations must be wrapped in parens - iterator : true, // if the `__iterator__` property should be allowed - jquery : true, // if jQuery globals should be predefined - lastsemic : true, // if semicolons may be omitted for the trailing - // statements inside of a one-line blocks. - laxbreak : true, // if line breaks should not be checked - laxcomma : true, // if line breaks should not be checked around commas - loopfunc : true, // if functions should be allowed to be defined within - // loops - mootools : true, // if MooTools globals should be predefined - multistr : true, // allow multiline strings - freeze : true, // if modifying native object prototypes should be disallowed - newcap : true, // if constructor names must be capitalized - noarg : true, // if arguments.caller and arguments.callee should be - // disallowed - node : true, // if the Node.js environment globals should be - // predefined - noempty : true, // if empty blocks should be disallowed - nonbsp : true, // if non-breaking spaces should be disallowed - nonew : true, // if using `new` for side-effects should be disallowed - nonstandard : true, // if non-standard (but widely adopted) globals should - // be predefined - nomen : true, // if names should be checked - onevar : true, // if only one var statement per function should be - // allowed - passfail : true, // if the scan should stop on first error - phantom : true, // if PhantomJS symbols should be allowed - plusplus : true, // if increment/decrement should not be allowed - proto : true, // if the `__proto__` property should be allowed - prototypejs : true, // if Prototype and Scriptaculous globals should be - // predefined - rhino : true, // if the Rhino environment globals should be predefined - shelljs : true, // if ShellJS globals should be predefined - typed : true, // if typed array globals should be predefined - undef : true, // if variables should be declared before used - scripturl : true, // if script-targeted URLs should be tolerated - smarttabs : true, // if smarttabs should be tolerated - // (http://www.emacswiki.org/emacs/SmartTabs) - strict : true, // require the "use strict"; pragma - sub : true, // if all forms of subscript notation are tolerated - supernew : true, // if `new function () { ... };` and `new Object;` - // should be tolerated - trailing : true, // if trailing whitespace rules apply - validthis : true, // if 'this' inside a non-constructor function is valid. - // This is a function scoped option only. - withstmt : true, // if with statements should be allowed - white : true, // if strict whitespace rules apply - worker : true, // if Web Worker script symbols should be allowed - wsh : true, // if the Windows Scripting Host environment globals - // should be predefined - yui : true, // YUI variables should be predefined - noyield : true, // allow generators without a yield - - // Obsolete options - onecase : true, // if one case switch statements should be allowed - regexp : true, // if the . should not be allowed in regexp literals - regexdash : true // if unescaped first/last dash (-) inside brackets - // should be tolerated - }, - - // These are the JSHint options that can take any value - // (we use this object to detect invalid options) - valOptions = { - maxlen : false, - indent : false, - maxerr : false, - predef : false, //predef is deprecated and being replaced by globals - globals : false, - quotmark : false, //'single'|'double'|true - scope : false, - maxstatements: false, // {int} max statements per function - maxdepth : false, // {int} max nested block depth per function - maxparams : false, // {int} max params per function - maxcomplexity: false, // {int} max cyclomatic complexity per function - shadow : false, // if variable shadowing should be tolerated - // "inner" - check for variables defined in the same scope only - // "outer" - check for variables defined in outer scopes as well - // false - same as inner - // true - allow variable shadowing - unused : true, // warn if variables are unused. Available options: - // false - don't check for unused variables - // true - "vars" + check last function param - // "vars" - skip checking unused function params - // "strict" - "vars" + check all function params - latedef : false, // warn if the variable is used before its definition - // false - don't emit any warnings - // true - warn if any variable is used before its definition - // "nofunc" - warn for any variable but function declarations - ignore : false // start/end ignoring lines of code, bypassing the lexer - // start - start ignoring lines, including the current line - // end - stop ignoring lines, starting on the next line - // line - ignore warnings / errors for just a single line - // (this option does not bypass the lexer) - }, - - // These are JSHint boolean options which are shared with JSLint - // where the definition in JSHint is opposite JSLint - invertedOptions = { - bitwise : true, - forin : true, - newcap : true, - nomen : true, - plusplus: true, - regexp : true, - undef : true, - white : true, - - // Inverted and renamed, use JSHint name here - eqeqeq : true, - onevar : true, - strict : true - }, - - // These are JSHint boolean options which are shared with JSLint - // where the name has been changed but the effect is unchanged - renamedOptions = { - eqeq : "eqeqeq", - vars : "onevar", - windows: "wsh", - sloppy : "strict" - }, - - declared, // Globals that were declared using /*global ... */ syntax. - exported, // Variables that are used outside of the current file. - - functionicity = [ - "closure", "exception", "global", "label", - "outer", "unused", "var" - ], - - funct, // The current function - functions, // All of the functions - - global, // The global scope - implied, // Implied globals - inblock, - indent, - lookahead, - lex, - member, - membersOnly, - noreach, - predefined, // Global variables defined by option - - scope, // The current scope - stack, - unuseds, - urls, - warnings, - - extraModules = [], - emitter = new events.EventEmitter(); - - function checkOption(name, t) { - name = name.trim(); - - if (/^[+-]W\d{3}$/g.test(name)) { - return true; - } - - if (valOptions[name] === undefined && boolOptions[name] === undefined) { - if (t.type !== "jslint") { - error("E001", t, name); - return false; - } - } - - return true; - } - - function isString(obj) { - return Object.prototype.toString.call(obj) === "[object String]"; - } - - function isIdentifier(tkn, value) { - if (!tkn) - return false; - - if (!tkn.identifier || tkn.value !== value) - return false; - - return true; - } - - function isReserved(token) { - if (!token.reserved) { - return false; - } - var meta = token.meta; - - if (meta && meta.isFutureReservedWord && state.option.inES5()) { - // ES3 FutureReservedWord in an ES5 environment. - if (!meta.es5) { - return false; - } - - // Some ES5 FutureReservedWord identifiers are active only - // within a strict mode environment. - if (meta.strictOnly) { - if (!state.option.strict && !state.directive["use strict"]) { - return false; - } - } - - if (token.isProperty) { - return false; - } - } - - return true; - } - - function supplant(str, data) { - return str.replace(/\{([^{}]*)\}/g, function (a, b) { - var r = data[b]; - return typeof r === "string" || typeof r === "number" ? r : a; - }); - } - - function combine(dest, src) { - Object.keys(src).forEach(function (name) { - if (JSHINT.blacklist.hasOwnProperty(name)) return; - dest[name] = src[name]; - }); - } - - function assume() { - if (state.option.es5) { - warning("I003"); - } - if (state.option.couch) { - combine(predefined, vars.couch); - } - - if (state.option.rhino) { - combine(predefined, vars.rhino); - } - - if (state.option.shelljs) { - combine(predefined, vars.shelljs); - combine(predefined, vars.node); - } - if (state.option.typed) { - combine(predefined, vars.typed); - } - - if (state.option.phantom) { - combine(predefined, vars.phantom); - } - - if (state.option.prototypejs) { - combine(predefined, vars.prototypejs); - } - - if (state.option.node) { - combine(predefined, vars.node); - combine(predefined, vars.typed); - } - - if (state.option.devel) { - combine(predefined, vars.devel); - } - - if (state.option.dojo) { - combine(predefined, vars.dojo); - } - - if (state.option.browser) { - combine(predefined, vars.browser); - combine(predefined, vars.typed); - } - - if (state.option.nonstandard) { - combine(predefined, vars.nonstandard); - } - - if (state.option.jquery) { - combine(predefined, vars.jquery); - } - - if (state.option.mootools) { - combine(predefined, vars.mootools); - } - - if (state.option.worker) { - combine(predefined, vars.worker); - } - - if (state.option.wsh) { - combine(predefined, vars.wsh); - } - - if (state.option.globalstrict && state.option.strict !== false) { - state.option.strict = true; - } - - if (state.option.yui) { - combine(predefined, vars.yui); - } - - // Let's assume that chronologically ES3 < ES5 < ES6/ESNext < Moz - - state.option.inMoz = function (strict) { - if (strict) { - return state.option.moz && !state.option.esnext; - } - return state.option.moz; - }; - - state.option.inESNext = function (strict) { - if (strict) { - return !state.option.moz && state.option.esnext; - } - return state.option.moz || state.option.esnext; - }; - - state.option.inES5 = function (/* strict */) { - return !state.option.es3; - }; - - state.option.inES3 = function (strict) { - if (strict) { - return !state.option.moz && !state.option.esnext && state.option.es3; - } - return state.option.es3; - }; - } - - // Produce an error warning. - function quit(code, line, chr) { - var percentage = Math.floor((line / state.lines.length) * 100); - var message = messages.errors[code].desc; - - throw { - name: "JSHintError", - line: line, - character: chr, - message: message + " (" + percentage + "% scanned).", - raw: message, - code: code - }; - } - - function isundef(scope, code, token, a) { - return JSHINT.undefs.push([scope, code, token, a]); - } - - function warning(code, t, a, b, c, d) { - var ch, l, w, msg; - - if (/^W\d{3}$/.test(code)) { - if (state.ignored[code]) - return; - - msg = messages.warnings[code]; - } else if (/E\d{3}/.test(code)) { - msg = messages.errors[code]; - } else if (/I\d{3}/.test(code)) { - msg = messages.info[code]; - } - - t = t || state.tokens.next; - if (t.id === "(end)") { // `~ - t = state.tokens.curr; - } - - l = t.line || 0; - ch = t.from || 0; - - w = { - id: "(error)", - raw: msg.desc, - code: msg.code, - evidence: state.lines[l - 1] || "", - line: l, - character: ch, - scope: JSHINT.scope, - a: a, - b: b, - c: c, - d: d - }; - - w.reason = supplant(msg.desc, w); - JSHINT.errors.push(w); - - if (state.option.passfail) { - quit("E042", l, ch); - } - - warnings += 1; - if (warnings >= state.option.maxerr) { - quit("E043", l, ch); - } - - return w; - } - - function warningAt(m, l, ch, a, b, c, d) { - return warning(m, { - line: l, - from: ch - }, a, b, c, d); - } - - function error(m, t, a, b, c, d) { - warning(m, t, a, b, c, d); - } - - function errorAt(m, l, ch, a, b, c, d) { - return error(m, { - line: l, - from: ch - }, a, b, c, d); - } - - // Tracking of "internal" scripts, like eval containing a static string - function addInternalSrc(elem, src) { - var i; - i = { - id: "(internal)", - elem: elem, - value: src - }; - JSHINT.internals.push(i); - return i; - } - - // name: string - // opts: { type: string, token: token, islet: bool } - function addlabel(name, opts) { - opts = opts || {}; - - var type = opts.type; - var token = opts.token; - var islet = opts.islet; - - // Define label in the current function in the current scope. - if (type === "exception") { - if (_.has(funct["(context)"], name)) { - if (funct[name] !== true && !state.option.node) { - warning("W002", state.tokens.next, name); - } - } - } - - if (_.has(funct, name) && !funct["(global)"]) { - if (funct[name] === true) { - if (state.option.latedef) { - if ((state.option.latedef === true && _.contains([funct[name], type], "unction")) || - !_.contains([funct[name], type], "unction")) { - warning("W003", state.tokens.next, name); - } - } - } else { - if ((!state.option.shadow || _.contains([ "inner", "outer" ], state.option.shadow)) && - type !== "exception" || funct["(blockscope)"].getlabel(name)) { - warning("W004", state.tokens.next, name); - } - } - } - - if (funct["(context)"] && _.has(funct["(context)"], name) && type !== "function") { - if (state.option.shadow === "outer") { - warning("W123", state.tokens.next, name); - } - } - - // if the identifier is from a let, adds it only to the current blockscope - if (islet) { - funct["(blockscope)"].current.add(name, type, state.tokens.curr); - } else { - funct["(blockscope)"].shadow(name); - funct[name] = type; - - if (token) { - funct["(tokens)"][name] = token; - } - - setprop(funct, name, { unused: opts.unused || false }); - - if (funct["(global)"]) { - global[name] = funct; - if (_.has(implied, name)) { - if (state.option.latedef) { - if ((state.option.latedef === true && _.contains([funct[name], type], "unction")) || - !_.contains([funct[name], type], "unction")) { - warning("W003", state.tokens.next, name); - } - } - - delete implied[name]; - } - } else { - scope[name] = funct; - } - } - } - - function doOption() { - var nt = state.tokens.next; - var body = nt.body.split(",").map(function (s) { return s.trim(); }); - var predef = {}; - - if (nt.type === "globals") { - body.forEach(function (g) { - g = g.split(":"); - var key = (g[0] || "").trim(); - var val = (g[1] || "").trim(); - - if (key.charAt(0) === "-") { - key = key.slice(1); - val = false; - - JSHINT.blacklist[key] = key; - delete predefined[key]; - } else { - predef[key] = (val === "true"); - } - }); - - combine(predefined, predef); - - for (var key in predef) { - if (_.has(predef, key)) { - declared[key] = nt; - } - } - } - - if (nt.type === "exported") { - body.forEach(function (e) { - exported[e] = true; - }); - } - - if (nt.type === "members") { - membersOnly = membersOnly || {}; - - body.forEach(function (m) { - var ch1 = m.charAt(0); - var ch2 = m.charAt(m.length - 1); - - if (ch1 === ch2 && (ch1 === "\"" || ch1 === "'")) { - m = m - .substr(1, m.length - 2) - .replace("\\\"", "\""); - } - - membersOnly[m] = false; - }); - } - - var numvals = [ - "maxstatements", - "maxparams", - "maxdepth", - "maxcomplexity", - "maxerr", - "maxlen", - "indent" - ]; - - if (nt.type === "jshint" || nt.type === "jslint") { - body.forEach(function (g) { - g = g.split(":"); - var key = (g[0] || "").trim(); - var val = (g[1] || "").trim(); - - if (!checkOption(key, nt)) { - return; - } - - if (numvals.indexOf(key) >= 0) { - - // GH988 - numeric options can be disabled by setting them to `false` - if (val !== "false") { - val = +val; - - if (typeof val !== "number" || !isFinite(val) || val <= 0 || Math.floor(val) !== val) { - error("E032", nt, g[1].trim()); - return; - } - - if (key === "indent") { - state.option["(explicitIndent)"] = true; - } - state.option[key] = val; - } else { - if (key === "indent") { - state.option["(explicitIndent)"] = false; - } else { - state.option[key] = false; - } - } - - return; - } - - if (key === "validthis") { - // `validthis` is valid only within a function scope. - - if (funct["(global)"]) - return void error("E009"); - - if (val !== "true" && val !== "false") - return void error("E002", nt); - - state.option.validthis = (val === "true"); - return; - } - - if (key === "quotmark") { - switch (val) { - case "true": - case "false": - state.option.quotmark = (val === "true"); - break; - case "double": - case "single": - state.option.quotmark = val; - break; - default: - error("E002", nt); - } - return; - } - - if (key === "shadow") { - switch (val) { - case "true": - state.option.shadow = true; - break; - case "outer": - state.option.shadow = "outer"; - break; - case "false": - case "inner": - state.option.shadow = "inner"; - break; - default: - error("E002", nt); - } - return; - } - - if (key === "unused") { - switch (val) { - case "true": - state.option.unused = true; - break; - case "false": - state.option.unused = false; - break; - case "vars": - case "strict": - state.option.unused = val; - break; - default: - error("E002", nt); - } - return; - } - - if (key === "latedef") { - switch (val) { - case "true": - state.option.latedef = true; - break; - case "false": - state.option.latedef = false; - break; - case "nofunc": - state.option.latedef = "nofunc"; - break; - default: - error("E002", nt); - } - return; - } - - if (key === "ignore") { - switch (val) { - case "start": - state.ignoreLinterErrors = true; - break; - case "end": - state.ignoreLinterErrors = false; - break; - case "line": - // Any errors or warnings that happened on the current line, make them go away. - JSHINT.errors = _.reject(JSHINT.errors, function (error) { - // nt.line returns to the current line - return error.line === nt.line; - }); - break; - default: - error("E002", nt); - } - return; - } - - var match = /^([+-])(W\d{3})$/g.exec(key); - if (match) { - // ignore for -W..., unignore for +W... - state.ignored[match[2]] = (match[1] === "-"); - return; - } - - var tn; - if (val === "true" || val === "false") { - if (nt.type === "jslint") { - tn = renamedOptions[key] || key; - state.option[tn] = (val === "true"); - - if (invertedOptions[tn] !== undefined) { - state.option[tn] = !state.option[tn]; - } - } else { - state.option[key] = (val === "true"); - } - - if (key === "newcap") { - state.option["(explicitNewcap)"] = true; - } - return; - } - - error("E002", nt); - }); - - assume(); - } - } - - // We need a peek function. If it has an argument, it peeks that much farther - // ahead. It is used to distinguish - // for ( var i in ... - // from - // for ( var i = ... - - function peek(p) { - var i = p || 0, j = 0, t; - - while (j <= i) { - t = lookahead[j]; - if (!t) { - t = lookahead[j] = lex.token(); - } - j += 1; - } - return t; - } - - // Produce the next token. It looks for programming errors. - - function advance(id, t) { - switch (state.tokens.curr.id) { - case "(number)": - if (state.tokens.next.id === ".") { - warning("W005", state.tokens.curr); - } - break; - case "-": - if (state.tokens.next.id === "-" || state.tokens.next.id === "--") { - warning("W006"); - } - break; - case "+": - if (state.tokens.next.id === "+" || state.tokens.next.id === "++") { - warning("W007"); - } - break; - } - - if (state.tokens.curr.type === "(string)" || state.tokens.curr.identifier) { - anonname = state.tokens.curr.value; - } - - if (id && state.tokens.next.id !== id) { - if (t) { - if (state.tokens.next.id === "(end)") { - error("E019", t, t.id); - } else { - error("E020", state.tokens.next, id, t.id, t.line, state.tokens.next.value); - } - } else if (state.tokens.next.type !== "(identifier)" || state.tokens.next.value !== id) { - warning("W116", state.tokens.next, id, state.tokens.next.value); - } - } - - state.tokens.prev = state.tokens.curr; - state.tokens.curr = state.tokens.next; - for (;;) { - state.tokens.next = lookahead.shift() || lex.token(); - - if (!state.tokens.next) { // No more tokens left, give up - quit("E041", state.tokens.curr.line); - } - - if (state.tokens.next.id === "(end)" || state.tokens.next.id === "(error)") { - return; - } - - if (state.tokens.next.check) { - state.tokens.next.check(); - } - - if (state.tokens.next.isSpecial) { - doOption(); - } else { - if (state.tokens.next.id !== "(endline)") { - break; - } - } - } - } - - function isInfix(token) { - return token.infix || (!token.identifier && !!token.led); - } - - function isEndOfExpr() { - var curr = state.tokens.curr; - var next = state.tokens.next; - if (next.id === ";" || next.id === "}" || next.id === ":") { - return true; - } - if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) { - return curr.line !== next.line; - } - return false; - } - - // This is the heart of JSHINT, the Pratt parser. In addition to parsing, it - // is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is - // like .nud except that it is only used on the first token of a statement. - // Having .fud makes it much easier to define statement-oriented languages like - // JavaScript. I retained Pratt's nomenclature. - - // .nud Null denotation - // .fud First null denotation - // .led Left denotation - // lbp Left binding power - // rbp Right binding power - - // They are elements of the parsing method called Top Down Operator Precedence. - - function expression(rbp, initial) { - var left, isArray = false, isObject = false, isLetExpr = false; - - // if current expression is a let expression - if (!initial && state.tokens.next.value === "let" && peek(0).value === "(") { - if (!state.option.inMoz(true)) { - warning("W118", state.tokens.next, "let expressions"); - } - isLetExpr = true; - // create a new block scope we use only for the current expression - funct["(blockscope)"].stack(); - advance("let"); - advance("("); - state.syntax["let"].fud.call(state.syntax["let"].fud, false); - advance(")"); - } - - if (state.tokens.next.id === "(end)") - error("E006", state.tokens.curr); - - advance(); - - if (initial) { - anonname = "anonymous"; - funct["(verb)"] = state.tokens.curr.value; - } - - if (initial === true && state.tokens.curr.fud) { - left = state.tokens.curr.fud(); - } else { - if (state.tokens.curr.nud) { - left = state.tokens.curr.nud(); - } else { - error("E030", state.tokens.curr, state.tokens.curr.id); - } - - while (rbp < state.tokens.next.lbp && !isEndOfExpr()) { - isArray = state.tokens.curr.value === "Array"; - isObject = state.tokens.curr.value === "Object"; - - // #527, new Foo.Array(), Foo.Array(), new Foo.Object(), Foo.Object() - // Line breaks in IfStatement heads exist to satisfy the checkJSHint - // "Line too long." error. - if (left && (left.value || (left.first && left.first.value))) { - // If the left.value is not "new", or the left.first.value is a "." - // then safely assume that this is not "new Array()" and possibly - // not "new Object()"... - if (left.value !== "new" || - (left.first && left.first.value && left.first.value === ".")) { - isArray = false; - // ...In the case of Object, if the left.value and state.tokens.curr.value - // are not equal, then safely assume that this not "new Object()" - if (left.value !== state.tokens.curr.value) { - isObject = false; - } - } - } - - advance(); - - if (isArray && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { - warning("W009", state.tokens.curr); - } - - if (isObject && state.tokens.curr.id === "(" && state.tokens.next.id === ")") { - warning("W010", state.tokens.curr); - } - - if (left && state.tokens.curr.led) { - left = state.tokens.curr.led(left); - } else { - error("E033", state.tokens.curr, state.tokens.curr.id); - } - } - } - if (isLetExpr) { - funct["(blockscope)"].unstack(); - } - return left; - } - - -// Functions for conformance of style. - - function adjacent(left, right) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - if (state.option.white) { - if (left.character !== right.from && left.line === right.line) { - left.from += (left.character - left.from); - warning("W011", left, left.value); - } - } - } - - function nobreak(left, right) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - if (state.option.white && (left.character !== right.from || left.line !== right.line)) { - warning("W012", right, right.value); - } - } - - function nospace(left, right) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - if (state.option.white && !left.comment) { - if (left.line === right.line) { - adjacent(left, right); - } - } - } - - function nonadjacent(left, right) { - if (state.option.white) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - - if (left.value === ";" && right.value === ";") { - return; - } - - if (left.line === right.line && left.character === right.from) { - left.from += (left.character - left.from); - warning("W013", left, left.value); - } - } - } - - function nobreaknonadjacent(left, right) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - if (!state.option.laxbreak && left.line !== right.line) { - warning("W014", right, right.value); - } else if (state.option.white) { - left = left || state.tokens.curr; - right = right || state.tokens.next; - if (left.character === right.from) { - left.from += (left.character - left.from); - warning("W013", left, left.value); - } - } - } - - function indentation(bias) { - if (!state.option.white && !state.option["(explicitIndent)"]) { - return; - } - - if (state.tokens.next.id === "(end)") { - return; - } - - var i = indent + (bias || 0); - if (state.tokens.next.from !== i) { - warning("W015", state.tokens.next, state.tokens.next.value, i, state.tokens.next.from); - } - } - - function nolinebreak(t) { - t = t || state.tokens.curr; - if (t.line !== state.tokens.next.line) { - warning("E022", t, t.value); - } - } - - function nobreakcomma(left, right) { - if (left.line !== right.line) { - if (!state.option.laxcomma) { - if (comma.first) { - warning("I001"); - comma.first = false; - } - warning("W014", left, right.value); - } - } else if (!left.comment && left.character !== right.from && state.option.white) { - left.from += (left.character - left.from); - warning("W011", left, left.value); - } - } - - function comma(opts) { - opts = opts || {}; - - if (!opts.peek) { - nobreakcomma(state.tokens.curr, state.tokens.next); - advance(","); - } else { - nobreakcomma(state.tokens.prev, state.tokens.curr); - } - - // TODO: This is a temporary solution to fight against false-positives in - // arrays and objects with trailing commas (see GH-363). The best solution - // would be to extract all whitespace rules out of parser. - - if (state.tokens.next.value !== "]" && state.tokens.next.value !== "}") { - nonadjacent(state.tokens.curr, state.tokens.next); - } - - if (state.tokens.next.identifier && !(opts.property && state.option.inES5())) { - // Keywords that cannot follow a comma operator. - switch (state.tokens.next.value) { - case "break": - case "case": - case "catch": - case "continue": - case "default": - case "do": - case "else": - case "finally": - case "for": - case "if": - case "in": - case "instanceof": - case "return": - case "switch": - case "throw": - case "try": - case "var": - case "let": - case "while": - case "with": - error("E024", state.tokens.next, state.tokens.next.value); - return false; - } - } - - if (state.tokens.next.type === "(punctuator)") { - switch (state.tokens.next.value) { - case "}": - case "]": - case ",": - if (opts.allowTrailing) { - return true; - } - - /* falls through */ - case ")": - error("E024", state.tokens.next, state.tokens.next.value); - return false; - } - } - return true; - } - - // Functional constructors for making the symbols that will be inherited by - // tokens. - - function symbol(s, p) { - var x = state.syntax[s]; - if (!x || typeof x !== "object") { - state.syntax[s] = x = { - id: s, - lbp: p, - value: s - }; - } - return x; - } - - function delim(s) { - return symbol(s, 0); - } - - function stmt(s, f) { - var x = delim(s); - x.identifier = x.reserved = true; - x.fud = f; - return x; - } - - function blockstmt(s, f) { - var x = stmt(s, f); - x.block = true; - return x; - } - - function reserveName(x) { - var c = x.id.charAt(0); - if ((c >= "a" && c <= "z") || (c >= "A" && c <= "Z")) { - x.identifier = x.reserved = true; - } - return x; - } - - function prefix(s, f) { - var x = symbol(s, 150); - reserveName(x); - - x.nud = (typeof f === "function") ? f : function () { - this.right = expression(150); - this.arity = "unary"; - - if (this.id === "++" || this.id === "--") { - if (state.option.plusplus) { - warning("W016", this, this.id); - } else if (this.right && (!this.right.identifier || isReserved(this.right)) && - this.right.id !== "." && this.right.id !== "[") { - warning("W017", this); - } - } - - return this; - }; - - return x; - } - - function type(s, f) { - var x = delim(s); - x.type = s; - x.nud = f; - return x; - } - - function reserve(name, func) { - var x = type(name, func); - x.identifier = true; - x.reserved = true; - return x; - } - - function FutureReservedWord(name, meta) { - var x = type(name, (meta && meta.nud) || function () { - return this; - }); - - meta = meta || {}; - meta.isFutureReservedWord = true; - - x.value = name; - x.identifier = true; - x.reserved = true; - x.meta = meta; - - return x; - } - - function reservevar(s, v) { - return reserve(s, function () { - if (typeof v === "function") { - v(this); - } - return this; - }); - } - - function infix(s, f, p, w) { - var x = symbol(s, p); - reserveName(x); - x.infix = true; - x.led = function (left) { - if (!w) { - nobreaknonadjacent(state.tokens.prev, state.tokens.curr); - nonadjacent(state.tokens.curr, state.tokens.next); - } - if (s === "in" && left.id === "!") { - warning("W018", left, "!"); - } - if (typeof f === "function") { - return f(left, this); - } else { - this.left = left; - this.right = expression(p); - return this; - } - }; - return x; - } - - - function application(s) { - var x = symbol(s, 42); - - x.led = function (left) { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "arrow function syntax (=>)"); - } - - nobreaknonadjacent(state.tokens.prev, state.tokens.curr); - nonadjacent(state.tokens.curr, state.tokens.next); - - this.left = left; - this.right = doFunction(undefined, undefined, false, left); - return this; - }; - return x; - } - - function relation(s, f) { - var x = symbol(s, 100); - - x.led = function (left) { - nobreaknonadjacent(state.tokens.prev, state.tokens.curr); - nonadjacent(state.tokens.curr, state.tokens.next); - var right = expression(100); - - if (isIdentifier(left, "NaN") || isIdentifier(right, "NaN")) { - warning("W019", this); - } else if (f) { - f.apply(this, [left, right]); - } - - if (!left || !right) { - quit("E041", state.tokens.curr.line); - } - - if (left.id === "!") { - warning("W018", left, "!"); - } - - if (right.id === "!") { - warning("W018", right, "!"); - } - - this.left = left; - this.right = right; - return this; - }; - return x; - } - - function isPoorRelation(node) { - return node && - ((node.type === "(number)" && +node.value === 0) || - (node.type === "(string)" && node.value === "") || - (node.type === "null" && !state.option.eqnull) || - node.type === "true" || - node.type === "false" || - node.type === "undefined"); - } - - // Checks whether the 'typeof' operator is used with the correct - // value. For docs on 'typeof' see: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof - - function isTypoTypeof(left, right) { - if (state.option.notypeof) - return false; - - if (!left || !right) - return false; - - var values = [ - "undefined", "object", "boolean", "number", - "string", "function", "xml", "object", "unknown" - ]; - - if (right.type === "(identifier)" && right.value === "typeof" && left.type === "(string)") - return !_.contains(values, left.value); - - return false; - } - - function findNativePrototype(left) { - var natives = [ - "Array", "ArrayBuffer", "Boolean", "Collator", "DataView", "Date", - "DateTimeFormat", "Error", "EvalError", "Float32Array", "Float64Array", - "Function", "Infinity", "Intl", "Int16Array", "Int32Array", "Int8Array", - "Iterator", "Number", "NumberFormat", "Object", "RangeError", - "ReferenceError", "RegExp", "StopIteration", "String", "SyntaxError", - "TypeError", "Uint16Array", "Uint32Array", "Uint8Array", "Uint8ClampedArray", - "URIError" - ]; - - function walkPrototype(obj) { - if (typeof obj !== "object") return; - return obj.right === "prototype" ? obj : walkPrototype(obj.left); - } - - function walkNative(obj) { - while (!obj.identifier && typeof obj.left === "object") - obj = obj.left; - - if (obj.identifier && natives.indexOf(obj.value) >= 0) - return obj.value; - } - - var prototype = walkPrototype(left); - if (prototype) return walkNative(prototype); - } - - function assignop(s, f, p) { - var x = infix(s, typeof f === "function" ? f : function (left, that) { - that.left = left; - - if (left) { - if (state.option.freeze) { - var nativeObject = findNativePrototype(left); - if (nativeObject) - warning("W121", left, nativeObject); - } - - if (predefined[left.value] === false && - scope[left.value]["(global)"] === true) { - warning("W020", left); - } else if (left["function"]) { - warning("W021", left, left.value); - } - - if (funct[left.value] === "const") { - error("E013", left, left.value); - } - - if (left.id === ".") { - if (!left.left) { - warning("E031", that); - } else if (left.left.value === "arguments" && !state.directive["use strict"]) { - warning("E031", that); - } - - that.right = expression(10); - return that; - } else if (left.id === "[") { - if (state.tokens.curr.left.first) { - state.tokens.curr.left.first.forEach(function (t) { - if (funct[t.value] === "const") { - error("E013", t, t.value); - } - }); - } else if (!left.left) { - warning("E031", that); - } else if (left.left.value === "arguments" && !state.directive["use strict"]) { - warning("E031", that); - } - that.right = expression(10); - return that; - } else if (left.identifier && !isReserved(left)) { - if (funct[left.value] === "exception") { - warning("W022", left); - } - that.right = expression(10); - return that; - } - - if (left === state.syntax["function"]) { - warning("W023", state.tokens.curr); - } - } - - error("E031", that); - }, p); - - x.exps = true; - x.assign = true; - return x; - } - - - function bitwise(s, f, p) { - var x = symbol(s, p); - reserveName(x); - x.led = (typeof f === "function") ? f : function (left) { - if (state.option.bitwise) { - warning("W016", this, this.id); - } - this.left = left; - this.right = expression(p); - return this; - }; - return x; - } - - - function bitwiseassignop(s) { - return assignop(s, function (left, that) { - if (state.option.bitwise) { - warning("W016", that, that.id); - } - nonadjacent(state.tokens.prev, state.tokens.curr); - nonadjacent(state.tokens.curr, state.tokens.next); - if (left) { - if (left.id === "." || left.id === "[" || - (left.identifier && !isReserved(left))) { - expression(10); - return that; - } - if (left === state.syntax["function"]) { - warning("W023", state.tokens.curr); - } - return that; - } - error("E031", that); - }, 20); - } - - - function suffix(s) { - var x = symbol(s, 150); - - x.led = function (left) { - if (state.option.plusplus) { - warning("W016", this, this.id); - } else if ((!left.identifier || isReserved(left)) && left.id !== "." && left.id !== "[") { - warning("W017", this); - } - - this.left = left; - return this; - }; - return x; - } - - // fnparam means that this identifier is being defined as a function - // argument (see identifier()) - // prop means that this identifier is that of an object property - - function optionalidentifier(fnparam, prop) { - if (!state.tokens.next.identifier) { - return; - } - - advance(); - - var curr = state.tokens.curr; - var val = state.tokens.curr.value; - - if (!isReserved(curr)) { - return val; - } - - if (prop) { - if (state.option.inES5()) { - return val; - } - } - - if (fnparam && val === "undefined") { - return val; - } - - // Display an info message about reserved words as properties - // and ES5 but do it only once. - if (prop && !api.getCache("displayed:I002")) { - api.setCache("displayed:I002", true); - warning("I002"); - } - - warning("W024", state.tokens.curr, state.tokens.curr.id); - return val; - } - - // fnparam means that this identifier is being defined as a function - // argument - // prop means that this identifier is that of an object property - function identifier(fnparam, prop) { - var i = optionalidentifier(fnparam, prop); - if (i) { - return i; - } - if (state.tokens.curr.id === "function" && state.tokens.next.id === "(") { - warning("W025"); - } else { - error("E030", state.tokens.next, state.tokens.next.value); - } - } - - - function reachable(s) { - var i = 0, t; - if (state.tokens.next.id !== ";" || noreach) { - return; - } - for (;;) { - do { - t = peek(i); - i += 1; - } while (t.id != "(end)" && t.id === "(comment)"); - - if (t.reach) { - return; - } - if (t.id !== "(endline)") { - if (t.id === "function") { - if (state.option.latedef === true) { - warning("W026", t); - } - break; - } - - warning("W027", t, t.value, s); - break; - } - } - } - - - function statement(noindent) { - var values; - var i = indent, r, s = scope, t = state.tokens.next; - - if (t.id === ";") { - advance(";"); - return; - } - - // Is this a labelled statement? - var res = isReserved(t); - - // We're being more tolerant here: if someone uses - // a FutureReservedWord as a label, we warn but proceed - // anyway. - - if (res && t.meta && t.meta.isFutureReservedWord && peek().id === ":") { - warning("W024", t, t.id); - res = false; - } - - // detect a destructuring assignment - if (_.has(["[", "{"], t.value)) { - if (lookupBlockType().isDestAssign) { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "destructuring expression"); - } - values = destructuringExpression(); - values.forEach(function (tok) { - isundef(funct, "W117", tok.token, tok.id); - }); - advance("="); - destructuringExpressionMatch(values, expression(10, true)); - advance(";"); - return; - } - } - if (t.identifier && !res && peek().id === ":") { - advance(); - advance(":"); - scope = Object.create(s); - addlabel(t.value, { type: "label" }); - - if (!state.tokens.next.labelled && state.tokens.next.value !== "{") { - warning("W028", state.tokens.next, t.value, state.tokens.next.value); - } - - state.tokens.next.label = t.value; - t = state.tokens.next; - } - - // Is it a lonely block? - - if (t.id === "{") { - // Is it a switch case block? - // - // switch (foo) { - // case bar: { <= here. - // ... - // } - // } - var iscase = (funct["(verb)"] === "case" && state.tokens.curr.value === ":"); - block(true, true, false, false, iscase); - return; - } - - // Parse the statement. - - if (!noindent) { - indentation(); - } - r = expression(0, true); - - if (r && (!r.identifier || r.value !== "function") && (r.type !== "(punctuator)")) { - if (!state.directive["use strict"] && - state.option.globalstrict && - state.option.strict) { - warning("E007"); - } - } - - // Look for the final semicolon. - - if (!t.block) { - if (!state.option.expr && (!r || !r.exps)) { - warning("W030", state.tokens.curr); - } else if (state.option.nonew && r && r.left && r.id === "(" && r.left.id === "new") { - warning("W031", t); - } - - if (state.tokens.next.id !== ";") { - if (!state.option.asi) { - // If this is the last statement in a block that ends on - // the same line *and* option lastsemic is on, ignore the warning. - // Otherwise, complain about missing semicolon. - if (!state.option.lastsemic || state.tokens.next.id !== "}" || - state.tokens.next.line !== state.tokens.curr.line) { - warningAt("W033", state.tokens.curr.line, state.tokens.curr.character); - } - } - } else { - adjacent(state.tokens.curr, state.tokens.next); - advance(";"); - nonadjacent(state.tokens.curr, state.tokens.next); - } - } - - // Restore the indentation. - - indent = i; - scope = s; - return r; - } - - - function statements(startLine) { - var a = [], p; - - while (!state.tokens.next.reach && state.tokens.next.id !== "(end)") { - if (state.tokens.next.id === ";") { - p = peek(); - - if (!p || (p.id !== "(" && p.id !== "[")) { - warning("W032"); - } - - advance(";"); - } else { - a.push(statement(startLine === state.tokens.next.line)); - } - } - return a; - } - - - /* - * read all directives - * recognizes a simple form of asi, but always - * warns, if it is used - */ - function directives() { - var i, p, pn; - - for (;;) { - if (state.tokens.next.id === "(string)") { - p = peek(0); - if (p.id === "(endline)") { - i = 1; - do { - pn = peek(i); - i = i + 1; - } while (pn.id === "(endline)"); - - if (pn.id !== ";") { - if (pn.id !== "(string)" && pn.id !== "(number)" && - pn.id !== "(regexp)" && pn.identifier !== true && - pn.id !== "}") { - break; - } - warning("W033", state.tokens.next); - } else { - p = pn; - } - } else if (p.id === "}") { - // Directive with no other statements, warn about missing semicolon - warning("W033", p); - } else if (p.id !== ";") { - break; - } - - indentation(); - advance(); - if (state.directive[state.tokens.curr.value]) { - warning("W034", state.tokens.curr, state.tokens.curr.value); - } - - if (state.tokens.curr.value === "use strict") { - if (!state.option["(explicitNewcap)"]) - state.option.newcap = true; - state.option.undef = true; - } - - // there's no directive negation, so always set to true - state.directive[state.tokens.curr.value] = true; - - if (p.id === ";") { - advance(";"); - } - continue; - } - break; - } - } - - - /* - * Parses a single block. A block is a sequence of statements wrapped in - * braces. - * - * ordinary - true for everything but function bodies and try blocks. - * stmt - true if block can be a single statement (e.g. in if/for/while). - * isfunc - true if block is a function body - * isfatarrow - true if its a body of a fat arrow function - * iscase - true if block is a switch case block - */ - function block(ordinary, stmt, isfunc, isfatarrow, iscase) { - var a, - b = inblock, - old_indent = indent, - m, - s = scope, - t, - line, - d; - - inblock = ordinary; - - if (!ordinary || !state.option.funcscope) - scope = Object.create(scope); - - nonadjacent(state.tokens.curr, state.tokens.next); - t = state.tokens.next; - - var metrics = funct["(metrics)"]; - metrics.nestedBlockDepth += 1; - metrics.verifyMaxNestedBlockDepthPerFunction(); - - if (state.tokens.next.id === "{") { - advance("{"); - - // create a new block scope - funct["(blockscope)"].stack(); - - line = state.tokens.curr.line; - if (state.tokens.next.id !== "}") { - indent += state.option.indent; - while (!ordinary && state.tokens.next.from > indent) { - indent += state.option.indent; - } - - if (isfunc) { - m = {}; - for (d in state.directive) { - if (_.has(state.directive, d)) { - m[d] = state.directive[d]; - } - } - directives(); - - if (state.option.strict && funct["(context)"]["(global)"]) { - if (!m["use strict"] && !state.directive["use strict"]) { - warning("E007"); - } - } - } - - a = statements(line); - - metrics.statementCount += a.length; - - if (isfunc) { - state.directive = m; - } - - indent -= state.option.indent; - if (line !== state.tokens.next.line) { - indentation(); - } - } else if (line !== state.tokens.next.line) { - indentation(); - } - advance("}", t); - - funct["(blockscope)"].unstack(); - - indent = old_indent; - } else if (!ordinary) { - if (isfunc) { - m = {}; - if (stmt && !isfatarrow && !state.option.inMoz(true)) { - error("W118", state.tokens.curr, "function closure expressions"); - } - - if (!stmt) { - for (d in state.directive) { - if (_.has(state.directive, d)) { - m[d] = state.directive[d]; - } - } - } - expression(10); - - if (state.option.strict && funct["(context)"]["(global)"]) { - if (!m["use strict"] && !state.directive["use strict"]) { - warning("E007"); - } - } - } else { - error("E021", state.tokens.next, "{", state.tokens.next.value); - } - } else { - - // check to avoid let declaration not within a block - funct["(nolet)"] = true; - - if (!stmt || state.option.curly) { - warning("W116", state.tokens.next, "{", state.tokens.next.value); - } - - noreach = true; - indent += state.option.indent; - // test indentation only if statement is in new line - a = [statement(state.tokens.next.line === state.tokens.curr.line)]; - indent -= state.option.indent; - noreach = false; - - delete funct["(nolet)"]; - } - // Don't clear and let it propagate out if it is "break", "return", or "throw" in switch case - if (!(iscase && ["break", "return", "throw"].indexOf(funct["(verb)"]) != -1)) { - funct["(verb)"] = null; - } - - if (!ordinary || !state.option.funcscope) scope = s; - inblock = b; - if (ordinary && state.option.noempty && (!a || a.length === 0)) { - warning("W035"); - } - metrics.nestedBlockDepth -= 1; - return a; - } - - - function countMember(m) { - if (membersOnly && typeof membersOnly[m] !== "boolean") { - warning("W036", state.tokens.curr, m); - } - if (typeof member[m] === "number") { - member[m] += 1; - } else { - member[m] = 1; - } - } - - - function note_implied(tkn) { - var name = tkn.value; - var desc = Object.getOwnPropertyDescriptor(implied, name); - - if (!desc) - implied[name] = [tkn.line]; - else - desc.value.push(tkn.line); - } - - - // Build the syntax table by declaring the syntactic elements of the language. - - type("(number)", function () { - return this; - }); - - type("(string)", function () { - return this; - }); - - state.syntax["(identifier)"] = { - type: "(identifier)", - lbp: 0, - identifier: true, - - nud: function () { - var v = this.value; - var s = scope[v]; - var f; - var block; - - if (typeof s === "function") { - // Protection against accidental inheritance. - s = undefined; - } else if (!funct["(blockscope)"].current.has(v) && typeof s === "boolean") { - f = funct; - funct = functions[0]; - addlabel(v, { type: "var" }); - s = funct; - funct = f; - } - - block = funct["(blockscope)"].getlabel(v); - - // The name is in scope and defined in the current function. - if (funct === s || block) { - // Change 'unused' to 'var', and reject labels. - // the name is in a block scope. - switch (block ? block[v]["(type)"] : funct[v]) { - case "unused": - if (block) block[v]["(type)"] = "var"; - else funct[v] = "var"; - break; - case "unction": - if (block) block[v]["(type)"] = "function"; - else funct[v] = "function"; - this["function"] = true; - break; - case "const": - setprop(funct, v, { unused: false }); - break; - case "function": - this["function"] = true; - break; - case "label": - warning("W037", state.tokens.curr, v); - break; - } - } else if (funct["(global)"]) { - // The name is not defined in the function. If we are in the global - // scope, then we have an undefined variable. - // - // Operators typeof and delete do not raise runtime errors even if - // the base object of a reference is null so no need to display warning - // if we're inside of typeof or delete. - - if (typeof predefined[v] !== "boolean") { - // Attempting to subscript a null reference will throw an - // error, even within the typeof and delete operators - if (!(anonname === "typeof" || anonname === "delete") || - (state.tokens.next && (state.tokens.next.value === "." || - state.tokens.next.value === "["))) { - - // if we're in a list comprehension, variables are declared - // locally and used before being defined. So we check - // the presence of the given variable in the comp array - // before declaring it undefined. - - if (!funct["(comparray)"].check(v)) { - isundef(funct, "W117", state.tokens.curr, v); - } - } - } - - note_implied(state.tokens.curr); - } else { - // If the name is already defined in the current - // function, but not as outer, then there is a scope error. - - switch (funct[v]) { - case "closure": - case "function": - case "var": - case "unused": - warning("W038", state.tokens.curr, v); - break; - case "label": - warning("W037", state.tokens.curr, v); - break; - case "outer": - case "global": - break; - default: - // If the name is defined in an outer function, make an outer entry, - // and if it was unused, make it var. - if (s === true) { - funct[v] = true; - } else if (s === null) { - warning("W039", state.tokens.curr, v); - note_implied(state.tokens.curr); - } else if (typeof s !== "object") { - // Operators typeof and delete do not raise runtime errors even - // if the base object of a reference is null so no need to - // - // display warning if we're inside of typeof or delete. - // Attempting to subscript a null reference will throw an - // error, even within the typeof and delete operators - if (!(anonname === "typeof" || anonname === "delete") || - (state.tokens.next && - (state.tokens.next.value === "." || state.tokens.next.value === "["))) { - - isundef(funct, "W117", state.tokens.curr, v); - } - funct[v] = true; - note_implied(state.tokens.curr); - } else { - switch (s[v]) { - case "function": - case "unction": - this["function"] = true; - s[v] = "closure"; - funct[v] = s["(global)"] ? "global" : "outer"; - break; - case "var": - case "unused": - s[v] = "closure"; - funct[v] = s["(global)"] ? "global" : "outer"; - break; - case "const": - setprop(s, v, { unused: false }); - break; - case "closure": - funct[v] = s["(global)"] ? "global" : "outer"; - break; - case "label": - warning("W037", state.tokens.curr, v); - } - } - } - } - return this; - }, - - led: function () { - error("E033", state.tokens.next, state.tokens.next.value); - } - }; - - type("(regexp)", function () { - return this; - }); - - // ECMAScript parser - - delim("(endline)"); - delim("(begin)"); - delim("(end)").reach = true; - delim("(error)").reach = true; - delim("}").reach = true; - delim(")"); - delim("]"); - delim("\"").reach = true; - delim("'").reach = true; - delim(";"); - delim(":").reach = true; - delim("#"); - - reserve("else"); - reserve("case").reach = true; - reserve("catch"); - reserve("default").reach = true; - reserve("finally"); - reservevar("arguments", function (x) { - if (state.directive["use strict"] && funct["(global)"]) { - warning("E008", x); - } - }); - reservevar("eval"); - reservevar("false"); - reservevar("Infinity"); - reservevar("null"); - reservevar("this", function (x) { - if (state.directive["use strict"] && !state.option.validthis && ((funct["(statement)"] && - funct["(name)"].charAt(0) > "Z") || funct["(global)"])) { - warning("W040", x); - } - }); - reservevar("true"); - reservevar("undefined"); - - assignop("=", "assign", 20); - assignop("+=", "assignadd", 20); - assignop("-=", "assignsub", 20); - assignop("*=", "assignmult", 20); - assignop("/=", "assigndiv", 20).nud = function () { - error("E014"); - }; - assignop("%=", "assignmod", 20); - - bitwiseassignop("&=", "assignbitand", 20); - bitwiseassignop("|=", "assignbitor", 20); - bitwiseassignop("^=", "assignbitxor", 20); - bitwiseassignop("<<=", "assignshiftleft", 20); - bitwiseassignop(">>=", "assignshiftright", 20); - bitwiseassignop(">>>=", "assignshiftrightunsigned", 20); - infix(",", function (left, that) { - var expr; - that.exprs = [left]; - if (!comma({peek: true})) { - return that; - } - while (true) { - if (!(expr = expression(10))) { - break; - } - that.exprs.push(expr); - if (state.tokens.next.value !== "," || !comma()) { - break; - } - } - return that; - }, 10, true); - - infix("?", function (left, that) { - increaseComplexityCount(); - that.left = left; - that.right = expression(10); - advance(":"); - that["else"] = expression(10); - return that; - }, 30); - - var orPrecendence = 40; - infix("||", function (left, that) { - increaseComplexityCount(); - that.left = left; - that.right = expression(orPrecendence); - return that; - }, orPrecendence); - infix("&&", "and", 50); - bitwise("|", "bitor", 70); - bitwise("^", "bitxor", 80); - bitwise("&", "bitand", 90); - relation("==", function (left, right) { - var eqnull = state.option.eqnull && (left.value === "null" || right.value === "null"); - - switch (true) { - case !eqnull && state.option.eqeqeq: - this.from = this.character; - warning("W116", this, "===", "=="); - break; - case isPoorRelation(left): - warning("W041", this, "===", left.value); - break; - case isPoorRelation(right): - warning("W041", this, "===", right.value); - break; - case isTypoTypeof(right, left): - warning("W122", this, right.value); - break; - case isTypoTypeof(left, right): - warning("W122", this, left.value); - break; - } - - return this; - }); - relation("===", function (left, right) { - if (isTypoTypeof(right, left)) { - warning("W122", this, right.value); - } else if (isTypoTypeof(left, right)) { - warning("W122", this, left.value); - } - return this; - }); - relation("!=", function (left, right) { - var eqnull = state.option.eqnull && - (left.value === "null" || right.value === "null"); - - if (!eqnull && state.option.eqeqeq) { - this.from = this.character; - warning("W116", this, "!==", "!="); - } else if (isPoorRelation(left)) { - warning("W041", this, "!==", left.value); - } else if (isPoorRelation(right)) { - warning("W041", this, "!==", right.value); - } else if (isTypoTypeof(right, left)) { - warning("W122", this, right.value); - } else if (isTypoTypeof(left, right)) { - warning("W122", this, left.value); - } - return this; - }); - relation("!==", function (left, right) { - if (isTypoTypeof(right, left)) { - warning("W122", this, right.value); - } else if (isTypoTypeof(left, right)) { - warning("W122", this, left.value); - } - return this; - }); - relation("<"); - relation(">"); - relation("<="); - relation(">="); - bitwise("<<", "shiftleft", 120); - bitwise(">>", "shiftright", 120); - bitwise(">>>", "shiftrightunsigned", 120); - infix("in", "in", 120); - infix("instanceof", "instanceof", 120); - infix("+", function (left, that) { - var right = expression(130); - if (left && right && left.id === "(string)" && right.id === "(string)") { - left.value += right.value; - left.character = right.character; - if (!state.option.scripturl && reg.javascriptURL.test(left.value)) { - warning("W050", left); - } - return left; - } - that.left = left; - that.right = right; - return that; - }, 130); - prefix("+", "num"); - prefix("+++", function () { - warning("W007"); - this.right = expression(150); - this.arity = "unary"; - return this; - }); - infix("+++", function (left) { - warning("W007"); - this.left = left; - this.right = expression(130); - return this; - }, 130); - infix("-", "sub", 130); - prefix("-", "neg"); - prefix("---", function () { - warning("W006"); - this.right = expression(150); - this.arity = "unary"; - return this; - }); - infix("---", function (left) { - warning("W006"); - this.left = left; - this.right = expression(130); - return this; - }, 130); - infix("*", "mult", 140); - infix("/", "div", 140); - infix("%", "mod", 140); - - suffix("++", "postinc"); - prefix("++", "preinc"); - state.syntax["++"].exps = true; - - suffix("--", "postdec"); - prefix("--", "predec"); - state.syntax["--"].exps = true; - prefix("delete", function () { - var p = expression(10); - if (!p || (p.id !== "." && p.id !== "[")) { - warning("W051"); - } - this.first = p; - return this; - }).exps = true; - - prefix("~", function () { - if (state.option.bitwise) { - warning("W052", this, "~"); - } - expression(150); - return this; - }); - - prefix("...", function () { - if (!state.option.inESNext()) { - warning("W104", this, "spread/rest operator"); - } - if (!state.tokens.next.identifier) { - error("E030", state.tokens.next, state.tokens.next.value); - } - expression(150); - return this; - }); - - prefix("!", function () { - this.right = expression(150); - this.arity = "unary"; - - if (!this.right) { // '!' followed by nothing? Give up. - quit("E041", this.line || 0); - } - - if (bang[this.right.id] === true) { - warning("W018", this, "!"); - } - return this; - }); - - prefix("typeof", "typeof"); - prefix("new", function () { - var c = expression(155), i; - if (c && c.id !== "function") { - if (c.identifier) { - c["new"] = true; - switch (c.value) { - case "Number": - case "String": - case "Boolean": - case "Math": - case "JSON": - warning("W053", state.tokens.prev, c.value); - break; - case "Function": - if (!state.option.evil) { - warning("W054"); - } - break; - case "Date": - case "RegExp": - case "this": - break; - default: - if (c.id !== "function") { - i = c.value.substr(0, 1); - if (state.option.newcap && (i < "A" || i > "Z") && !_.has(global, c.value)) { - warning("W055", state.tokens.curr); - } - } - } - } else { - if (c.id !== "." && c.id !== "[" && c.id !== "(") { - warning("W056", state.tokens.curr); - } - } - } else { - if (!state.option.supernew) - warning("W057", this); - } - adjacent(state.tokens.curr, state.tokens.next); - if (state.tokens.next.id !== "(" && !state.option.supernew) { - warning("W058", state.tokens.curr, state.tokens.curr.value); - } - this.first = c; - return this; - }); - state.syntax["new"].exps = true; - - prefix("void").exps = true; - - infix(".", function (left, that) { - adjacent(state.tokens.prev, state.tokens.curr); - nobreak(); - var m = identifier(false, true); - - if (typeof m === "string") { - countMember(m); - } - - that.left = left; - that.right = m; - - if (m && m === "hasOwnProperty" && state.tokens.next.value === "=") { - warning("W001"); - } - - if (left && left.value === "arguments" && (m === "callee" || m === "caller")) { - if (state.option.noarg) - warning("W059", left, m); - else if (state.directive["use strict"]) - error("E008"); - } else if (!state.option.evil && left && left.value === "document" && - (m === "write" || m === "writeln")) { - warning("W060", left); - } - - if (!state.option.evil && (m === "eval" || m === "execScript")) { - warning("W061"); - } - - return that; - }, 160, true); - - infix("(", function (left, that) { - if (state.tokens.prev.id !== "}" && state.tokens.prev.id !== ")") { - nobreak(state.tokens.prev, state.tokens.curr); - } - - nospace(); - if (state.option.immed && left && !left.immed && left.id === "function") { - warning("W062"); - } - - var n = 0; - var p = []; - - if (left) { - if (left.type === "(identifier)") { - if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { - if ("Number String Boolean Date Object".indexOf(left.value) === -1) { - if (left.value === "Math") { - warning("W063", left); - } else if (state.option.newcap) { - warning("W064", left); - } - } - } - } - } - - if (state.tokens.next.id !== ")") { - for (;;) { - p[p.length] = expression(10); - n += 1; - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - } - - advance(")"); - nospace(state.tokens.prev, state.tokens.curr); - - if (typeof left === "object") { - if (state.option.inES3() && left.value === "parseInt" && n === 1) { - warning("W065", state.tokens.curr); - } - if (!state.option.evil) { - if (left.value === "eval" || left.value === "Function" || - left.value === "execScript") { - warning("W061", left); - - if (p[0] && [0].id === "(string)") { - addInternalSrc(left, p[0].value); - } - } else if (p[0] && p[0].id === "(string)" && - (left.value === "setTimeout" || - left.value === "setInterval")) { - warning("W066", left); - addInternalSrc(left, p[0].value); - - // window.setTimeout/setInterval - } else if (p[0] && p[0].id === "(string)" && - left.value === "." && - left.left.value === "window" && - (left.right === "setTimeout" || - left.right === "setInterval")) { - warning("W066", left); - addInternalSrc(left, p[0].value); - } - } - if (!left.identifier && left.id !== "." && left.id !== "[" && - left.id !== "(" && left.id !== "&&" && left.id !== "||" && - left.id !== "?") { - warning("W067", left); - } - } - - that.left = left; - return that; - }, 155, true).exps = true; - - prefix("(", function () { - nospace(); - var bracket, brackets = []; - var pn, pn1, i = 0; - var ret; - var parens = 1; - - do { - pn = peek(i); - - if (pn.value === "(") { - parens += 1; - } else if (pn.value === ")") { - parens -= 1; - } - - i += 1; - pn1 = peek(i); - i += 1; - } while (!(parens === 0 && pn.value === ")") && - pn1.value !== "=>" && pn1.value !== ";" && pn1.type !== "(end)"); - - if (state.tokens.next.id === "function") { - state.tokens.next.immed = true; - } - - var exprs = []; - - if (state.tokens.next.id !== ")") { - for (;;) { - if (pn1.value === "=>" && _.contains(["{", "["], state.tokens.next.value)) { - bracket = state.tokens.next; - bracket.left = destructuringExpression(); - brackets.push(bracket); - for (var t in bracket.left) { - exprs.push(bracket.left[t].token); - } - } else { - exprs.push(expression(10)); - } - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - } - - advance(")", this); - nospace(state.tokens.prev, state.tokens.curr); - if (state.option.immed && exprs[0] && exprs[0].id === "function") { - if (state.tokens.next.id !== "(" && - (state.tokens.next.id !== "." || (peek().value !== "call" && peek().value !== "apply"))) { - warning("W068", this); - } - } - - if (state.tokens.next.value === "=>") { - return exprs; - } - if (!exprs.length) { - return; - } - if (exprs.length > 1) { - ret = Object.create(state.syntax[","]); - ret.exprs = exprs; - } else { - ret = exprs[0]; - } - if (ret) { - ret.paren = true; - } - return ret; - }); - - application("=>"); - - infix("[", function (left, that) { - nobreak(state.tokens.prev, state.tokens.curr); - nospace(); - var e = expression(10), s; - if (e && e.type === "(string)") { - if (!state.option.evil && (e.value === "eval" || e.value === "execScript")) { - warning("W061", that); - } - - countMember(e.value); - if (!state.option.sub && reg.identifier.test(e.value)) { - s = state.syntax[e.value]; - if (!s || !isReserved(s)) { - warning("W069", state.tokens.prev, e.value); - } - } - } - advance("]", that); - - if (e && e.value === "hasOwnProperty" && state.tokens.next.value === "=") { - warning("W001"); - } - - nospace(state.tokens.prev, state.tokens.curr); - that.left = left; - that.right = e; - return that; - }, 160, true); - - function comprehensiveArrayExpression() { - var res = {}; - res.exps = true; - funct["(comparray)"].stack(); - - // Handle reversed for expressions, used in spidermonkey - var reversed = false; - if (state.tokens.next.value !== "for") { - reversed = true; - if (!state.option.inMoz(true)) { - warning("W116", state.tokens.next, "for", state.tokens.next.value); - } - funct["(comparray)"].setState("use"); - res.right = expression(10); - } - - advance("for"); - if (state.tokens.next.value === "each") { - advance("each"); - if (!state.option.inMoz(true)) { - warning("W118", state.tokens.curr, "for each"); - } - } - advance("("); - funct["(comparray)"].setState("define"); - res.left = expression(130); - if (_.contains(["in", "of"], state.tokens.next.value)) { - advance(); - } else { - error("E045", state.tokens.curr); - } - funct["(comparray)"].setState("generate"); - expression(10); - - advance(")"); - if (state.tokens.next.value === "if") { - advance("if"); - advance("("); - funct["(comparray)"].setState("filter"); - res.filter = expression(10); - advance(")"); - } - - if (!reversed) { - funct["(comparray)"].setState("use"); - res.right = expression(10); - } - - advance("]"); - funct["(comparray)"].unstack(); - return res; - } - - prefix("[", function () { - var blocktype = lookupBlockType(true); - if (blocktype.isCompArray) { - if (!state.option.inESNext()) { - warning("W119", state.tokens.curr, "array comprehension"); - } - return comprehensiveArrayExpression(); - } else if (blocktype.isDestAssign && !state.option.inESNext()) { - warning("W104", state.tokens.curr, "destructuring assignment"); - } - var b = state.tokens.curr.line !== state.tokens.next.line; - this.first = []; - if (b) { - indent += state.option.indent; - if (state.tokens.next.from === indent + state.option.indent) { - indent += state.option.indent; - } - } - while (state.tokens.next.id !== "(end)") { - while (state.tokens.next.id === ",") { - if (!state.option.inES5()) - warning("W070"); - advance(","); - } - if (state.tokens.next.id === "]") { - break; - } - if (b && state.tokens.curr.line !== state.tokens.next.line) { - indentation(); - } - this.first.push(expression(10)); - if (state.tokens.next.id === ",") { - comma({ allowTrailing: true }); - if (state.tokens.next.id === "]" && !state.option.inES5(true)) { - warning("W070", state.tokens.curr); - break; - } - } else { - break; - } - } - if (b) { - indent -= state.option.indent; - indentation(); - } - advance("]", this); - return this; - }, 160); - - - function property_name() { - var id = optionalidentifier(false, true); - - if (!id) { - if (state.tokens.next.id === "(string)") { - id = state.tokens.next.value; - advance(); - } else if (state.tokens.next.id === "(number)") { - id = state.tokens.next.value.toString(); - advance(); - } - } - - if (id === "hasOwnProperty") { - warning("W001"); - } - - return id; - } - - function functionparams(parsed) { - var curr, next; - var params = []; - var ident; - var tokens = []; - var t; - var pastDefault = false; - - if (parsed) { - if (Array.isArray(parsed)) { - for (var i in parsed) { - curr = parsed[i]; - if (_.contains(["{", "["], curr.id)) { - for (t in curr.left) { - t = tokens[t]; - if (t && t.id) { - params.push(t.id); - addlabel(t.id, { type: "unused", token: t.token }); - } - } - } else if (curr.value === "...") { - if (!state.option.inESNext()) { - warning("W104", curr, "spread/rest operator"); - } - continue; - } else if (curr.value !== ",") { - params.push(curr.value); - addlabel(curr.value, { type: "unused", token: curr }); - } - } - return params; - } else { - if (parsed.identifier === true) { - addlabel(parsed.value, { type: "unused", token: parsed }); - return [parsed]; - } - } - } - - next = state.tokens.next; - - advance("("); - nospace(); - - if (state.tokens.next.id === ")") { - advance(")"); - return; - } - - for (;;) { - if (_.contains(["{", "["], state.tokens.next.id)) { - tokens = destructuringExpression(); - for (t in tokens) { - t = tokens[t]; - if (t.id) { - params.push(t.id); - addlabel(t.id, { type: "unused", token: t.token }); - } - } - } else if (state.tokens.next.value === "...") { - if (!state.option.inESNext()) { - warning("W104", state.tokens.next, "spread/rest operator"); - } - advance("..."); - nospace(); - ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); - } else { - ident = identifier(true); - params.push(ident); - addlabel(ident, { type: "unused", token: state.tokens.curr }); - } - - // it is a syntax error to have a regular argument after a default argument - if (pastDefault) { - if (state.tokens.next.id !== "=") { - error("E051", state.tokens.current); - } - } - if (state.tokens.next.id === "=") { - if (!state.option.inESNext()) { - warning("W119", state.tokens.next, "default parameters"); - } - advance("="); - pastDefault = true; - expression(10); - } - if (state.tokens.next.id === ",") { - comma(); - } else { - advance(")", next); - nospace(state.tokens.prev, state.tokens.curr); - return params; - } - } - } - - function setprop(funct, name, values) { - if (!funct["(properties)"][name]) { - funct["(properties)"][name] = { unused: false }; - } - - _.extend(funct["(properties)"][name], values); - } - - function getprop(funct, name, prop) { - if (!funct["(properties)"][name]) - return null; - - return funct["(properties)"][name][prop] || null; - } - - function functor(name, token, scope, overwrites) { - var funct = { - "(name)" : name, - "(breakage)" : 0, - "(loopage)" : 0, - "(scope)" : scope, - "(tokens)" : {}, - "(properties)": {}, - - "(catch)" : false, - "(global)" : false, - - "(line)" : null, - "(character)" : null, - "(metrics)" : null, - "(statement)" : null, - "(context)" : null, - "(blockscope)": null, - "(comparray)" : null, - "(generator)" : null, - "(params)" : null - }; - - if (token) { - _.extend(funct, { - "(line)" : token.line, - "(character)": token.character, - "(metrics)" : createMetrics(token) - }); - } - - _.extend(funct, overwrites); - - if (funct["(context)"]) { - funct["(blockscope)"] = funct["(context)"]["(blockscope)"]; - funct["(comparray)"] = funct["(context)"]["(comparray)"]; - } - - return funct; - } - - function doFunction(name, statement, generator, fatarrowparams) { - var f; - var oldOption = state.option; - var oldIgnored = state.ignored; - var oldScope = scope; - - state.option = Object.create(state.option); - state.ignored = Object.create(state.ignored); - scope = Object.create(scope); - - funct = functor(name || "\"" + anonname + "\"", state.tokens.next, scope, { - "(statement)": statement, - "(context)": funct, - "(generator)": generator ? true : null - }); - - f = funct; - state.tokens.curr.funct = funct; - - functions.push(funct); - - if (name) { - addlabel(name, { type: "function" }); - } - - funct["(params)"] = functionparams(fatarrowparams); - funct["(metrics)"].verifyMaxParametersPerFunction(funct["(params)"]); - - // So we parse fat-arrow functions after we encounter =>. So basically - // doFunction is called with the left side of => as its last argument. - // This means that the parser, at that point, had already added its - // arguments to the undefs array and here we undo that. - - JSHINT.undefs = _.filter(JSHINT.undefs, function (item) { - return !_.contains(_.union(fatarrowparams), item[2]); - }); - - block(false, true, true, fatarrowparams ? true : false); - - if (!state.option.noyield && generator && funct["(generator)"] !== "yielded") { - warning("W124", state.tokens.curr); - } - - funct["(metrics)"].verifyMaxStatementsPerFunction(); - funct["(metrics)"].verifyMaxComplexityPerFunction(); - funct["(unusedOption)"] = state.option.unused; - - scope = oldScope; - state.option = oldOption; - state.ignored = oldIgnored; - funct["(last)"] = state.tokens.curr.line; - funct["(lastcharacter)"] = state.tokens.curr.character; - - _.map(Object.keys(funct), function (key) { - if (key[0] === "(") return; - funct["(blockscope)"].unshadow(key); - }); - - funct = funct["(context)"]; - - return f; - } - - function createMetrics(functionStartToken) { - return { - statementCount: 0, - nestedBlockDepth: -1, - ComplexityCount: 1, - - verifyMaxStatementsPerFunction: function () { - if (state.option.maxstatements && - this.statementCount > state.option.maxstatements) { - warning("W071", functionStartToken, this.statementCount); - } - }, - - verifyMaxParametersPerFunction: function (params) { - params = params || []; - - if (state.option.maxparams && params.length > state.option.maxparams) { - warning("W072", functionStartToken, params.length); - } - }, - - verifyMaxNestedBlockDepthPerFunction: function () { - if (state.option.maxdepth && - this.nestedBlockDepth > 0 && - this.nestedBlockDepth === state.option.maxdepth + 1) { - warning("W073", null, this.nestedBlockDepth); - } - }, - - verifyMaxComplexityPerFunction: function () { - var max = state.option.maxcomplexity; - var cc = this.ComplexityCount; - if (max && cc > max) { - warning("W074", functionStartToken, cc); - } - } - }; - } - - function increaseComplexityCount() { - funct["(metrics)"].ComplexityCount += 1; - } - - // Parse assignments that were found instead of conditionals. - // For example: if (a = 1) { ... } - - function checkCondAssignment(expr) { - var id, paren; - if (expr) { - id = expr.id; - paren = expr.paren; - if (id === "," && (expr = expr.exprs[expr.exprs.length - 1])) { - id = expr.id; - paren = paren || expr.paren; - } - } - switch (id) { - case "=": - case "+=": - case "-=": - case "*=": - case "%=": - case "&=": - case "|=": - case "^=": - case "/=": - if (!paren && !state.option.boss) { - warning("W084"); - } - } - } - - - (function (x) { - x.nud = function (isclassdef) { - var b, f, i, p, t, g; - var props = {}; // All properties, including accessors - var tag = ""; - - function saveProperty(name, tkn) { - if (props[name] && _.has(props, name)) - warning("W075", state.tokens.next, i); - else - props[name] = {}; - - props[name].basic = true; - props[name].basictkn = tkn; - } - - function saveSetter(name, tkn) { - if (props[name] && _.has(props, name)) { - if (props[name].basic || props[name].setter) - warning("W075", state.tokens.next, i); - } else { - props[name] = {}; - } - - props[name].setter = true; - props[name].setterToken = tkn; - } - - function saveGetter(name) { - if (props[name] && _.has(props, name)) { - if (props[name].basic || props[name].getter) - warning("W075", state.tokens.next, i); - } else { - props[name] = {}; - } - - props[name].getter = true; - props[name].getterToken = state.tokens.curr; - } - - b = state.tokens.curr.line !== state.tokens.next.line; - if (b) { - indent += state.option.indent; - if (state.tokens.next.from === indent + state.option.indent) { - indent += state.option.indent; - } - } - - for (;;) { - if (state.tokens.next.id === "}") { - break; - } - - if (b) { - indentation(); - } - - if (isclassdef && state.tokens.next.value === "static") { - advance("static"); - tag = "static "; - } - - if (state.tokens.next.value === "get" && peek().id !== ":") { - advance("get"); - - if (!state.option.inES5(!isclassdef)) { - error("E034"); - } - - i = property_name(); - - // ES6 allows for get() {...} and set() {...} method - // definition shorthand syntax, so we don't produce an error - // if the esnext option is enabled. - if (!i && !state.option.inESNext()) { - error("E035"); - } - - // It is a Syntax Error if PropName of MethodDefinition is - // "constructor" and SpecialMethod of MethodDefinition is true. - if (isclassdef && i === "constructor") { - error("E049", state.tokens.next, "class getter method", i); - } - - // We don't want to save this getter unless it's an actual getter - // and not an ES6 concise method - if (i) { - saveGetter(tag + i); - } - - t = state.tokens.next; - adjacent(state.tokens.curr, state.tokens.next); - f = doFunction(); - p = f["(params)"]; - - // Don't warn about getter/setter pairs if this is an ES6 concise method - if (i && p) { - warning("W076", t, p[0], i); - } - - adjacent(state.tokens.curr, state.tokens.next); - } else if (state.tokens.next.value === "set" && peek().id !== ":") { - advance("set"); - - if (!state.option.inES5(!isclassdef)) { - error("E034"); - } - - i = property_name(); - - // ES6 allows for get() {...} and set() {...} method - // definition shorthand syntax, so we don't produce an error - // if the esnext option is enabled. - if (!i && !state.option.inESNext()) { - error("E035"); - } - - // It is a Syntax Error if PropName of MethodDefinition is - // "constructor" and SpecialMethod of MethodDefinition is true. - if (isclassdef && i === "constructor") { - error("E049", state.tokens.next, "class setter method", i); - } - - // We don't want to save this getter unless it's an actual getter - // and not an ES6 concise method - if (i) { - saveSetter(tag + i, state.tokens.next); - } - - t = state.tokens.next; - adjacent(state.tokens.curr, state.tokens.next); - f = doFunction(); - p = f["(params)"]; - - // Don't warn about getter/setter pairs if this is an ES6 concise method - if (i && (!p || p.length !== 1)) { - warning("W077", t, i); - } - } else { - g = false; - if (state.tokens.next.value === "*" && state.tokens.next.type === "(punctuator)") { - if (!state.option.inESNext()) { - warning("W104", state.tokens.next, "generator functions"); - } - advance("*"); - g = true; - } - i = property_name(); - saveProperty(tag + i, state.tokens.next); - - if (typeof i !== "string") { - break; - } - - if (state.tokens.next.value === "(") { - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "concise methods"); - } - doFunction(i, undefined, g); - } else if (!isclassdef) { - advance(":"); - nonadjacent(state.tokens.curr, state.tokens.next); - expression(10); - } - } - // It is a Syntax Error if PropName of MethodDefinition is "prototype". - if (isclassdef && i === "prototype") { - error("E049", state.tokens.next, "class method", i); - } - - countMember(i); - if (isclassdef) { - tag = ""; - continue; - } - if (state.tokens.next.id === ",") { - comma({ allowTrailing: true, property: true }); - if (state.tokens.next.id === ",") { - warning("W070", state.tokens.curr); - } else if (state.tokens.next.id === "}" && !state.option.inES5(true)) { - warning("W070", state.tokens.curr); - } - } else { - break; - } - } - if (b) { - indent -= state.option.indent; - indentation(); - } - advance("}", this); - - // Check for lonely setters if in the ES5 mode. - if (state.option.inES5()) { - for (var name in props) { - if (_.has(props, name) && props[name].setter && !props[name].getter) { - warning("W078", props[name].setterToken); - } - } - } - return this; - }; - x.fud = function () { - error("E036", state.tokens.curr); - }; - }(delim("{"))); - - function destructuringExpression() { - var id, ids; - var identifiers = []; - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "destructuring expression"); - } - var nextInnerDE = function () { - var ident; - if (_.contains(["[", "{"], state.tokens.next.value)) { - ids = destructuringExpression(); - for (var id in ids) { - id = ids[id]; - identifiers.push({ id: id.id, token: id.token }); - } - } else if (state.tokens.next.value === ",") { - identifiers.push({ id: null, token: state.tokens.curr }); - } else if (state.tokens.next.value === "(") { - advance("("); - nextInnerDE(); - advance(")"); - } else { - ident = identifier(); - if (ident) - identifiers.push({ id: ident, token: state.tokens.curr }); - } - }; - if (state.tokens.next.value === "[") { - advance("["); - nextInnerDE(); - while (state.tokens.next.value !== "]") { - advance(","); - nextInnerDE(); - } - advance("]"); - } else if (state.tokens.next.value === "{") { - advance("{"); - id = identifier(); - if (state.tokens.next.value === ":") { - advance(":"); - nextInnerDE(); - } else { - identifiers.push({ id: id, token: state.tokens.curr }); - } - while (state.tokens.next.value !== "}") { - advance(","); - id = identifier(); - if (state.tokens.next.value === ":") { - advance(":"); - nextInnerDE(); - } else { - identifiers.push({ id: id, token: state.tokens.curr }); - } - } - advance("}"); - } - return identifiers; - } - - function destructuringExpressionMatch(tokens, value) { - var first = value.first; - - if (!first) - return; - - _.zip(tokens, Array.isArray(first) ? first : [ first ]).forEach(function (val) { - var token = val[0]; - var value = val[1]; - - if (token && value) - token.first = value; - else if (token && token.first && !value) - warning("W080", token.first, token.first.value); - }); - } - - var conststatement = stmt("const", function (prefix) { - var tokens; - var value; - var lone; // State variable to know if it is a lone identifier, or a destructuring statement. - - if (!state.option.inESNext()) - warning("W104", state.tokens.curr, "const"); - - this.first = []; - for (;;) { - var names = []; - nonadjacent(state.tokens.curr, state.tokens.next); - if (_.contains(["{", "["], state.tokens.next.value)) { - tokens = destructuringExpression(); - lone = false; - } else { - tokens = [ { id: identifier(), token: state.tokens.curr } ]; - lone = true; - } - for (var t in tokens) { - if (tokens.hasOwnProperty(t)) { - t = tokens[t]; - if (funct[t.id] === "const") { - warning("E011", null, t.id); - } - if (funct["(global)"] && predefined[t.id] === false) { - warning("W079", t.token, t.id); - } - if (t.id) { - addlabel(t.id, { token: t.token, type: "const", unused: true }); - names.push(t.token); - } - } - } - if (prefix) { - break; - } - - this.first = this.first.concat(names); - - if (state.tokens.next.id !== "=") { - warning("E012", state.tokens.curr, state.tokens.curr.value); - } - - if (state.tokens.next.id === "=") { - nonadjacent(state.tokens.curr, state.tokens.next); - advance("="); - nonadjacent(state.tokens.curr, state.tokens.next); - if (state.tokens.next.id === "undefined") { - warning("W080", state.tokens.prev, state.tokens.prev.value); - } - if (peek(0).id === "=" && state.tokens.next.identifier) { - warning("W120", state.tokens.next, state.tokens.next.value); - } - value = expression(10); - if (lone) { - tokens[0].first = value; - } else { - destructuringExpressionMatch(names, value); - } - } - - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - return this; - }); - - conststatement.exps = true; - var varstatement = stmt("var", function (prefix) { - // JavaScript does not have block scope. It only has function scope. So, - // declaring a variable in a block can have unexpected consequences. - var tokens, lone, value; - - if (funct["(onevar)"] && state.option.onevar) { - warning("W081"); - } else if (!funct["(global)"]) { - funct["(onevar)"] = true; - } - - this.first = []; - for (;;) { - var names = []; - nonadjacent(state.tokens.curr, state.tokens.next); - if (_.contains(["{", "["], state.tokens.next.value)) { - tokens = destructuringExpression(); - lone = false; - } else { - tokens = [ { id: identifier(), token: state.tokens.curr } ]; - lone = true; - } - for (var t in tokens) { - if (tokens.hasOwnProperty(t)) { - t = tokens[t]; - if (state.option.inESNext() && funct[t.id] === "const") { - warning("E011", null, t.id); - } - if (funct["(global)"] && predefined[t.id] === false) { - warning("W079", t.token, t.id); - } - if (t.id) { - addlabel(t.id, { type: "unused", token: t.token }); - names.push(t.token); - } - } - } - if (prefix) { - break; - } - - this.first = this.first.concat(names); - - if (state.tokens.next.id === "=") { - nonadjacent(state.tokens.curr, state.tokens.next); - advance("="); - nonadjacent(state.tokens.curr, state.tokens.next); - if (state.tokens.next.id === "undefined") { - warning("W080", state.tokens.prev, state.tokens.prev.value); - } - if (peek(0).id === "=" && state.tokens.next.identifier) { - warning("W120", state.tokens.next, state.tokens.next.value); - } - value = expression(10); - if (lone) { - tokens[0].first = value; - } else { - destructuringExpressionMatch(names, value); - } - } - - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - return this; - }); - varstatement.exps = true; - - var letstatement = stmt("let", function (prefix) { - var tokens, lone, value, letblock; - - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "let"); - } - - if (state.tokens.next.value === "(") { - if (!state.option.inMoz(true)) { - warning("W118", state.tokens.next, "let block"); - } - advance("("); - funct["(blockscope)"].stack(); - letblock = true; - } else if (funct["(nolet)"]) { - error("E048", state.tokens.curr); - } - - if (funct["(onevar)"] && state.option.onevar) { - warning("W081"); - } else if (!funct["(global)"]) { - funct["(onevar)"] = true; - } - - this.first = []; - for (;;) { - var names = []; - nonadjacent(state.tokens.curr, state.tokens.next); - if (_.contains(["{", "["], state.tokens.next.value)) { - tokens = destructuringExpression(); - lone = false; - } else { - tokens = [ { id: identifier(), token: state.tokens.curr.value } ]; - lone = true; - } - for (var t in tokens) { - if (tokens.hasOwnProperty(t)) { - t = tokens[t]; - if (state.option.inESNext() && funct[t.id] === "const") { - warning("E011", null, t.id); - } - if (funct["(global)"] && predefined[t.id] === false) { - warning("W079", t.token, t.id); - } - if (t.id && !funct["(nolet)"]) { - addlabel(t.id, { type: "unused", token: t.token, islet: true }); - names.push(t.token); - } - } - } - if (prefix) { - break; - } - - this.first = this.first.concat(names); - - if (state.tokens.next.id === "=") { - nonadjacent(state.tokens.curr, state.tokens.next); - advance("="); - nonadjacent(state.tokens.curr, state.tokens.next); - if (state.tokens.next.id === "undefined") { - warning("W080", state.tokens.prev, state.tokens.prev.value); - } - if (peek(0).id === "=" && state.tokens.next.identifier) { - warning("W120", state.tokens.next, state.tokens.next.value); - } - value = expression(10); - if (lone) { - tokens[0].first = value; - } else { - destructuringExpressionMatch(names, value); - } - } - - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - if (letblock) { - advance(")"); - block(true, true); - this.block = true; - funct["(blockscope)"].unstack(); - } - - return this; - }); - letstatement.exps = true; - - blockstmt("class", function () { - return classdef.call(this, true); - }); - - function classdef(stmt) { - /*jshint validthis:true */ - if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "class"); - } - if (stmt) { - // BindingIdentifier - this.name = identifier(); - addlabel(this.name, { type: "unused", token: state.tokens.curr }); - } else if (state.tokens.next.identifier && state.tokens.next.value !== "extends") { - // BindingIdentifier(opt) - this.name = identifier(); - } - classtail(this); - return this; - } - - function classtail(c) { - var strictness = state.directive["use strict"]; - - // ClassHeritage(opt) - if (state.tokens.next.value === "extends") { - advance("extends"); - c.heritage = expression(10); - } - - // A ClassBody is always strict code. - state.directive["use strict"] = true; - advance("{"); - // ClassBody(opt) - c.body = state.syntax["{"].nud(true); - state.directive["use strict"] = strictness; - } - - blockstmt("function", function () { - var generator = false; - if (state.tokens.next.value === "*") { - advance("*"); - if (state.option.inESNext(true)) { - generator = true; - } else { - warning("W119", state.tokens.curr, "function*"); - } - } - if (inblock) { - warning("W082", state.tokens.curr); - - } - var i = identifier(); - if (funct[i] === "const") { - warning("E011", null, i); - } - adjacent(state.tokens.curr, state.tokens.next); - addlabel(i, { type: "unction", token: state.tokens.curr }); - - doFunction(i, { statement: true }, generator); - if (state.tokens.next.id === "(" && state.tokens.next.line === state.tokens.curr.line) { - error("E039"); - } - return this; - }); - - prefix("function", function () { - var generator = false; - if (state.tokens.next.value === "*") { - if (!state.option.inESNext()) { - warning("W119", state.tokens.curr, "function*"); - } - advance("*"); - generator = true; - } - var i = optionalidentifier(); - if (i || state.option.gcl) { - adjacent(state.tokens.curr, state.tokens.next); - } else { - nonadjacent(state.tokens.curr, state.tokens.next); - } - doFunction(i, undefined, generator); - if (!state.option.loopfunc && funct["(loopage)"]) { - warning("W083"); - } - return this; - }); - - blockstmt("if", function () { - var t = state.tokens.next; - increaseComplexityCount(); - state.condition = true; - advance("("); - nonadjacent(this, t); - nospace(); - checkCondAssignment(expression(0)); - advance(")", t); - state.condition = false; - nospace(state.tokens.prev, state.tokens.curr); - block(true, true); - if (state.tokens.next.id === "else") { - nonadjacent(state.tokens.curr, state.tokens.next); - advance("else"); - if (state.tokens.next.id === "if" || state.tokens.next.id === "switch") { - statement(true); - } else { - block(true, true); - } - } - return this; - }); - - blockstmt("try", function () { - var b; - - function doCatch() { - var oldScope = scope; - var e; - - advance("catch"); - nonadjacent(state.tokens.curr, state.tokens.next); - advance("("); - - scope = Object.create(oldScope); - - e = state.tokens.next.value; - if (state.tokens.next.type !== "(identifier)") { - e = null; - warning("E030", state.tokens.next, e); - } - - advance(); - - funct = functor("(catch)", state.tokens.next, scope, { - "(context)" : funct, - "(breakage)" : funct["(breakage)"], - "(loopage)" : funct["(loopage)"], - "(statement)": false, - "(catch)" : true - }); - - if (e) { - addlabel(e, { type: "exception" }); - } - - if (state.tokens.next.value === "if") { - if (!state.option.inMoz(true)) { - warning("W118", state.tokens.curr, "catch filter"); - } - advance("if"); - expression(0); - } - - advance(")"); - - state.tokens.curr.funct = funct; - functions.push(funct); - - block(false); - - scope = oldScope; - - funct["(last)"] = state.tokens.curr.line; - funct["(lastcharacter)"] = state.tokens.curr.character; - funct = funct["(context)"]; - } - - block(true); - - while (state.tokens.next.id === "catch") { - increaseComplexityCount(); - if (b && (!state.option.inMoz(true))) { - warning("W118", state.tokens.next, "multiple catch blocks"); - } - doCatch(); - b = true; - } - - if (state.tokens.next.id === "finally") { - advance("finally"); - block(true); - return; - } - - if (!b) { - error("E021", state.tokens.next, "catch", state.tokens.next.value); - } - - return this; - }); - - blockstmt("while", function () { - var t = state.tokens.next; - funct["(breakage)"] += 1; - funct["(loopage)"] += 1; - increaseComplexityCount(); - advance("("); - nonadjacent(this, t); - nospace(); - checkCondAssignment(expression(0)); - advance(")", t); - nospace(state.tokens.prev, state.tokens.curr); - block(true, true); - funct["(breakage)"] -= 1; - funct["(loopage)"] -= 1; - return this; - }).labelled = true; - - blockstmt("with", function () { - var t = state.tokens.next; - if (state.directive["use strict"]) { - error("E010", state.tokens.curr); - } else if (!state.option.withstmt) { - warning("W085", state.tokens.curr); - } - - advance("("); - nonadjacent(this, t); - nospace(); - expression(0); - advance(")", t); - nospace(state.tokens.prev, state.tokens.curr); - block(true, true); - - return this; - }); - - blockstmt("switch", function () { - var t = state.tokens.next; - var g = false; - var noindent = false; - - funct["(breakage)"] += 1; - advance("("); - nonadjacent(this, t); - nospace(); - checkCondAssignment(expression(0)); - advance(")", t); - nospace(state.tokens.prev, state.tokens.curr); - nonadjacent(state.tokens.curr, state.tokens.next); - t = state.tokens.next; - advance("{"); - nonadjacent(state.tokens.curr, state.tokens.next); - - if (state.tokens.next.from === indent) - noindent = true; - - if (!noindent) - indent += state.option.indent; - - this.cases = []; - - for (;;) { - switch (state.tokens.next.id) { - case "case": - switch (funct["(verb)"]) { - case "yield": - case "break": - case "case": - case "continue": - case "return": - case "switch": - case "throw": - break; - default: - // You can tell JSHint that you don't use break intentionally by - // adding a comment /* falls through */ on a line just before - // the next `case`. - if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { - warning("W086", state.tokens.curr, "case"); - } - } - indentation(); - advance("case"); - this.cases.push(expression(20)); - increaseComplexityCount(); - g = true; - advance(":"); - funct["(verb)"] = "case"; - break; - case "default": - switch (funct["(verb)"]) { - case "yield": - case "break": - case "continue": - case "return": - case "throw": - break; - default: - // Do not display a warning if 'default' is the first statement or if - // there is a special /* falls through */ comment. - if (this.cases.length) { - if (!reg.fallsThrough.test(state.lines[state.tokens.next.line - 2])) { - warning("W086", state.tokens.curr, "default"); - } - } - } - indentation(); - advance("default"); - g = true; - advance(":"); - break; - case "}": - if (!noindent) - indent -= state.option.indent; - indentation(); - advance("}", t); - funct["(breakage)"] -= 1; - funct["(verb)"] = undefined; - return; - case "(end)": - error("E023", state.tokens.next, "}"); - return; - default: - indent += state.option.indent; - if (g) { - switch (state.tokens.curr.id) { - case ",": - error("E040"); - return; - case ":": - g = false; - statements(); - break; - default: - error("E025", state.tokens.curr); - return; - } - } else { - if (state.tokens.curr.id === ":") { - advance(":"); - error("E024", state.tokens.curr, ":"); - statements(); - } else { - error("E021", state.tokens.next, "case", state.tokens.next.value); - return; - } - } - indent -= state.option.indent; - } - } - }).labelled = true; - - stmt("debugger", function () { - if (!state.option.debug) { - warning("W087", this); - } - return this; - }).exps = true; - - (function () { - var x = stmt("do", function () { - funct["(breakage)"] += 1; - funct["(loopage)"] += 1; - increaseComplexityCount(); - - this.first = block(true, true); - advance("while"); - var t = state.tokens.next; - nonadjacent(state.tokens.curr, t); - advance("("); - nospace(); - checkCondAssignment(expression(0)); - advance(")", t); - nospace(state.tokens.prev, state.tokens.curr); - funct["(breakage)"] -= 1; - funct["(loopage)"] -= 1; - return this; - }); - x.labelled = true; - x.exps = true; - }()); - - blockstmt("for", function () { - var s, t = state.tokens.next; - var letscope = false; - var foreachtok = null; - - if (t.value === "each") { - foreachtok = t; - advance("each"); - if (!state.option.inMoz(true)) { - warning("W118", state.tokens.curr, "for each"); - } - } - - funct["(breakage)"] += 1; - funct["(loopage)"] += 1; - increaseComplexityCount(); - advance("("); - nonadjacent(this, t); - nospace(); - - // what kind of for(â€Ļ) statement it is? for(â€Ļofâ€Ļ)? for(â€Ļinâ€Ļ)? for(â€Ļ;â€Ļ;â€Ļ)? - var nextop; // contains the token of the "in" or "of" operator - var i = 0; - var inof = ["in", "of"]; - do { - nextop = peek(i); - ++i; - } while (!_.contains(inof, nextop.value) && nextop.value !== ";" && - nextop.type !== "(end)"); - - // if we're in a for (â€Ļ in|of â€Ļ) statement - if (_.contains(inof, nextop.value)) { - if (!state.option.inESNext() && nextop.value === "of") { - error("W104", nextop, "for of"); - } - if (state.tokens.next.id === "var") { - advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud, true); - } else if (state.tokens.next.id === "let") { - advance("let"); - // create a new block scope - letscope = true; - funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud, true); - } else { - switch (funct[state.tokens.next.value]) { - case "unused": - funct[state.tokens.next.value] = "var"; - break; - case "var": - break; - default: - if (!funct["(blockscope)"].getlabel(state.tokens.next.value)) - warning("W088", state.tokens.next, state.tokens.next.value); - } - advance(); - } - advance(nextop.value); - expression(20); - advance(")", t); - s = block(true, true); - if (state.option.forin && s && (s.length > 1 || typeof s[0] !== "object" || - s[0].value !== "if")) { - warning("W089", this); - } - funct["(breakage)"] -= 1; - funct["(loopage)"] -= 1; - } else { - if (foreachtok) { - error("E045", foreachtok); - } - if (state.tokens.next.id !== ";") { - if (state.tokens.next.id === "var") { - advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud); - } else if (state.tokens.next.id === "let") { - advance("let"); - // create a new block scope - letscope = true; - funct["(blockscope)"].stack(); - state.syntax["let"].fud.call(state.syntax["let"].fud); - } else { - for (;;) { - expression(0, "for"); - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - } - } - nolinebreak(state.tokens.curr); - advance(";"); - if (state.tokens.next.id !== ";") { - checkCondAssignment(expression(0)); - } - nolinebreak(state.tokens.curr); - advance(";"); - if (state.tokens.next.id === ";") { - error("E021", state.tokens.next, ")", ";"); - } - if (state.tokens.next.id !== ")") { - for (;;) { - expression(0, "for"); - if (state.tokens.next.id !== ",") { - break; - } - comma(); - } - } - advance(")", t); - nospace(state.tokens.prev, state.tokens.curr); - block(true, true); - funct["(breakage)"] -= 1; - funct["(loopage)"] -= 1; - - } - // unstack loop blockscope - if (letscope) { - funct["(blockscope)"].unstack(); - } - return this; - }).labelled = true; - - - stmt("break", function () { - var v = state.tokens.next.value; - - if (funct["(breakage)"] === 0) - warning("W052", state.tokens.next, this.value); - - if (!state.option.asi) - nolinebreak(this); - - if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { - if (funct[v] !== "label") { - warning("W090", state.tokens.next, v); - } else if (scope[v] !== funct) { - warning("W091", state.tokens.next, v); - } - this.first = state.tokens.next; - advance(); - } - } - reachable("break"); - return this; - }).exps = true; - - - stmt("continue", function () { - var v = state.tokens.next.value; - - if (funct["(breakage)"] === 0) - warning("W052", state.tokens.next, this.value); - - if (!state.option.asi) - nolinebreak(this); - - if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - if (state.tokens.curr.line === state.tokens.next.line) { - if (funct[v] !== "label") { - warning("W090", state.tokens.next, v); - } else if (scope[v] !== funct) { - warning("W091", state.tokens.next, v); - } - this.first = state.tokens.next; - advance(); - } - } else if (!funct["(loopage)"]) { - warning("W052", state.tokens.next, this.value); - } - reachable("continue"); - return this; - }).exps = true; - - - stmt("return", function () { - if (this.line === state.tokens.next.line) { - if (state.tokens.next.id !== ";" && !state.tokens.next.reach) { - nonadjacent(state.tokens.curr, state.tokens.next); - this.first = expression(0); - - if (this.first && - this.first.type === "(punctuator)" && this.first.value === "=" && - !this.first.paren && !state.option.boss) { - warningAt("W093", this.first.line, this.first.character); - } - } - } else { - if (state.tokens.next.type === "(punctuator)" && - ["[", "{", "+", "-"].indexOf(state.tokens.next.value) > -1) { - nolinebreak(this); // always warn (Line breaking error) - } - } - reachable("return"); - return this; - }).exps = true; - - (function (x) { - x.exps = true; - x.lbp = 25; - }(prefix("yield", function () { - var prev = state.tokens.prev; - if (state.option.inESNext(true) && !funct["(generator)"]) { - error("E046", state.tokens.curr, "yield"); - } else if (!state.option.inESNext()) { - warning("W104", state.tokens.curr, "yield"); - } - funct["(generator)"] = "yielded"; - if (this.line === state.tokens.next.line || !state.option.inMoz(true)) { - if (state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud) { - nobreaknonadjacent(state.tokens.curr, state.tokens.next); - this.first = expression(10); - - if (this.first.type === "(punctuator)" && this.first.value === "=" && - !this.first.paren && !state.option.boss) { - warningAt("W093", this.first.line, this.first.character); - } - } - - if (state.option.inMoz(true) && state.tokens.next.id !== ")" && - (prev.lbp > 30 || (!prev.assign && !isEndOfExpr()) || prev.id === "yield")) { - error("E050", this); - } - } else if (!state.option.asi) { - nolinebreak(this); // always warn (Line breaking error) - } - return this; - }))); - - - stmt("throw", function () { - nolinebreak(this); - nonadjacent(state.tokens.curr, state.tokens.next); - this.first = expression(20); - reachable("throw"); - return this; - }).exps = true; - - stmt("import", function () { - if (!state.option.inESNext()) { - warning("W119", state.tokens.curr, "import"); - } - - if (state.tokens.next.identifier) { - this.name = identifier(); - addlabel(this.name, { type: "unused", token: state.tokens.curr }); - } else { - advance("{"); - for (;;) { - var importName; - if (state.tokens.next.type === "default") { - importName = "default"; - advance("default"); - } else { - importName = identifier(); - } - if (state.tokens.next.value === "as") { - advance("as"); - importName = identifier(); - } - addlabel(importName, { type: "unused", token: state.tokens.curr }); - - if (state.tokens.next.value === ",") { - advance(","); - } else if (state.tokens.next.value === "}") { - advance("}"); - break; - } else { - error("E024", state.tokens.next, state.tokens.next.value); - break; - } - } - } - - advance("from"); - advance("(string)"); - return this; - }).exps = true; - - stmt("export", function () { - if (!state.option.inESNext()) { - warning("W119", state.tokens.curr, "export"); - } - - if (state.tokens.next.type === "default") { - advance("default"); - if (state.tokens.next.id === "function" || state.tokens.next.id === "class") { - this.block = true; - } - this.exportee = expression(10); - - return this; - } - - if (state.tokens.next.value === "{") { - advance("{"); - for (;;) { - identifier(); - - if (state.tokens.next.value === ",") { - advance(","); - } else if (state.tokens.next.value === "}") { - advance("}"); - break; - } else { - error("E024", state.tokens.next, state.tokens.next.value); - break; - } - } - return this; - } - - if (state.tokens.next.id === "var") { - advance("var"); - state.syntax["var"].fud.call(state.syntax["var"].fud); - } else if (state.tokens.next.id === "let") { - advance("let"); - state.syntax["let"].fud.call(state.syntax["let"].fud); - } else if (state.tokens.next.id === "const") { - advance("const"); - state.syntax["const"].fud.call(state.syntax["const"].fud); - } else if (state.tokens.next.id === "function") { - this.block = true; - advance("function"); - state.syntax["function"].fud(); - } else if (state.tokens.next.id === "class") { - this.block = true; - advance("class"); - state.syntax["class"].fud(); - } else { - error("E024", state.tokens.next, state.tokens.next.value); - } - - return this; - }).exps = true; - - // Future Reserved Words - - FutureReservedWord("abstract"); - FutureReservedWord("boolean"); - FutureReservedWord("byte"); - FutureReservedWord("char"); - FutureReservedWord("class", { es5: true, nud: classdef }); - FutureReservedWord("double"); - FutureReservedWord("enum", { es5: true }); - FutureReservedWord("export", { es5: true }); - FutureReservedWord("extends", { es5: true }); - FutureReservedWord("final"); - FutureReservedWord("float"); - FutureReservedWord("goto"); - FutureReservedWord("implements", { es5: true, strictOnly: true }); - FutureReservedWord("import", { es5: true }); - FutureReservedWord("int"); - FutureReservedWord("interface", { es5: true, strictOnly: true }); - FutureReservedWord("long"); - FutureReservedWord("native"); - FutureReservedWord("package", { es5: true, strictOnly: true }); - FutureReservedWord("private", { es5: true, strictOnly: true }); - FutureReservedWord("protected", { es5: true, strictOnly: true }); - FutureReservedWord("public", { es5: true, strictOnly: true }); - FutureReservedWord("short"); - FutureReservedWord("static", { es5: true, strictOnly: true }); - FutureReservedWord("super", { es5: true }); - FutureReservedWord("synchronized"); - FutureReservedWord("throws"); - FutureReservedWord("transient"); - FutureReservedWord("volatile"); - - // this function is used to determine whether a squarebracket or a curlybracket - // expression is a comprehension array, destructuring assignment or a json value. - - var lookupBlockType = function () { - var pn, pn1; - var i = -1; - var bracketStack = 0; - var ret = {}; - if (_.contains(["[", "{"], state.tokens.curr.value)) - bracketStack += 1; - do { - pn = (i === -1) ? state.tokens.next : peek(i); - pn1 = peek(i + 1); - i = i + 1; - if (_.contains(["[", "{"], pn.value)) { - bracketStack += 1; - } else if (_.contains(["]", "}"], pn.value)) { - bracketStack -= 1; - } - if (pn.identifier && pn.value === "for" && bracketStack === 1) { - ret.isCompArray = true; - ret.notJson = true; - break; - } - if (_.contains(["}", "]"], pn.value) && pn1.value === "=" && bracketStack === 0) { - ret.isDestAssign = true; - ret.notJson = true; - break; - } - if (pn.value === ";") { - ret.isBlock = true; - ret.notJson = true; - } - } while (bracketStack > 0 && pn.id !== "(end)" && i < 15); - return ret; - }; - - // Check whether this function has been reached for a destructuring assign with undeclared values - function destructuringAssignOrJsonValue() { - // lookup for the assignment (esnext only) - // if it has semicolons, it is a block, so go parse it as a block - // or it's not a block, but there are assignments, check for undeclared variables - - var block = lookupBlockType(); - if (block.notJson) { - if (!state.option.inESNext() && block.isDestAssign) { - warning("W104", state.tokens.curr, "destructuring assignment"); - } - statements(); - // otherwise parse json value - } else { - state.option.laxbreak = true; - state.jsonMode = true; - jsonValue(); - } - } - - // array comprehension parsing function - // parses and defines the three states of the list comprehension in order - // to avoid defining global variables, but keeping them to the list comprehension scope - // only. The order of the states are as follows: - // * "use" which will be the returned iterative part of the list comprehension - // * "define" which will define the variables local to the list comprehension - // * "filter" which will help filter out values - - var arrayComprehension = function () { - var CompArray = function () { - this.mode = "use"; - this.variables = []; - }; - var _carrays = []; - var _current; - function declare(v) { - var l = _current.variables.filter(function (elt) { - // if it has, change its undef state - if (elt.value === v) { - elt.undef = false; - return v; - } - }).length; - return l !== 0; - } - function use(v) { - var l = _current.variables.filter(function (elt) { - // and if it has been defined - if (elt.value === v && !elt.undef) { - if (elt.unused === true) { - elt.unused = false; - } - return v; - } - }).length; - // otherwise we warn about it - return (l === 0); - } - return {stack: function () { - _current = new CompArray(); - _carrays.push(_current); - }, - unstack: function () { - _current.variables.filter(function (v) { - if (v.unused) - warning("W098", v.token, v.value); - if (v.undef) - isundef(v.funct, "W117", v.token, v.value); - }); - _carrays.splice(-1, 1); - _current = _carrays[_carrays.length - 1]; - }, - setState: function (s) { - if (_.contains(["use", "define", "generate", "filter"], s)) - _current.mode = s; - }, - check: function (v) { - if (!_current) { - return; - } - // When we are in "use" state of the list comp, we enqueue that var - if (_current && _current.mode === "use") { - if (use(v)) { - _current.variables.push({ - funct: funct, - token: state.tokens.curr, - value: v, - undef: true, - unused: false - }); - } - return true; - // When we are in "define" state of the list comp, - } else if (_current && _current.mode === "define") { - // check if the variable has been used previously - if (!declare(v)) { - _current.variables.push({ - funct: funct, - token: state.tokens.curr, - value: v, - undef: false, - unused: true - }); - } - return true; - // When we are in the "generate" state of the list comp, - } else if (_current && _current.mode === "generate") { - isundef(funct, "W117", state.tokens.curr, v); - return true; - // When we are in "filter" state, - } else if (_current && _current.mode === "filter") { - // we check whether current variable has been declared - if (use(v)) { - // if not we warn about it - isundef(funct, "W117", state.tokens.curr, v); - } - return true; - } - return false; - } - }; - }; - - - // Parse JSON - - function jsonValue() { - - function jsonObject() { - var o = {}, t = state.tokens.next; - advance("{"); - if (state.tokens.next.id !== "}") { - for (;;) { - if (state.tokens.next.id === "(end)") { - error("E026", state.tokens.next, t.line); - } else if (state.tokens.next.id === "}") { - warning("W094", state.tokens.curr); - break; - } else if (state.tokens.next.id === ",") { - error("E028", state.tokens.next); - } else if (state.tokens.next.id !== "(string)") { - warning("W095", state.tokens.next, state.tokens.next.value); - } - if (o[state.tokens.next.value] === true) { - warning("W075", state.tokens.next, state.tokens.next.value); - } else if ((state.tokens.next.value === "__proto__" && - !state.option.proto) || (state.tokens.next.value === "__iterator__" && - !state.option.iterator)) { - warning("W096", state.tokens.next, state.tokens.next.value); - } else { - o[state.tokens.next.value] = true; - } - advance(); - advance(":"); - jsonValue(); - if (state.tokens.next.id !== ",") { - break; - } - advance(","); - } - } - advance("}"); - } - - function jsonArray() { - var t = state.tokens.next; - advance("["); - if (state.tokens.next.id !== "]") { - for (;;) { - if (state.tokens.next.id === "(end)") { - error("E027", state.tokens.next, t.line); - } else if (state.tokens.next.id === "]") { - warning("W094", state.tokens.curr); - break; - } else if (state.tokens.next.id === ",") { - error("E028", state.tokens.next); - } - jsonValue(); - if (state.tokens.next.id !== ",") { - break; - } - advance(","); - } - } - advance("]"); - } - - switch (state.tokens.next.id) { - case "{": - jsonObject(); - break; - case "[": - jsonArray(); - break; - case "true": - case "false": - case "null": - case "(number)": - case "(string)": - advance(); - break; - case "-": - advance("-"); - if (state.tokens.curr.character !== state.tokens.next.from) { - warning("W011", state.tokens.curr); - } - adjacent(state.tokens.curr, state.tokens.next); - advance("(number)"); - break; - default: - error("E003", state.tokens.next); - } - } - - var blockScope = function () { - var _current = {}; - var _variables = [_current]; - - function _checkBlockLabels() { - for (var t in _current) { - if (_current[t]["(type)"] === "unused") { - if (state.option.unused) { - var tkn = _current[t]["(token)"]; - var line = tkn.line; - var chr = tkn.character; - warningAt("W098", line, chr, t); - } - } - } - } - - return { - stack: function () { - _current = {}; - _variables.push(_current); - }, - - unstack: function () { - _checkBlockLabels(); - _variables.splice(_variables.length - 1, 1); - _current = _.last(_variables); - }, - - getlabel: function (l) { - for (var i = _variables.length - 1 ; i >= 0; --i) { - if (_.has(_variables[i], l) && !_variables[i][l]["(shadowed)"]) { - return _variables[i]; - } - } - }, - - shadow: function (name) { - for (var i = _variables.length - 1; i >= 0; i--) { - if (_.has(_variables[i], name)) { - _variables[i][name]["(shadowed)"] = true; - } - } - }, - - unshadow: function (name) { - for (var i = _variables.length - 1; i >= 0; i--) { - if (_.has(_variables[i], name)) { - _variables[i][name]["(shadowed)"] = false; - } - } - }, - - current: { - has: function (t) { - return _.has(_current, t); - }, - - add: function (t, type, tok) { - _current[t] = { "(type)" : type, "(token)": tok, "(shadowed)": false }; - } - } - }; - }; - - // The actual JSHINT function itself. - var itself = function (s, o, g) { - var i, k, x; - var optionKeys; - var newOptionObj = {}; - var newIgnoredObj = {}; - - o = _.clone(o); - state.reset(); - - if (o && o.scope) { - JSHINT.scope = o.scope; - } else { - JSHINT.errors = []; - JSHINT.undefs = []; - JSHINT.internals = []; - JSHINT.blacklist = {}; - JSHINT.scope = "(main)"; - } - - predefined = Object.create(null); - combine(predefined, vars.ecmaIdentifiers); - combine(predefined, vars.reservedVars); - - combine(predefined, g || {}); - - declared = Object.create(null); - exported = Object.create(null); - - function each(obj, cb) { - if (!obj) - return; - - if (!Array.isArray(obj) && typeof obj === "object") - obj = Object.keys(obj); - - obj.forEach(cb); - } - - if (o) { - each(o.predef || null, function (item) { - var slice, prop; - - if (item[0] === "-") { - slice = item.slice(1); - JSHINT.blacklist[slice] = slice; - } else { - prop = Object.getOwnPropertyDescriptor(o.predef, item); - predefined[item] = prop ? prop.value : false; - } - }); - - each(o.exported || null, function (item) { - exported[item] = true; - }); - - delete o.predef; - delete o.exported; - - optionKeys = Object.keys(o); - for (x = 0; x < optionKeys.length; x++) { - if (/^-W\d{3}$/g.test(optionKeys[x])) { - newIgnoredObj[optionKeys[x].slice(1)] = true; - } else { - newOptionObj[optionKeys[x]] = o[optionKeys[x]]; - - if (optionKeys[x] === "newcap" && o[optionKeys[x]] === false) - newOptionObj["(explicitNewcap)"] = true; - - if (optionKeys[x] === "indent") - newOptionObj["(explicitIndent)"] = o[optionKeys[x]] === false ? false : true; - } - } - } - - state.option = newOptionObj; - state.ignored = newIgnoredObj; - - state.option.indent = state.option.indent || 4; - state.option.maxerr = state.option.maxerr || 50; - - indent = 1; - global = Object.create(predefined); - scope = global; - - funct = functor("(global)", null, scope, { - "(global)" : true, - "(blockscope)": blockScope(), - "(comparray)" : arrayComprehension(), - "(metrics)" : createMetrics(state.tokens.next) - }); - - functions = [funct]; - urls = []; - stack = null; - member = {}; - membersOnly = null; - implied = {}; - inblock = false; - lookahead = []; - warnings = 0; - unuseds = []; - - if (!isString(s) && !Array.isArray(s)) { - errorAt("E004", 0); - return false; - } - - api = { - get isJSON() { - return state.jsonMode; - }, - - getOption: function (name) { - return state.option[name] || null; - }, - - getCache: function (name) { - return state.cache[name]; - }, - - setCache: function (name, value) { - state.cache[name] = value; - }, - - warn: function (code, data) { - warningAt.apply(null, [ code, data.line, data.char ].concat(data.data)); - }, - - on: function (names, listener) { - names.split(" ").forEach(function (name) { - emitter.on(name, listener); - }.bind(this)); - } - }; - - emitter.removeAllListeners(); - (extraModules || []).forEach(function (func) { - func(api); - }); - - state.tokens.prev = state.tokens.curr = state.tokens.next = state.syntax["(begin)"]; - - lex = new Lexer(s); - - lex.on("warning", function (ev) { - warningAt.apply(null, [ ev.code, ev.line, ev.character].concat(ev.data)); - }); - - lex.on("error", function (ev) { - errorAt.apply(null, [ ev.code, ev.line, ev.character ].concat(ev.data)); - }); - - lex.on("fatal", function (ev) { - quit("E041", ev.line, ev.from); - }); - - lex.on("Identifier", function (ev) { - emitter.emit("Identifier", ev); - }); - - lex.on("String", function (ev) { - emitter.emit("String", ev); - }); - - lex.on("Number", function (ev) { - emitter.emit("Number", ev); - }); - - lex.start(); - - // Check options - for (var name in o) { - if (_.has(o, name)) { - checkOption(name, state.tokens.curr); - } - } - - assume(); - - // combine the passed globals after we've assumed all our options - combine(predefined, g || {}); - - //reset values - comma.first = true; - - try { - advance(); - switch (state.tokens.next.id) { - case "{": - case "[": - destructuringAssignOrJsonValue(); - break; - default: - directives(); - - if (state.directive["use strict"]) { - if (!state.option.globalstrict && !(state.option.node || state.option.phantom)) { - warning("W097", state.tokens.prev); - } - } - - statements(); - } - advance((state.tokens.next && state.tokens.next.value !== ".") ? "(end)" : undefined); - funct["(blockscope)"].unstack(); - - var markDefined = function (name, context) { - do { - if (typeof context[name] === "string") { - // JSHINT marks unused variables as 'unused' and - // unused function declaration as 'unction'. This - // code changes such instances back 'var' and - // 'closure' so that the code in JSHINT.data() - // doesn't think they're unused. - - if (context[name] === "unused") - context[name] = "var"; - else if (context[name] === "unction") - context[name] = "closure"; - - return true; - } - - context = context["(context)"]; - } while (context); - - return false; - }; - - var clearImplied = function (name, line) { - if (!implied[name]) - return; - - var newImplied = []; - for (var i = 0; i < implied[name].length; i += 1) { - if (implied[name][i] !== line) - newImplied.push(implied[name][i]); - } - - if (newImplied.length === 0) - delete implied[name]; - else - implied[name] = newImplied; - }; - - var warnUnused = function (name, tkn, type, unused_opt) { - var line = tkn.line; - var chr = tkn.character; - - if (unused_opt === undefined) { - unused_opt = state.option.unused; - } - - if (unused_opt === true) { - unused_opt = "last-param"; - } - - var warnable_types = { - "vars": ["var"], - "last-param": ["var", "param"], - "strict": ["var", "param", "last-param"] - }; - - if (unused_opt) { - if (warnable_types[unused_opt] && warnable_types[unused_opt].indexOf(type) !== -1) { - warningAt("W098", line, chr, name); - } - } - - unuseds.push({ - name: name, - line: line, - character: chr - }); - }; - - var checkUnused = function (func, key) { - var type = func[key]; - var tkn = func["(tokens)"][key]; - - if (key.charAt(0) === "(") - return; - - if (type !== "unused" && type !== "unction" && type !== "const") - return; - - // Params are checked separately from other variables. - if (func["(params)"] && func["(params)"].indexOf(key) !== -1) - return; - - // Variable is in global scope and defined as exported. - if (func["(global)"] && _.has(exported, key)) - return; - - // Is this constant unused? - if (type === "const" && !getprop(func, key, "unused")) - return; - - warnUnused(key, tkn, "var"); - }; - - // Check queued 'x is not defined' instances to see if they're still undefined. - for (i = 0; i < JSHINT.undefs.length; i += 1) { - k = JSHINT.undefs[i].slice(0); - - if (markDefined(k[2].value, k[0])) { - clearImplied(k[2].value, k[2].line); - } else if (state.option.undef) { - warning.apply(warning, k.slice(1)); - } - } - - functions.forEach(function (func) { - if (func["(unusedOption)"] === false) { - return; - } - - for (var key in func) { - if (_.has(func, key)) { - checkUnused(func, key); - } - } - - if (!func["(params)"]) - return; - - var params = func["(params)"].slice(); - var param = params.pop(); - var type, unused_opt; - - while (param) { - type = func[param]; - unused_opt = func["(unusedOption)"] || state.option.unused; - unused_opt = unused_opt === true ? "last-param" : unused_opt; - - // 'undefined' is a special case for (function (window, undefined) { ... })(); - // patterns. - - if (param === "undefined") - return; - - if (type === "unused" || type === "unction") { - warnUnused(param, func["(tokens)"][param], "param", func["(unusedOption)"]); - } else if (unused_opt === "last-param") { - return; - } - - param = params.pop(); - } - }); - - for (var key in declared) { - if (_.has(declared, key) && !_.has(global, key) && !_.has(exported, key)) { - warnUnused(key, declared[key], "var"); - } - } - - } catch (err) { - if (err && err.name === "JSHintError") { - var nt = state.tokens.next || {}; - JSHINT.errors.push({ - scope : "(main)", - raw : err.raw, - code : err.code, - reason : err.message, - line : err.line || nt.line, - character : err.character || nt.from - }, null); - } else { - throw err; - } - } - - // Loop over the listed "internals", and check them as well. - - if (JSHINT.scope === "(main)") { - o = o || {}; - - for (i = 0; i < JSHINT.internals.length; i += 1) { - k = JSHINT.internals[i]; - o.scope = k.elem; - itself(k.value, o, g); - } - } - - return JSHINT.errors.length === 0; - }; - - // Modules. - itself.addModule = function (func) { - extraModules.push(func); - }; - - itself.addModule(style.register); - - // Data summary. - itself.data = function () { - var data = { - functions: [], - options: state.option - }; - - var implieds = []; - var members = []; - var fu, f, i, j, n, globals; - - if (itself.errors.length) { - data.errors = itself.errors; - } - - if (state.jsonMode) { - data.json = true; - } - - for (n in implied) { - if (_.has(implied, n)) { - implieds.push({ - name: n, - line: implied[n] - }); - } - } - - if (implieds.length > 0) { - data.implieds = implieds; - } - - if (urls.length > 0) { - data.urls = urls; - } - - globals = Object.keys(scope); - if (globals.length > 0) { - data.globals = globals; - } - - for (i = 1; i < functions.length; i += 1) { - f = functions[i]; - fu = {}; - - for (j = 0; j < functionicity.length; j += 1) { - fu[functionicity[j]] = []; - } - - for (j = 0; j < functionicity.length; j += 1) { - if (fu[functionicity[j]].length === 0) { - delete fu[functionicity[j]]; - } - } - - fu.name = f["(name)"]; - fu.param = f["(params)"]; - fu.line = f["(line)"]; - fu.character = f["(character)"]; - fu.last = f["(last)"]; - fu.lastcharacter = f["(lastcharacter)"]; - - fu.metrics = { - complexity: f["(metrics)"].ComplexityCount, - parameters: (f["(params)"] || []).length, - statements: f["(metrics)"].statementCount - }; - - data.functions.push(fu); - } - - if (unuseds.length > 0) { - data.unused = unuseds; - } - - members = []; - for (n in member) { - if (typeof member[n] === "number") { - data.member = member; - break; - } - } - - return data; - }; - - itself.jshint = itself; - - return itself; -}()); - -// Make JSHINT a Node module, if possible. -if (typeof exports === "object" && exports) { - exports.JSHINT = JSHINT; -} - -},{"./lex.js":14,"./messages.js":15,"./reg.js":16,"./state.js":17,"./style.js":18,"./vars.js":19,"console-browserify":10,"events":5,"underscore":11}],14:[function(require,module,exports){ -/* - * Lexical analysis and token construction. - */ - -"use strict"; - -var _ = require("underscore"); -var events = require("events"); -var reg = require("./reg.js"); -var state = require("./state.js").state; - -var unicodeData = require("../data/ascii-identifier-data.js"); -var asciiIdentifierStartTable = unicodeData.asciiIdentifierStartTable; -var asciiIdentifierPartTable = unicodeData.asciiIdentifierPartTable; -var nonAsciiIdentifierStartTable = require("../data/non-ascii-identifier-start.js"); -var nonAsciiIdentifierPartTable = require("../data/non-ascii-identifier-part-only.js"); - -// Some of these token types are from JavaScript Parser API -// while others are specific to JSHint parser. -// JS Parser API: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API - -var Token = { - Identifier: 1, - Punctuator: 2, - NumericLiteral: 3, - StringLiteral: 4, - Comment: 5, - Keyword: 6, - NullLiteral: 7, - BooleanLiteral: 8, - RegExp: 9 -}; - -// Object that handles postponed lexing verifications that checks the parsed -// environment state. - -function asyncTrigger() { - var _checks = []; - - return { - push: function (fn) { - _checks.push(fn); - }, - - check: function () { - for (var check = 0; check < _checks.length; ++check) { - _checks[check](); - } - - _checks.splice(0, _checks.length); - } - }; -} - -/* - * Lexer for JSHint. - * - * This object does a char-by-char scan of the provided source code - * and produces a sequence of tokens. - * - * var lex = new Lexer("var i = 0;"); - * lex.start(); - * lex.token(); // returns the next token - * - * You have to use the token() method to move the lexer forward - * but you don't have to use its return value to get tokens. In addition - * to token() method returning the next token, the Lexer object also - * emits events. - * - * lex.on("Identifier", function (data) { - * if (data.name.indexOf("_") >= 0) { - * // Produce a warning. - * } - * }); - * - * Note that the token() method returns tokens in a JSLint-compatible - * format while the event emitter uses a slightly modified version of - * Mozilla's JavaScript Parser API. Eventually, we will move away from - * JSLint format. - */ -function Lexer(source) { - var lines = source; - - if (typeof lines === "string") { - lines = lines - .replace(/\r\n/g, "\n") - .replace(/\r/g, "\n") - .split("\n"); - } - - // If the first line is a shebang (#!), make it a blank and move on. - // Shebangs are used by Node scripts. - - if (lines[0] && lines[0].substr(0, 2) === "#!") { - if (lines[0].indexOf("node") !== -1) { - state.option.node = true; - } - lines[0] = ""; - } - - this.emitter = new events.EventEmitter(); - this.source = source; - this.setLines(lines); - this.prereg = true; - - this.line = 0; - this.char = 1; - this.from = 1; - this.input = ""; - this.inComment = false; - - for (var i = 0; i < state.option.indent; i += 1) { - state.tab += " "; - } -} - -Lexer.prototype = { - _lines: [], - - getLines: function () { - this._lines = state.lines; - return this._lines; - }, - - setLines: function (val) { - this._lines = val; - state.lines = this._lines; - }, - - /* - * Return the next i character without actually moving the - * char pointer. - */ - peek: function (i) { - return this.input.charAt(i || 0); - }, - - /* - * Move the char pointer forward i times. - */ - skip: function (i) { - i = i || 1; - this.char += i; - this.input = this.input.slice(i); - }, - - /* - * Subscribe to a token event. The API for this method is similar - * Underscore.js i.e. you can subscribe to multiple events with - * one call: - * - * lex.on("Identifier Number", function (data) { - * // ... - * }); - */ - on: function (names, listener) { - names.split(" ").forEach(function (name) { - this.emitter.on(name, listener); - }.bind(this)); - }, - - /* - * Trigger a token event. All arguments will be passed to each - * listener. - */ - trigger: function () { - this.emitter.emit.apply(this.emitter, Array.prototype.slice.call(arguments)); - }, - - /* - * Postpone a token event. the checking condition is set as - * last parameter, and the trigger function is called in a - * stored callback. To be later called using the check() function - * by the parser. This avoids parser's peek() to give the lexer - * a false context. - */ - triggerAsync: function (type, args, checks, fn) { - checks.push(function () { - if (fn()) { - this.trigger(type, args); - } - }.bind(this)); - }, - - /* - * Extract a punctuator out of the next sequence of characters - * or return 'null' if its not possible. - * - * This method's implementation was heavily influenced by the - * scanPunctuator function in the Esprima parser's source code. - */ - scanPunctuator: function () { - var ch1 = this.peek(); - var ch2, ch3, ch4; - - switch (ch1) { - // Most common single-character punctuators - case ".": - if ((/^[0-9]$/).test(this.peek(1))) { - return null; - } - if (this.peek(1) === "." && this.peek(2) === ".") { - return { - type: Token.Punctuator, - value: "..." - }; - } - /* falls through */ - case "(": - case ")": - case ";": - case ",": - case "{": - case "}": - case "[": - case "]": - case ":": - case "~": - case "?": - return { - type: Token.Punctuator, - value: ch1 - }; - - // A pound sign (for Node shebangs) - case "#": - return { - type: Token.Punctuator, - value: ch1 - }; - - // We're at the end of input - case "": - return null; - } - - // Peek more characters - - ch2 = this.peek(1); - ch3 = this.peek(2); - ch4 = this.peek(3); - - // 4-character punctuator: >>>= - - if (ch1 === ">" && ch2 === ">" && ch3 === ">" && ch4 === "=") { - return { - type: Token.Punctuator, - value: ">>>=" - }; - } - - // 3-character punctuators: === !== >>> <<= >>= - - if (ch1 === "=" && ch2 === "=" && ch3 === "=") { - return { - type: Token.Punctuator, - value: "===" - }; - } - - if (ch1 === "!" && ch2 === "=" && ch3 === "=") { - return { - type: Token.Punctuator, - value: "!==" - }; - } - - if (ch1 === ">" && ch2 === ">" && ch3 === ">") { - return { - type: Token.Punctuator, - value: ">>>" - }; - } - - if (ch1 === "<" && ch2 === "<" && ch3 === "=") { - return { - type: Token.Punctuator, - value: "<<=" - }; - } - - if (ch1 === ">" && ch2 === ">" && ch3 === "=") { - return { - type: Token.Punctuator, - value: ">>=" - }; - } - - // Fat arrow punctuator - if (ch1 === "=" && ch2 === ">") { - return { - type: Token.Punctuator, - value: ch1 + ch2 - }; - } - - // 2-character punctuators: <= >= == != ++ -- << >> && || - // += -= *= %= &= |= ^= (but not /=, see below) - if (ch1 === ch2 && ("+-<>&|".indexOf(ch1) >= 0)) { - return { - type: Token.Punctuator, - value: ch1 + ch2 - }; - } - - if ("<>=!+-*%&|^".indexOf(ch1) >= 0) { - if (ch2 === "=") { - return { - type: Token.Punctuator, - value: ch1 + ch2 - }; - } - - return { - type: Token.Punctuator, - value: ch1 - }; - } - - // Special case: /=. We need to make sure that this is an - // operator and not a regular expression. - - if (ch1 === "/") { - if (ch2 === "=" && /\/=(?!(\S*\/[gim]?))/.test(this.input)) { - // /= is not a part of a regular expression, return it as a - // punctuator. - return { - type: Token.Punctuator, - value: "/=" - }; - } - - return { - type: Token.Punctuator, - value: "/" - }; - } - - return null; - }, - - /* - * Extract a comment out of the next sequence of characters and/or - * lines or return 'null' if its not possible. Since comments can - * span across multiple lines this method has to move the char - * pointer. - * - * In addition to normal JavaScript comments (// and /*) this method - * also recognizes JSHint- and JSLint-specific comments such as - * /*jshint, /*jslint, /*globals and so on. - */ - scanComments: function () { - var ch1 = this.peek(); - var ch2 = this.peek(1); - var rest = this.input.substr(2); - var startLine = this.line; - var startChar = this.char; - - // Create a comment token object and make sure it - // has all the data JSHint needs to work with special - // comments. - - function commentToken(label, body, opt) { - var special = ["jshint", "jslint", "members", "member", "globals", "global", "exported"]; - var isSpecial = false; - var value = label + body; - var commentType = "plain"; - opt = opt || {}; - - if (opt.isMultiline) { - value += "*/"; - } - - special.forEach(function (str) { - if (isSpecial) { - return; - } - - // Don't recognize any special comments other than jshint for single-line - // comments. This introduced many problems with legit comments. - if (label === "//" && str !== "jshint") { - return; - } - - if (body.substr(0, str.length) === str) { - isSpecial = true; - label = label + str; - body = body.substr(str.length); - } - - if (!isSpecial && body.charAt(0) === " " && body.substr(1, str.length) === str) { - isSpecial = true; - label = label + " " + str; - body = body.substr(str.length + 1); - } - - if (!isSpecial) { - return; - } - - switch (str) { - case "member": - commentType = "members"; - break; - case "global": - commentType = "globals"; - break; - default: - commentType = str; - } - }); - - return { - type: Token.Comment, - commentType: commentType, - value: value, - body: body, - isSpecial: isSpecial, - isMultiline: opt.isMultiline || false, - isMalformed: opt.isMalformed || false - }; - } - - // End of unbegun comment. Raise an error and skip that input. - if (ch1 === "*" && ch2 === "/") { - this.trigger("error", { - code: "E018", - line: startLine, - character: startChar - }); - - this.skip(2); - return null; - } - - // Comments must start either with // or /* - if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) { - return null; - } - - // One-line comment - if (ch2 === "/") { - this.skip(this.input.length); // Skip to the EOL. - return commentToken("//", rest); - } - - var body = ""; - - /* Multi-line comment */ - if (ch2 === "*") { - this.inComment = true; - this.skip(2); - - while (this.peek() !== "*" || this.peek(1) !== "/") { - if (this.peek() === "") { // End of Line - body += "\n"; - - // If we hit EOF and our comment is still unclosed, - // trigger an error and end the comment implicitly. - if (!this.nextLine()) { - this.trigger("error", { - code: "E017", - line: startLine, - character: startChar - }); - - this.inComment = false; - return commentToken("/*", body, { - isMultiline: true, - isMalformed: true - }); - } - } else { - body += this.peek(); - this.skip(); - } - } - - this.skip(2); - this.inComment = false; - return commentToken("/*", body, { isMultiline: true }); - } - }, - - /* - * Extract a keyword out of the next sequence of characters or - * return 'null' if its not possible. - */ - scanKeyword: function () { - var result = /^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input); - var keywords = [ - "if", "in", "do", "var", "for", "new", - "try", "let", "this", "else", "case", - "void", "with", "enum", "while", "break", - "catch", "throw", "const", "yield", "class", - "super", "return", "typeof", "delete", - "switch", "export", "import", "default", - "finally", "extends", "function", "continue", - "debugger", "instanceof" - ]; - - if (result && keywords.indexOf(result[0]) >= 0) { - return { - type: Token.Keyword, - value: result[0] - }; - } - - return null; - }, - - /* - * Extract a JavaScript identifier out of the next sequence of - * characters or return 'null' if its not possible. In addition, - * to Identifier this method can also produce BooleanLiteral - * (true/false) and NullLiteral (null). - */ - scanIdentifier: function () { - var id = ""; - var index = 0; - var type, char; - - function isNonAsciiIdentifierStart(code) { - return nonAsciiIdentifierStartTable.indexOf(code) > -1; - } - - function isNonAsciiIdentifierPart(code) { - return isNonAsciiIdentifierStart(code) || nonAsciiIdentifierPartTable.indexOf(code) > -1; - } - - function isHexDigit(str) { - return (/^[0-9a-fA-F]$/).test(str); - } - - var readUnicodeEscapeSequence = function () { - /*jshint validthis:true */ - index += 1; - - if (this.peek(index) !== "u") { - return null; - } - - var ch1 = this.peek(index + 1); - var ch2 = this.peek(index + 2); - var ch3 = this.peek(index + 3); - var ch4 = this.peek(index + 4); - var code; - - if (isHexDigit(ch1) && isHexDigit(ch2) && isHexDigit(ch3) && isHexDigit(ch4)) { - code = parseInt(ch1 + ch2 + ch3 + ch4, 16); - - if (asciiIdentifierPartTable[code] || isNonAsciiIdentifierPart(code)) { - index += 5; - return "\\u" + ch1 + ch2 + ch3 + ch4; - } - - return null; - } - - return null; - }.bind(this); - - var getIdentifierStart = function () { - /*jshint validthis:true */ - var chr = this.peek(index); - var code = chr.charCodeAt(0); - - if (code === 92) { - return readUnicodeEscapeSequence(); - } - - if (code < 128) { - if (asciiIdentifierStartTable[code]) { - index += 1; - return chr; - } - - return null; - } - - if (isNonAsciiIdentifierStart(code)) { - index += 1; - return chr; - } - - return null; - }.bind(this); - - var getIdentifierPart = function () { - /*jshint validthis:true */ - var chr = this.peek(index); - var code = chr.charCodeAt(0); - - if (code === 92) { - return readUnicodeEscapeSequence(); - } - - if (code < 128) { - if (asciiIdentifierPartTable[code]) { - index += 1; - return chr; - } - - return null; - } - - if (isNonAsciiIdentifierPart(code)) { - index += 1; - return chr; - } - - return null; - }.bind(this); - - char = getIdentifierStart(); - if (char === null) { - return null; - } - - id = char; - for (;;) { - char = getIdentifierPart(); - - if (char === null) { - break; - } - - id += char; - } - - switch (id) { - case "true": - case "false": - type = Token.BooleanLiteral; - break; - case "null": - type = Token.NullLiteral; - break; - default: - type = Token.Identifier; - } - - return { - type: type, - value: id - }; - }, - - /* - * Extract a numeric literal out of the next sequence of - * characters or return 'null' if its not possible. This method - * supports all numeric literals described in section 7.8.3 - * of the EcmaScript 5 specification. - * - * This method's implementation was heavily influenced by the - * scanNumericLiteral function in the Esprima parser's source code. - */ - scanNumericLiteral: function () { - var index = 0; - var value = ""; - var length = this.input.length; - var char = this.peek(index); - var bad; - - function isDecimalDigit(str) { - return (/^[0-9]$/).test(str); - } - - function isOctalDigit(str) { - return (/^[0-7]$/).test(str); - } - - function isHexDigit(str) { - return (/^[0-9a-fA-F]$/).test(str); - } - - function isIdentifierStart(ch) { - return (ch === "$") || (ch === "_") || (ch === "\\") || - (ch >= "a" && ch <= "z") || (ch >= "A" && ch <= "Z"); - } - - // Numbers must start either with a decimal digit or a point. - - if (char !== "." && !isDecimalDigit(char)) { - return null; - } - - if (char !== ".") { - value = this.peek(index); - index += 1; - char = this.peek(index); - - if (value === "0") { - // Base-16 numbers. - if (char === "x" || char === "X") { - index += 1; - value += char; - - while (index < length) { - char = this.peek(index); - if (!isHexDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (value.length <= 2) { // 0x - return { - type: Token.NumericLiteral, - value: value, - isMalformed: true - }; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: Token.NumericLiteral, - value: value, - base: 16, - isMalformed: false - }; - } - - // Base-8 numbers. - if (isOctalDigit(char)) { - index += 1; - value += char; - bad = false; - - while (index < length) { - char = this.peek(index); - - // Numbers like '019' (note the 9) are not valid octals - // but we still parse them and mark as malformed. - - if (isDecimalDigit(char)) { - bad = true; - } else if (!isOctalDigit(char)) { - break; - } - value += char; - index += 1; - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: Token.NumericLiteral, - value: value, - base: 8, - isMalformed: false - }; - } - - // Decimal numbers that start with '0' such as '09' are illegal - // but we still parse them and return as malformed. - - if (isDecimalDigit(char)) { - index += 1; - value += char; - } - } - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } - - // Decimal digits. - - if (char === ".") { - value += char; - index += 1; - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } - - // Exponent part. - - if (char === "e" || char === "E") { - value += char; - index += 1; - char = this.peek(index); - - if (char === "+" || char === "-") { - value += this.peek(index); - index += 1; - } - - char = this.peek(index); - if (isDecimalDigit(char)) { - value += char; - index += 1; - - while (index < length) { - char = this.peek(index); - if (!isDecimalDigit(char)) { - break; - } - value += char; - index += 1; - } - } else { - return null; - } - } - - if (index < length) { - char = this.peek(index); - if (isIdentifierStart(char)) { - return null; - } - } - - return { - type: Token.NumericLiteral, - value: value, - base: 10, - isMalformed: !isFinite(value) - }; - }, - - /* - * Extract a string out of the next sequence of characters and/or - * lines or return 'null' if its not possible. Since strings can - * span across multiple lines this method has to move the char - * pointer. - * - * This method recognizes pseudo-multiline JavaScript strings: - * - * var str = "hello\ - * world"; - */ - scanStringLiteral: function (checks) { - /*jshint loopfunc:true */ - var quote = this.peek(); - - // String must start with a quote. - if (quote !== "\"" && quote !== "'") { - return null; - } - - // In JSON strings must always use double quotes. - this.triggerAsync("warning", { - code: "W108", - line: this.line, - character: this.char // +1? - }, checks, function () { return state.jsonMode && quote !== "\""; }); - - var value = ""; - var startLine = this.line; - var startChar = this.char; - var allowNewLine = false; - - this.skip(); - - while (this.peek() !== quote) { - while (this.peek() === "") { // End Of Line - - // If an EOL is not preceded by a backslash, show a warning - // and proceed like it was a legit multi-line string where - // author simply forgot to escape the newline symbol. - // - // Another approach is to implicitly close a string on EOL - // but it generates too many false positives. - - if (!allowNewLine) { - this.trigger("warning", { - code: "W112", - line: this.line, - character: this.char - }); - } else { - allowNewLine = false; - - // Otherwise show a warning if multistr option was not set. - // For JSON, show warning no matter what. - - this.triggerAsync("warning", { - code: "W043", - line: this.line, - character: this.char - }, checks, function () { return !state.option.multistr; }); - - this.triggerAsync("warning", { - code: "W042", - line: this.line, - character: this.char - }, checks, function () { return state.jsonMode && state.option.multistr; }); - } - - // If we get an EOF inside of an unclosed string, show an - // error and implicitly close it at the EOF point. - - if (!this.nextLine()) { - this.trigger("error", { - code: "E029", - line: startLine, - character: startChar - }); - - return { - type: Token.StringLiteral, - value: value, - isUnclosed: true, - quote: quote - }; - } - } - - allowNewLine = false; - var char = this.peek(); - var jump = 1; // A length of a jump, after we're done - // parsing this character. - - if (char < " ") { - // Warn about a control character in a string. - this.trigger("warning", { - code: "W113", - line: this.line, - character: this.char, - data: [ "" ] - }); - } - - // Special treatment for some escaped characters. - - if (char === "\\") { - this.skip(); - char = this.peek(); - - switch (char) { - case "'": - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\'" ] - }, checks, function () {return state.jsonMode; }); - break; - case "b": - char = "\\b"; - break; - case "f": - char = "\\f"; - break; - case "n": - char = "\\n"; - break; - case "r": - char = "\\r"; - break; - case "t": - char = "\\t"; - break; - case "0": - char = "\\0"; - - // Octal literals fail in strict mode. - // Check if the number is between 00 and 07. - var n = parseInt(this.peek(1), 10); - this.triggerAsync("warning", { - code: "W115", - line: this.line, - character: this.char - }, checks, - function () { return n >= 0 && n <= 7 && state.directive["use strict"]; }); - break; - case "u": - char = String.fromCharCode(parseInt(this.input.substr(1, 4), 16)); - jump = 5; - break; - case "v": - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\v" ] - }, checks, function () { return state.jsonMode; }); - - char = "\v"; - break; - case "x": - var x = parseInt(this.input.substr(1, 2), 16); - - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "\\x-" ] - }, checks, function () { return state.jsonMode; }); - - char = String.fromCharCode(x); - jump = 3; - break; - case "\\": - char = "\\\\"; - break; - case "\"": - char = "\\\""; - break; - case "/": - break; - case "": - allowNewLine = true; - char = ""; - break; - case "!": - if (value.slice(value.length - 2) === "<") { - break; - } - - /*falls through */ - default: - // Weird escaping. - this.trigger("warning", { - code: "W044", - line: this.line, - character: this.char - }); - } - } - - value += char; - this.skip(jump); - } - - this.skip(); - return { - type: Token.StringLiteral, - value: value, - isUnclosed: false, - quote: quote - }; - }, - - /* - * Extract a regular expression out of the next sequence of - * characters and/or lines or return 'null' if its not possible. - * - * This method is platform dependent: it accepts almost any - * regular expression values but then tries to compile and run - * them using system's RegExp object. This means that there are - * rare edge cases where one JavaScript engine complains about - * your regular expression while others don't. - */ - scanRegExp: function () { - var index = 0; - var length = this.input.length; - var char = this.peek(); - var value = char; - var body = ""; - var flags = []; - var malformed = false; - var isCharSet = false; - var terminated; - - var scanUnexpectedChars = function () { - // Unexpected control character - if (char < " ") { - malformed = true; - this.trigger("warning", { - code: "W048", - line: this.line, - character: this.char - }); - } - - // Unexpected escaped character - if (char === "<") { - malformed = true; - this.trigger("warning", { - code: "W049", - line: this.line, - character: this.char, - data: [ char ] - }); - } - }.bind(this); - - // Regular expressions must start with '/' - if (!this.prereg || char !== "/") { - return null; - } - - index += 1; - terminated = false; - - // Try to get everything in between slashes. A couple of - // cases aside (see scanUnexpectedChars) we don't really - // care whether the resulting expression is valid or not. - // We will check that later using the RegExp object. - - while (index < length) { - char = this.peek(index); - value += char; - body += char; - - if (isCharSet) { - if (char === "]") { - if (this.peek(index - 1) !== "\\" || this.peek(index - 2) === "\\") { - isCharSet = false; - } - } - - if (char === "\\") { - index += 1; - char = this.peek(index); - body += char; - value += char; - - scanUnexpectedChars(); - } - - index += 1; - continue; - } - - if (char === "\\") { - index += 1; - char = this.peek(index); - body += char; - value += char; - - scanUnexpectedChars(); - - if (char === "/") { - index += 1; - continue; - } - - if (char === "[") { - index += 1; - continue; - } - } - - if (char === "[") { - isCharSet = true; - index += 1; - continue; - } - - if (char === "/") { - body = body.substr(0, body.length - 1); - terminated = true; - index += 1; - break; - } - - index += 1; - } - - // A regular expression that was never closed is an - // error from which we cannot recover. - - if (!terminated) { - this.trigger("error", { - code: "E015", - line: this.line, - character: this.from - }); - - return void this.trigger("fatal", { - line: this.line, - from: this.from - }); - } - - // Parse flags (if any). - - while (index < length) { - char = this.peek(index); - if (!/[gim]/.test(char)) { - break; - } - flags.push(char); - value += char; - index += 1; - } - - // Check regular expression for correctness. - - try { - new RegExp(body, flags.join("")); - } catch (err) { - malformed = true; - this.trigger("error", { - code: "E016", - line: this.line, - character: this.char, - data: [ err.message ] // Platform dependent! - }); - } - - return { - type: Token.RegExp, - value: value, - flags: flags, - isMalformed: malformed - }; - }, - - /* - * Scan for any occurrence of mixed tabs and spaces. If smarttabs option - * is on, ignore tabs followed by spaces. - * - * Tabs followed by one space followed by a block comment are allowed. - */ - scanMixedSpacesAndTabs: function () { - var at, match; - - if (state.option.smarttabs) { - // Negative look-behind for "//" - match = this.input.match(/(\/\/|^\s?\*)? \t/); - at = match && !match[1] ? 0 : -1; - } else { - at = this.input.search(/ \t|\t [^\*]/); - } - - return at; - }, - - /* - * Scan for any occurrence of non-breaking spaces. Non-breaking spaces - * can be mistakenly typed on OS X with option-space. Non UTF-8 web - * pages with non-breaking pages produce syntax errors. - */ - scanNonBreakingSpaces: function () { - return state.option.nonbsp ? - this.input.search(/(\u00A0)/) : -1; - }, - - /* - * Scan for characters that get silently deleted by one or more browsers. - */ - scanUnsafeChars: function () { - return this.input.search(reg.unsafeChars); - }, - - /* - * Produce the next raw token or return 'null' if no tokens can be matched. - * This method skips over all space characters. - */ - next: function (checks) { - this.from = this.char; - - // Move to the next non-space character. - var start; - if (/\s/.test(this.peek())) { - start = this.char; - - while (/\s/.test(this.peek())) { - this.from += 1; - this.skip(); - } - - if (this.peek() === "") { // EOL - if (!/^\s*$/.test(this.getLines()[this.line - 1]) && state.option.trailing) { - this.trigger("warning", { code: "W102", line: this.line, character: start }); - } - } - } - - // Methods that work with multi-line structures and move the - // character pointer. - - var match = this.scanComments() || - this.scanStringLiteral(checks); - - if (match) { - return match; - } - - // Methods that don't move the character pointer. - - match = - this.scanRegExp() || - this.scanPunctuator() || - this.scanKeyword() || - this.scanIdentifier() || - this.scanNumericLiteral(); - - if (match) { - this.skip(match.value.length); - return match; - } - - // No token could be matched, give up. - - return null; - }, - - /* - * Switch to the next line and reset all char pointers. Once - * switched, this method also checks for mixed spaces and tabs - * and other minor warnings. - */ - nextLine: function () { - var char; - - if (this.line >= this.getLines().length) { - return false; - } - - this.input = this.getLines()[this.line]; - this.line += 1; - this.char = 1; - this.from = 1; - - var inputTrimmed = this.input.trim(); - - var startsWith = function () { - return _.some(arguments, function (prefix) { - return inputTrimmed.indexOf(prefix) === 0; - }); - }; - - var endsWith = function () { - return _.some(arguments, function (suffix) { - return inputTrimmed.indexOf(suffix, inputTrimmed.length - suffix.length) !== -1; - }); - }; - - // If we are ignoring linter errors, replace the input with empty string - // if it doesn't already at least start or end a multi-line comment - if (state.ignoreLinterErrors === true) { - if (!startsWith("/*", "//") && !endsWith("*/")) { - this.input = ""; - } - } - - char = this.scanNonBreakingSpaces(); - if (char >= 0) { - this.trigger("warning", { code: "W125", line: this.line, character: char + 1 }); - } - - char = this.scanMixedSpacesAndTabs(); - if (char >= 0) { - this.trigger("warning", { code: "W099", line: this.line, character: char + 1 }); - } - - this.input = this.input.replace(/\t/g, state.tab); - char = this.scanUnsafeChars(); - - if (char >= 0) { - this.trigger("warning", { code: "W100", line: this.line, character: char }); - } - - // If there is a limit on line length, warn when lines get too - // long. - - if (state.option.maxlen && state.option.maxlen < this.input.length) { - var inComment = this.inComment || - startsWith.call(inputTrimmed, "//") || - startsWith.call(inputTrimmed, "/*"); - - var shouldTriggerError = !inComment || !reg.maxlenException.test(inputTrimmed); - - if (shouldTriggerError) { - this.trigger("warning", { code: "W101", line: this.line, character: this.input.length }); - } - } - - return true; - }, - - /* - * This is simply a synonym for nextLine() method with a friendlier - * public name. - */ - start: function () { - this.nextLine(); - }, - - /* - * Produce the next token. This function is called by advance() to get - * the next token. It returns a token in a JSLint-compatible format. - */ - token: function () { - /*jshint loopfunc:true */ - var checks = asyncTrigger(); - var token; - - - function isReserved(token, isProperty) { - if (!token.reserved) { - return false; - } - var meta = token.meta; - - if (meta && meta.isFutureReservedWord && state.option.inES5()) { - // ES3 FutureReservedWord in an ES5 environment. - if (!meta.es5) { - return false; - } - - // Some ES5 FutureReservedWord identifiers are active only - // within a strict mode environment. - if (meta.strictOnly) { - if (!state.option.strict && !state.directive["use strict"]) { - return false; - } - } - - if (isProperty) { - return false; - } - } - - return true; - } - - // Produce a token object. - var create = function (type, value, isProperty) { - /*jshint validthis:true */ - var obj; - - if (type !== "(endline)" && type !== "(end)") { - this.prereg = false; - } - - if (type === "(punctuator)") { - switch (value) { - case ".": - case ")": - case "~": - case "#": - case "]": - this.prereg = false; - break; - default: - this.prereg = true; - } - - obj = Object.create(state.syntax[value] || state.syntax["(error)"]); - } - - if (type === "(identifier)") { - if (value === "return" || value === "case" || value === "typeof") { - this.prereg = true; - } - - if (_.has(state.syntax, value)) { - obj = Object.create(state.syntax[value] || state.syntax["(error)"]); - - // If this can't be a reserved keyword, reset the object. - if (!isReserved(obj, isProperty && type === "(identifier)")) { - obj = null; - } - } - } - - if (!obj) { - obj = Object.create(state.syntax[type]); - } - - obj.identifier = (type === "(identifier)"); - obj.type = obj.type || type; - obj.value = value; - obj.line = this.line; - obj.character = this.char; - obj.from = this.from; - - if (isProperty && obj.identifier) { - obj.isProperty = isProperty; - } - - obj.check = checks.check; - - return obj; - }.bind(this); - - for (;;) { - if (!this.input.length) { - return create(this.nextLine() ? "(endline)" : "(end)", ""); - } - - token = this.next(checks); - - if (!token) { - if (this.input.length) { - // Unexpected character. - this.trigger("error", { - code: "E024", - line: this.line, - character: this.char, - data: [ this.peek() ] - }); - - this.input = ""; - } - - continue; - } - - switch (token.type) { - case Token.StringLiteral: - this.triggerAsync("String", { - line: this.line, - char: this.char, - from: this.from, - value: token.value, - quote: token.quote - }, checks, function () { return true; }); - - return create("(string)", token.value); - case Token.Identifier: - this.trigger("Identifier", { - line: this.line, - char: this.char, - from: this.form, - name: token.value, - isProperty: state.tokens.curr.id === "." - }); - - /* falls through */ - case Token.Keyword: - case Token.NullLiteral: - case Token.BooleanLiteral: - return create("(identifier)", token.value, state.tokens.curr.id === "."); - - case Token.NumericLiteral: - if (token.isMalformed) { - this.trigger("warning", { - code: "W045", - line: this.line, - character: this.char, - data: [ token.value ] - }); - } - - this.triggerAsync("warning", { - code: "W114", - line: this.line, - character: this.char, - data: [ "0x-" ] - }, checks, function () { return token.base === 16 && state.jsonMode; }); - - this.triggerAsync("warning", { - code: "W115", - line: this.line, - character: this.char - }, checks, function () { - return state.directive["use strict"] && token.base === 8; - }); - - this.trigger("Number", { - line: this.line, - char: this.char, - from: this.from, - value: token.value, - base: token.base, - isMalformed: token.malformed - }); - - return create("(number)", token.value); - - case Token.RegExp: - return create("(regexp)", token.value); - - case Token.Comment: - state.tokens.curr.comment = true; - - if (token.isSpecial) { - return { - id: '(comment)', - value: token.value, - body: token.body, - type: token.commentType, - isSpecial: token.isSpecial, - line: this.line, - character: this.char, - from: this.from - }; - } - - break; - - case "": - break; - - default: - return create("(punctuator)", token.value); - } - } - } -}; - -exports.Lexer = Lexer; - -},{"../data/ascii-identifier-data.js":1,"../data/non-ascii-identifier-part-only.js":2,"../data/non-ascii-identifier-start.js":3,"./reg.js":16,"./state.js":17,"events":5,"underscore":11}],15:[function(require,module,exports){ -"use strict"; - -var _ = require("underscore"); - -var errors = { - // JSHint options - E001: "Bad option: '{a}'.", - E002: "Bad option value.", - - // JSHint input - E003: "Expected a JSON value.", - E004: "Input is neither a string nor an array of strings.", - E005: "Input is empty.", - E006: "Unexpected early end of program.", - - // Strict mode - E007: "Missing \"use strict\" statement.", - E008: "Strict violation.", - E009: "Option 'validthis' can't be used in a global scope.", - E010: "'with' is not allowed in strict mode.", - - // Constants - E011: "const '{a}' has already been declared.", - E012: "const '{a}' is initialized to 'undefined'.", - E013: "Attempting to override '{a}' which is a constant.", - - // Regular expressions - E014: "A regular expression literal can be confused with '/='.", - E015: "Unclosed regular expression.", - E016: "Invalid regular expression.", - - // Tokens - E017: "Unclosed comment.", - E018: "Unbegun comment.", - E019: "Unmatched '{a}'.", - E020: "Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", - E021: "Expected '{a}' and instead saw '{b}'.", - E022: "Line breaking error '{a}'.", - E023: "Missing '{a}'.", - E024: "Unexpected '{a}'.", - E025: "Missing ':' on a case clause.", - E026: "Missing '}' to match '{' from line {a}.", - E027: "Missing ']' to match '[' from line {a}.", - E028: "Illegal comma.", - E029: "Unclosed string.", - - // Everything else - E030: "Expected an identifier and instead saw '{a}'.", - E031: "Bad assignment.", // FIXME: Rephrase - E032: "Expected a small integer or 'false' and instead saw '{a}'.", - E033: "Expected an operator and instead saw '{a}'.", - E034: "get/set are ES5 features.", - E035: "Missing property name.", - E036: "Expected to see a statement and instead saw a block.", - E037: null, - E038: null, - E039: "Function declarations are not invocable. Wrap the whole function invocation in parens.", - E040: "Each value should have its own case label.", - E041: "Unrecoverable syntax error.", - E042: "Stopping.", - E043: "Too many errors.", - E044: null, - E045: "Invalid for each loop.", - E046: "A yield statement shall be within a generator function (with syntax: `function*`)", - E047: null, // Vacant - E048: "Let declaration not directly within block.", - E049: "A {a} cannot be named '{b}'.", - E050: "Mozilla requires the yield expression to be parenthesized here.", - E051: "Regular parameters cannot come after default parameters." -}; - -var warnings = { - W001: "'hasOwnProperty' is a really bad name.", - W002: "Value of '{a}' may be overwritten in IE 8 and earlier.", - W003: "'{a}' was used before it was defined.", - W004: "'{a}' is already defined.", - W005: "A dot following a number can be confused with a decimal point.", - W006: "Confusing minuses.", - W007: "Confusing pluses.", - W008: "A leading decimal point can be confused with a dot: '{a}'.", - W009: "The array literal notation [] is preferable.", - W010: "The object literal notation {} is preferable.", - W011: "Unexpected space after '{a}'.", - W012: "Unexpected space before '{a}'.", - W013: "Missing space after '{a}'.", - W014: "Bad line breaking before '{a}'.", - W015: "Expected '{a}' to have an indentation at {b} instead at {c}.", - W016: "Unexpected use of '{a}'.", - W017: "Bad operand.", - W018: "Confusing use of '{a}'.", - W019: "Use the isNaN function to compare with NaN.", - W020: "Read only.", - W021: "'{a}' is a function.", - W022: "Do not assign to the exception parameter.", - W023: "Expected an identifier in an assignment and instead saw a function invocation.", - W024: "Expected an identifier and instead saw '{a}' (a reserved word).", - W025: "Missing name in function declaration.", - W026: "Inner functions should be listed at the top of the outer function.", - W027: "Unreachable '{a}' after '{b}'.", - W028: "Label '{a}' on {b} statement.", - W030: "Expected an assignment or function call and instead saw an expression.", - W031: "Do not use 'new' for side effects.", - W032: "Unnecessary semicolon.", - W033: "Missing semicolon.", - W034: "Unnecessary directive \"{a}\".", - W035: "Empty block.", - W036: "Unexpected /*member '{a}'.", - W037: "'{a}' is a statement label.", - W038: "'{a}' used out of scope.", - W039: "'{a}' is not allowed.", - W040: "Possible strict violation.", - W041: "Use '{a}' to compare with '{b}'.", - W042: "Avoid EOL escaping.", - W043: "Bad escaping of EOL. Use option multistr if needed.", - W044: "Bad or unnecessary escaping.", - W045: "Bad number '{a}'.", - W046: "Don't use extra leading zeros '{a}'.", - W047: "A trailing decimal point can be confused with a dot: '{a}'.", - W048: "Unexpected control character in regular expression.", - W049: "Unexpected escaped character '{a}' in regular expression.", - W050: "JavaScript URL.", - W051: "Variables should not be deleted.", - W052: "Unexpected '{a}'.", - W053: "Do not use {a} as a constructor.", - W054: "The Function constructor is a form of eval.", - W055: "A constructor name should start with an uppercase letter.", - W056: "Bad constructor.", - W057: "Weird construction. Is 'new' necessary?", - W058: "Missing '()' invoking a constructor.", - W059: "Avoid arguments.{a}.", - W060: "document.write can be a form of eval.", - W061: "eval can be harmful.", - W062: "Wrap an immediate function invocation in parens " + - "to assist the reader in understanding that the expression " + - "is the result of a function, and not the function itself.", - W063: "Math is not a function.", - W064: "Missing 'new' prefix when invoking a constructor.", - W065: "Missing radix parameter.", - W066: "Implied eval. Consider passing a function instead of a string.", - W067: "Bad invocation.", - W068: "Wrapping non-IIFE function literals in parens is unnecessary.", - W069: "['{a}'] is better written in dot notation.", - W070: "Extra comma. (it breaks older versions of IE)", - W071: "This function has too many statements. ({a})", - W072: "This function has too many parameters. ({a})", - W073: "Blocks are nested too deeply. ({a})", - W074: "This function's cyclomatic complexity is too high. ({a})", - W075: "Duplicate key '{a}'.", - W076: "Unexpected parameter '{a}' in get {b} function.", - W077: "Expected a single parameter in set {a} function.", - W078: "Setter is defined without getter.", - W079: "Redefinition of '{a}'.", - W080: "It's not necessary to initialize '{a}' to 'undefined'.", - W081: "Too many var statements.", - W082: "Function declarations should not be placed in blocks. " + - "Use a function expression or move the statement to the top of " + - "the outer function.", - W083: "Don't make functions within a loop.", - W084: "Expected a conditional expression and instead saw an assignment.", - W085: "Don't use 'with'.", - W086: "Expected a 'break' statement before '{a}'.", - W087: "Forgotten 'debugger' statement?", - W088: "Creating global 'for' variable. Should be 'for (var {a} ...'.", - W089: "The body of a for in should be wrapped in an if statement to filter " + - "unwanted properties from the prototype.", - W090: "'{a}' is not a statement label.", - W091: "'{a}' is out of scope.", - W093: "Did you mean to return a conditional instead of an assignment?", - W094: "Unexpected comma.", - W095: "Expected a string and instead saw {a}.", - W096: "The '{a}' key may produce unexpected results.", - W097: "Use the function form of \"use strict\".", - W098: "'{a}' is defined but never used.", - W099: "Mixed spaces and tabs.", - W100: "This character may get silently deleted by one or more browsers.", - W101: "Line is too long.", - W102: "Trailing whitespace.", - W103: "The '{a}' property is deprecated.", - W104: "'{a}' is available in ES6 (use esnext option) or Mozilla JS extensions (use moz).", - W105: "Unexpected {a} in '{b}'.", - W106: "Identifier '{a}' is not in camel case.", - W107: "Script URL.", - W108: "Strings must use doublequote.", - W109: "Strings must use singlequote.", - W110: "Mixed double and single quotes.", - W112: "Unclosed string.", - W113: "Control character in string: {a}.", - W114: "Avoid {a}.", - W115: "Octal literals are not allowed in strict mode.", - W116: "Expected '{a}' and instead saw '{b}'.", - W117: "'{a}' is not defined.", - W118: "'{a}' is only available in Mozilla JavaScript extensions (use moz option).", - W119: "'{a}' is only available in ES6 (use esnext option).", - W120: "You might be leaking a variable ({a}) here.", - W121: "Extending prototype of native object: '{a}'.", - W122: "Invalid typeof value '{a}'", - W123: "'{a}' is already defined in outer scope.", - W124: "A generator function shall contain a yield statement.", - W125: "This line contains non-breaking spaces: http://jshint.com/doc/options/#nonbsp" -}; - -var info = { - I001: "Comma warnings can be turned off with 'laxcomma'.", - I002: "Reserved words as properties can be used under the 'es5' option.", - I003: "ES5 option is now set per default" -}; - -exports.errors = {}; -exports.warnings = {}; -exports.info = {}; - -_.each(errors, function (desc, code) { - exports.errors[code] = { code: code, desc: desc }; -}); - -_.each(warnings, function (desc, code) { - exports.warnings[code] = { code: code, desc: desc }; -}); - -_.each(info, function (desc, code) { - exports.info[code] = { code: code, desc: desc }; -}); - -},{"underscore":11}],16:[function(require,module,exports){ -/* - * Regular expressions. Some of these are stupidly long. - */ - -/*jshint maxlen:1000 */ - -"use string"; - -// Unsafe comment or string (ax) -exports.unsafeString = - /@cc|<\/?|script|\]\s*\]|<\s*!|</i; - -// Unsafe characters that are silently deleted by one or more browsers (cx) -exports.unsafeChars = - /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; - -// Characters in strings that need escaping (nx and nxg) -exports.needEsc = - /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/; - -exports.needEscGlobal = - /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; - -// Star slash (lx) -exports.starSlash = /\*\//; - -// Identifier (ix) -exports.identifier = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/; - -// JavaScript URL (jx) -exports.javascriptURL = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; - -// Catches /* falls through */ comments (ft) -exports.fallsThrough = /^\s*\/\*\s*falls?\sthrough\s*\*\/\s*$/; - -// very conservative rule (eg: only one space between the start of the comment and the first character) -// to relax the maxlen option -exports.maxlenException = /^(?:(?:\/\/|\/\*|\*) ?)?[^ ]+$/; - -},{}],17:[function(require,module,exports){ -"use strict"; - -var state = { - syntax: {}, - - reset: function () { - this.tokens = { - prev: null, - next: null, - curr: null - }; - - this.option = {}; - this.ignored = {}; - this.directive = {}; - this.jsonMode = false; - this.jsonWarnings = []; - this.lines = []; - this.tab = ""; - this.cache = {}; // Node.JS doesn't have Map. Sniff. - this.ignoreLinterErrors = false; // Blank out non-multi-line-commented - // lines when ignoring linter errors - } -}; - -exports.state = state; - -},{}],18:[function(require,module,exports){ -"use strict"; - -exports.register = function (linter) { - // Check for properties named __proto__. This special property was - // deprecated and then re-introduced for ES6. - - linter.on("Identifier", function style_scanProto(data) { - if (linter.getOption("proto")) { - return; - } - - if (data.name === "__proto__") { - linter.warn("W103", { - line: data.line, - char: data.char, - data: [ data.name ] - }); - } - }); - - // Check for properties named __iterator__. This is a special property - // available only in browsers with JavaScript 1.7 implementation. - - linter.on("Identifier", function style_scanIterator(data) { - if (linter.getOption("iterator")) { - return; - } - - if (data.name === "__iterator__") { - linter.warn("W104", { - line: data.line, - char: data.char, - data: [ data.name ] - }); - } - }); - - // Check for dangling underscores. - - linter.on("Identifier", function style_scanDangling(data) { - if (!linter.getOption("nomen")) { - return; - } - - // Underscore.js - if (data.name === "_") { - return; - } - - // In Node, __dirname and __filename should be ignored. - if (linter.getOption("node")) { - if (/^(__dirname|__filename)$/.test(data.name) && !data.isProperty) { - return; - } - } - - if (/^(_+.*|.*_+)$/.test(data.name)) { - linter.warn("W105", { - line: data.line, - char: data.from, - data: [ "dangling '_'", data.name ] - }); - } - }); - - // Check that all identifiers are using camelCase notation. - // Exceptions: names like MY_VAR and _myVar. - - linter.on("Identifier", function style_scanCamelCase(data) { - if (!linter.getOption("camelcase")) { - return; - } - - if (data.name.replace(/^_+|_+$/g, "").indexOf("_") > -1 && !data.name.match(/^[A-Z0-9_]*$/)) { - linter.warn("W106", { - line: data.line, - char: data.from, - data: [ data.name ] - }); - } - }); - - // Enforce consistency in style of quoting. - - linter.on("String", function style_scanQuotes(data) { - var quotmark = linter.getOption("quotmark"); - var code; - - if (!quotmark) { - return; - } - - // If quotmark is set to 'single' warn about all double-quotes. - - if (quotmark === "single" && data.quote !== "'") { - code = "W109"; - } - - // If quotmark is set to 'double' warn about all single-quotes. - - if (quotmark === "double" && data.quote !== "\"") { - code = "W108"; - } - - // If quotmark is set to true, remember the first quotation style - // and then warn about all others. - - if (quotmark === true) { - if (!linter.getCache("quotmark")) { - linter.setCache("quotmark", data.quote); - } - - if (linter.getCache("quotmark") !== data.quote) { - code = "W110"; - } - } - - if (code) { - linter.warn(code, { - line: data.line, - char: data.char, - }); - } - }); - - linter.on("Number", function style_scanNumbers(data) { - if (data.value.charAt(0) === ".") { - // Warn about a leading decimal point. - linter.warn("W008", { - line: data.line, - char: data.char, - data: [ data.value ] - }); - } - - if (data.value.substr(data.value.length - 1) === ".") { - // Warn about a trailing decimal point. - linter.warn("W047", { - line: data.line, - char: data.char, - data: [ data.value ] - }); - } - - if (/^00+/.test(data.value)) { - // Multiple leading zeroes. - linter.warn("W046", { - line: data.line, - char: data.char, - data: [ data.value ] - }); - } - }); - - // Warn about script URLs. - - linter.on("String", function style_scanJavaScriptURLs(data) { - var re = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i; - - if (linter.getOption("scripturl")) { - return; - } - - if (re.test(data.value)) { - linter.warn("W107", { - line: data.line, - char: data.char - }); - } - }); -}; -},{}],19:[function(require,module,exports){ -// jshint -W001 - -"use strict"; - -// Identifiers provided by the ECMAScript standard. - -exports.reservedVars = { - arguments : false, - NaN : false -}; - -exports.ecmaIdentifiers = { - Array : false, - Boolean : false, - Date : false, - decodeURI : false, - decodeURIComponent : false, - encodeURI : false, - encodeURIComponent : false, - Error : false, - "eval" : false, - EvalError : false, - Function : false, - hasOwnProperty : false, - isFinite : false, - isNaN : false, - JSON : false, - Math : false, - Map : false, - Number : false, - Object : false, - parseInt : false, - parseFloat : false, - RangeError : false, - ReferenceError : false, - RegExp : false, - Set : false, - String : false, - SyntaxError : false, - TypeError : false, - URIError : false, - WeakMap : false -}; - -// Global variables commonly provided by a web browser environment. - -exports.browser = { - Audio : false, - Blob : false, - addEventListener : false, - applicationCache : false, - atob : false, - blur : false, - btoa : false, - CanvasGradient : false, - CanvasPattern : false, - CanvasRenderingContext2D: false, - clearInterval : false, - clearTimeout : false, - close : false, - closed : false, - CustomEvent : false, - DOMParser : false, - defaultStatus : false, - document : false, - Element : false, - ElementTimeControl : false, - event : false, - FileReader : false, - FormData : false, - focus : false, - frames : false, - getComputedStyle : false, - HTMLElement : false, - HTMLAnchorElement : false, - HTMLBaseElement : false, - HTMLBlockquoteElement: false, - HTMLBodyElement : false, - HTMLBRElement : false, - HTMLButtonElement : false, - HTMLCanvasElement : false, - HTMLDirectoryElement : false, - HTMLDivElement : false, - HTMLDListElement : false, - HTMLFieldSetElement : false, - HTMLFontElement : false, - HTMLFormElement : false, - HTMLFrameElement : false, - HTMLFrameSetElement : false, - HTMLHeadElement : false, - HTMLHeadingElement : false, - HTMLHRElement : false, - HTMLHtmlElement : false, - HTMLIFrameElement : false, - HTMLImageElement : false, - HTMLInputElement : false, - HTMLIsIndexElement : false, - HTMLLabelElement : false, - HTMLLayerElement : false, - HTMLLegendElement : false, - HTMLLIElement : false, - HTMLLinkElement : false, - HTMLMapElement : false, - HTMLMenuElement : false, - HTMLMetaElement : false, - HTMLModElement : false, - HTMLObjectElement : false, - HTMLOListElement : false, - HTMLOptGroupElement : false, - HTMLOptionElement : false, - HTMLParagraphElement : false, - HTMLParamElement : false, - HTMLPreElement : false, - HTMLQuoteElement : false, - HTMLScriptElement : false, - HTMLSelectElement : false, - HTMLStyleElement : false, - HTMLTableCaptionElement: false, - HTMLTableCellElement : false, - HTMLTableColElement : false, - HTMLTableElement : false, - HTMLTableRowElement : false, - HTMLTableSectionElement: false, - HTMLTextAreaElement : false, - HTMLTitleElement : false, - HTMLUListElement : false, - HTMLVideoElement : false, - history : false, - Image : false, - length : false, - localStorage : false, - location : false, - matchMedia : false, - MessageChannel : false, - MessageEvent : false, - MessagePort : false, - MouseEvent : false, - moveBy : false, - moveTo : false, - MutationObserver : false, - name : false, - Node : false, - NodeFilter : false, - navigator : false, - onbeforeunload : true, - onblur : true, - onerror : true, - onfocus : true, - onload : true, - onresize : true, - onunload : true, - open : false, - openDatabase : false, - opener : false, - Option : false, - parent : false, - print : false, - removeEventListener : false, - resizeBy : false, - resizeTo : false, - screen : false, - scroll : false, - scrollBy : false, - scrollTo : false, - sessionStorage : false, - setInterval : false, - setTimeout : false, - SharedWorker : false, - status : false, - SVGAElement : false, - SVGAltGlyphDefElement: false, - SVGAltGlyphElement : false, - SVGAltGlyphItemElement: false, - SVGAngle : false, - SVGAnimateColorElement: false, - SVGAnimateElement : false, - SVGAnimateMotionElement: false, - SVGAnimateTransformElement: false, - SVGAnimatedAngle : false, - SVGAnimatedBoolean : false, - SVGAnimatedEnumeration: false, - SVGAnimatedInteger : false, - SVGAnimatedLength : false, - SVGAnimatedLengthList: false, - SVGAnimatedNumber : false, - SVGAnimatedNumberList: false, - SVGAnimatedPathData : false, - SVGAnimatedPoints : false, - SVGAnimatedPreserveAspectRatio: false, - SVGAnimatedRect : false, - SVGAnimatedString : false, - SVGAnimatedTransformList: false, - SVGAnimationElement : false, - SVGCSSRule : false, - SVGCircleElement : false, - SVGClipPathElement : false, - SVGColor : false, - SVGColorProfileElement: false, - SVGColorProfileRule : false, - SVGComponentTransferFunctionElement: false, - SVGCursorElement : false, - SVGDefsElement : false, - SVGDescElement : false, - SVGDocument : false, - SVGElement : false, - SVGElementInstance : false, - SVGElementInstanceList: false, - SVGEllipseElement : false, - SVGExternalResourcesRequired: false, - SVGFEBlendElement : false, - SVGFEColorMatrixElement: false, - SVGFEComponentTransferElement: false, - SVGFECompositeElement: false, - SVGFEConvolveMatrixElement: false, - SVGFEDiffuseLightingElement: false, - SVGFEDisplacementMapElement: false, - SVGFEDistantLightElement: false, - SVGFEFloodElement : false, - SVGFEFuncAElement : false, - SVGFEFuncBElement : false, - SVGFEFuncGElement : false, - SVGFEFuncRElement : false, - SVGFEGaussianBlurElement: false, - SVGFEImageElement : false, - SVGFEMergeElement : false, - SVGFEMergeNodeElement: false, - SVGFEMorphologyElement: false, - SVGFEOffsetElement : false, - SVGFEPointLightElement: false, - SVGFESpecularLightingElement: false, - SVGFESpotLightElement: false, - SVGFETileElement : false, - SVGFETurbulenceElement: false, - SVGFilterElement : false, - SVGFilterPrimitiveStandardAttributes: false, - SVGFitToViewBox : false, - SVGFontElement : false, - SVGFontFaceElement : false, - SVGFontFaceFormatElement: false, - SVGFontFaceNameElement: false, - SVGFontFaceSrcElement: false, - SVGFontFaceUriElement: false, - SVGForeignObjectElement: false, - SVGGElement : false, - SVGGlyphElement : false, - SVGGlyphRefElement : false, - SVGGradientElement : false, - SVGHKernElement : false, - SVGICCColor : false, - SVGImageElement : false, - SVGLangSpace : false, - SVGLength : false, - SVGLengthList : false, - SVGLineElement : false, - SVGLinearGradientElement: false, - SVGLocatable : false, - SVGMPathElement : false, - SVGMarkerElement : false, - SVGMaskElement : false, - SVGMatrix : false, - SVGMetadataElement : false, - SVGMissingGlyphElement: false, - SVGNumber : false, - SVGNumberList : false, - SVGPaint : false, - SVGPathElement : false, - SVGPathSeg : false, - SVGPathSegArcAbs : false, - SVGPathSegArcRel : false, - SVGPathSegClosePath : false, - SVGPathSegCurvetoCubicAbs: false, - SVGPathSegCurvetoCubicRel: false, - SVGPathSegCurvetoCubicSmoothAbs: false, - SVGPathSegCurvetoCubicSmoothRel: false, - SVGPathSegCurvetoQuadraticAbs: false, - SVGPathSegCurvetoQuadraticRel: false, - SVGPathSegCurvetoQuadraticSmoothAbs: false, - SVGPathSegCurvetoQuadraticSmoothRel: false, - SVGPathSegLinetoAbs : false, - SVGPathSegLinetoHorizontalAbs: false, - SVGPathSegLinetoHorizontalRel: false, - SVGPathSegLinetoRel : false, - SVGPathSegLinetoVerticalAbs: false, - SVGPathSegLinetoVerticalRel: false, - SVGPathSegList : false, - SVGPathSegMovetoAbs : false, - SVGPathSegMovetoRel : false, - SVGPatternElement : false, - SVGPoint : false, - SVGPointList : false, - SVGPolygonElement : false, - SVGPolylineElement : false, - SVGPreserveAspectRatio: false, - SVGRadialGradientElement: false, - SVGRect : false, - SVGRectElement : false, - SVGRenderingIntent : false, - SVGSVGElement : false, - SVGScriptElement : false, - SVGSetElement : false, - SVGStopElement : false, - SVGStringList : false, - SVGStylable : false, - SVGStyleElement : false, - SVGSwitchElement : false, - SVGSymbolElement : false, - SVGTRefElement : false, - SVGTSpanElement : false, - SVGTests : false, - SVGTextContentElement: false, - SVGTextElement : false, - SVGTextPathElement : false, - SVGTextPositioningElement: false, - SVGTitleElement : false, - SVGTransform : false, - SVGTransformList : false, - SVGTransformable : false, - SVGURIReference : false, - SVGUnitTypes : false, - SVGUseElement : false, - SVGVKernElement : false, - SVGViewElement : false, - SVGViewSpec : false, - SVGZoomAndPan : false, - TimeEvent : false, - top : false, - URL : false, - WebSocket : false, - window : false, - Worker : false, - XMLHttpRequest : false, - XMLSerializer : false, - XPathEvaluator : false, - XPathException : false, - XPathExpression : false, - XPathNamespace : false, - XPathNSResolver : false, - XPathResult : false -}; - -exports.devel = { - alert : false, - confirm: false, - console: false, - Debug : false, - opera : false, - prompt : false -}; - -exports.worker = { - importScripts: true, - postMessage : true, - self : true -}; - -// Widely adopted global names that are not part of ECMAScript standard -exports.nonstandard = { - escape : false, - unescape: false -}; - -// Globals provided by popular JavaScript environments. - -exports.couch = { - "require" : false, - respond : false, - getRow : false, - emit : false, - send : false, - start : false, - sum : false, - log : false, - exports : false, - module : false, - provides : false -}; - -exports.node = { - __filename : false, - __dirname : false, - GLOBAL : false, - global : false, - module : false, - require : false, - - // These globals are writeable because Node allows the following - // usage pattern: var Buffer = require("buffer").Buffer; - - Buffer : true, - console : true, - exports : true, - process : true, - setTimeout : true, - clearTimeout : true, - setInterval : true, - clearInterval : true, - setImmediate : true, // v0.9.1+ - clearImmediate: true // v0.9.1+ -}; - -exports.phantom = { - phantom : true, - require : true, - WebPage : true, - console : true, // in examples, but undocumented - exports : true // v1.7+ -}; - -exports.rhino = { - defineClass : false, - deserialize : false, - gc : false, - help : false, - importPackage: false, - "java" : false, - load : false, - loadClass : false, - print : false, - quit : false, - readFile : false, - readUrl : false, - runCommand : false, - seal : false, - serialize : false, - spawn : false, - sync : false, - toint32 : false, - version : false -}; - -exports.shelljs = { - target : false, - echo : false, - exit : false, - cd : false, - pwd : false, - ls : false, - find : false, - cp : false, - rm : false, - mv : false, - mkdir : false, - test : false, - cat : false, - sed : false, - grep : false, - which : false, - dirs : false, - pushd : false, - popd : false, - env : false, - exec : false, - chmod : false, - config : false, - error : false, - tempdir : false -}; - -exports.typed = { - ArrayBuffer : false, - ArrayBufferView : false, - DataView : false, - Float32Array : false, - Float64Array : false, - Int16Array : false, - Int32Array : false, - Int8Array : false, - Uint16Array : false, - Uint32Array : false, - Uint8Array : false, - Uint8ClampedArray : false -}; - -exports.wsh = { - ActiveXObject : true, - Enumerator : true, - GetObject : true, - ScriptEngine : true, - ScriptEngineBuildVersion : true, - ScriptEngineMajorVersion : true, - ScriptEngineMinorVersion : true, - VBArray : true, - WSH : true, - WScript : true, - XDomainRequest : true -}; - -// Globals provided by popular JavaScript libraries. - -exports.dojo = { - dojo : false, - dijit : false, - dojox : false, - define : false, - "require": false -}; - -exports.jquery = { - "$" : false, - jQuery : false -}; - -exports.mootools = { - "$" : false, - "$$" : false, - Asset : false, - Browser : false, - Chain : false, - Class : false, - Color : false, - Cookie : false, - Core : false, - Document : false, - DomReady : false, - DOMEvent : false, - DOMReady : false, - Drag : false, - Element : false, - Elements : false, - Event : false, - Events : false, - Fx : false, - Group : false, - Hash : false, - HtmlTable : false, - Iframe : false, - IframeShim : false, - InputValidator: false, - instanceOf : false, - Keyboard : false, - Locale : false, - Mask : false, - MooTools : false, - Native : false, - Options : false, - OverText : false, - Request : false, - Scroller : false, - Slick : false, - Slider : false, - Sortables : false, - Spinner : false, - Swiff : false, - Tips : false, - Type : false, - typeOf : false, - URI : false, - Window : false -}; - -exports.prototypejs = { - "$" : false, - "$$" : false, - "$A" : false, - "$F" : false, - "$H" : false, - "$R" : false, - "$break" : false, - "$continue" : false, - "$w" : false, - Abstract : false, - Ajax : false, - Class : false, - Enumerable : false, - Element : false, - Event : false, - Field : false, - Form : false, - Hash : false, - Insertion : false, - ObjectRange : false, - PeriodicalExecuter: false, - Position : false, - Prototype : false, - Selector : false, - Template : false, - Toggle : false, - Try : false, - Autocompleter : false, - Builder : false, - Control : false, - Draggable : false, - Draggables : false, - Droppables : false, - Effect : false, - Sortable : false, - SortableObserver : false, - Sound : false, - Scriptaculous : false -}; - -exports.yui = { - YUI : false, - Y : false, - YUI_config: false -}; - - -},{}]},{},["nr+AlQ"]) -JSHINT = require('jshint').JSHINT; -if (typeof exports === 'object' && exports) exports.JSHINT = JSHINT; -}()); diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js index bc660e7fe3a3..c2560b27682b 100644 --- a/tests/bin/eslint.js +++ b/tests/bin/eslint.js @@ -18,7 +18,9 @@ const path = require("node:path"); // Data //------------------------------------------------------------------------------ -const EXECUTABLE_PATH = path.resolve(path.join(__dirname, "../../bin/eslint.js")); +const EXECUTABLE_PATH = path.resolve( + path.join(__dirname, "../../bin/eslint.js"), +); //----------------------------------------------------------------------------- // Helpers @@ -30,7 +32,7 @@ const EXECUTABLE_PATH = path.resolve(path.join(__dirname, "../../bin/eslint.js") * @returns {Promise} A Promise that fulfills with the exit code when the child process exits */ function awaitExit(exitingProcess) { - return new Promise(resolve => exitingProcess.once("exit", resolve)); + return new Promise(resolve => exitingProcess.once("exit", resolve)); } /** @@ -40,9 +42,13 @@ function awaitExit(exitingProcess) { * @returns {Promise} A Promise that fulfills if the exit code ends up matching, and rejects otherwise. */ function assertExitCode(exitingProcess, expectedExitCode) { - return awaitExit(exitingProcess).then(exitCode => { - assert.strictEqual(exitCode, expectedExitCode, `Expected an exit code of ${expectedExitCode} but got ${exitCode}.`); - }); + return awaitExit(exitingProcess).then(exitCode => { + assert.strictEqual( + exitCode, + expectedExitCode, + `Expected an exit code of ${expectedExitCode} but got ${exitCode}.`, + ); + }); } /** @@ -52,12 +58,12 @@ function assertExitCode(exitingProcess, expectedExitCode) { * stdout and stderr output produced by the process when it exits. */ function getOutput(runningProcess) { - let stdout = ""; - let stderr = ""; + let stdout = ""; + let stderr = ""; - runningProcess.stdout.on("data", data => (stdout += data)); - runningProcess.stderr.on("data", data => (stderr += data)); - return awaitExit(runningProcess).then(() => ({ stdout, stderr })); + runningProcess.stdout.on("data", data => (stdout += data)); + runningProcess.stderr.on("data", data => (stderr += data)); + return awaitExit(runningProcess).then(() => ({ stdout, stderr })); } //------------------------------------------------------------------------------ @@ -65,433 +71,572 @@ function getOutput(runningProcess) { //------------------------------------------------------------------------------ describe("bin/eslint.js", () => { - const forkedProcesses = new Set(); - - /** - * Forks the process to run an instance of ESLint. - * @param {string[]} [args] An array of arguments - * @param {Object} [options] An object containing options for the resulting child process - * @returns {ChildProcess} The resulting child process - */ - function runESLint(args, options) { - const newProcess = childProcess.fork(EXECUTABLE_PATH, args, Object.assign({ silent: true }, options)); - - forkedProcesses.add(newProcess); - return newProcess; - } - - describe("reading from stdin", () => { - it("has exit code 0 if no linting errors are reported", () => { - const child = runESLint(["--stdin", "--no-config-lookup"]); - - child.stdin.write("var foo = bar;\n"); - child.stdin.end(); - return assertExitCode(child, 0); - }); - - it("has exit code 0 if no linting errors are reported", () => { - const child = runESLint( - [ - "--stdin", - "--no-config-lookup", - "--rule", - "{'no-extra-semi': 2}", - "--fix-dry-run", - "--format", - "json" - ], - { - - // Use the tests directory as the CWD to suppress the ESLintIgnoreWarning - cwd: path.resolve(__dirname, "../") - } - ); - - const expectedOutput = JSON.stringify([ - { - filePath: "", - messages: [], - suppressedMessages: [], - errorCount: 0, - fatalErrorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var foo = bar;\n", - usedDeprecatedRules: [ - { - ruleId: "no-extra-semi", - replacedBy: ["@stylistic/js/no-extra-semi"], - info: { - 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-js", - url: "https://eslint.style/packages/js" - }, - rule: { - name: "no-extra-semi", - url: "https://eslint.style/rules/js/no-extra-semi" - } - } - ] - } - } - ] - } - ]); - - const exitCodePromise = assertExitCode(child, 0); - const stdoutPromise = getOutput(child).then(output => { - assert.strictEqual(output.stdout.trim(), expectedOutput); - assert.strictEqual(output.stderr, ""); - }); - - child.stdin.write("var foo = bar;;\n"); - child.stdin.end(); - - return Promise.all([exitCodePromise, stdoutPromise]); - }); - - it("has exit code 1 if a syntax error is thrown", () => { - const child = runESLint(["--stdin", "--no-config-lookup"]); - - child.stdin.write("This is not valid JS syntax.\n"); - child.stdin.end(); - return assertExitCode(child, 1); - }); - - it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => { - const child = runESLint(["--stdin", "--no-config-lookup", "--exit-on-fatal-error"]); - - child.stdin.write("This is not valid JS syntax.\n"); - child.stdin.end(); - return assertExitCode(child, 2); - }); - - it("has exit code 1 if a linting error occurs", () => { - const child = runESLint(["--stdin", "--no-config-lookup", "--rule", "semi:2"]); - - child.stdin.write("var foo = bar // <-- no semicolon\n"); - child.stdin.end(); - return assertExitCode(child, 1); - }); - - it( - "gives a detailed error message if no config file is found in /", - () => { - if ( - fs.readdirSync("/").includes( - "eslint.config.js" - ) - ) { - return Promise.resolve(true); - } - const child = runESLint( - ["--stdin"], { cwd: "/", env: { HOME: "/" } } - ); - - const exitCodePromise = assertExitCode(child, 2); - const stderrPromise = getOutput(child).then(output => { - assert.match( - output.stderr, - /couldn't find an eslint\.config/u - ); - }); - - child.stdin.write("1 < 3;\n"); - child.stdin.end(); - return Promise.all([exitCodePromise, stderrPromise]); - } - ); - - it("successfully reads from an asynchronous pipe", () => { - const child = runESLint(["--stdin", "--no-config-lookup"]); - - child.stdin.write("var foo = bar;\n"); - return new Promise(resolve => setTimeout(resolve, 300)).then(() => { - child.stdin.write("var baz = qux;\n"); - child.stdin.end(); - - return assertExitCode(child, 0); - }); - }); - - it("successfully handles more than 4k data via stdin", () => { - const child = runESLint(["--stdin", "--no-config-lookup"]); - const large = fs.createReadStream(path.join(__dirname, "../bench/large.js"), "utf8"); - - large.pipe(child.stdin); - - return assertExitCode(child, 0); - }); - }); - - describe("running on files", () => { - it("has exit code 0 if no linting errors occur", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup"]), 0)); - it("has exit code 0 if a linting warning is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [1, never]"]), 0)); - it("has exit code 1 if a linting error is reported", () => assertExitCode(runESLint(["bin/eslint.js", "--no-config-lookup", "--rule", "semi: [2, never]"]), 1)); - it("has exit code 1 if a syntax error is thrown", () => assertExitCode(runESLint(["tests/fixtures/exit-on-fatal-error/fatal-error.js", "--no-config-lookup"]), 1)); - }); - - describe("automatically fixing files", () => { - const fixturesPath = path.join(__dirname, "../fixtures/autofix-integration"); - const tempFilePath = `${fixturesPath}/temp.js`; - const startingText = fs.readFileSync(`${fixturesPath}/left-pad.js`).toString(); - const expectedFixedText = fs.readFileSync(`${fixturesPath}/left-pad-expected.js`).toString(); - const expectedFixedTextQuiet = fs.readFileSync(`${fixturesPath}/left-pad-expected-quiet.js`).toString(); - - beforeEach(() => { - fs.writeFileSync(tempFilePath, startingText); - }); - - it("has exit code 0 and fixes a file if all rules can be fixed", () => { - const child = runESLint(["--fix", "--no-config-lookup", "--no-ignore", tempFilePath]); - const exitCodeAssertion = assertExitCode(child, 0); - const outputFileAssertion = awaitExit(child).then(() => { - assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText); - }); - - return Promise.all([exitCodeAssertion, outputFileAssertion]); - }); - - it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => { - const child = runESLint(["--fix", "--quiet", "--no-config-lookup", "--no-ignore", tempFilePath]); - const exitCodeAssertion = assertExitCode(child, 0); - const stdoutAssertion = getOutput(child).then(output => assert.strictEqual(output.stdout, "")); - const outputFileAssertion = awaitExit(child).then(() => { - assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet); - }); - - return Promise.all([exitCodeAssertion, stdoutAssertion, outputFileAssertion]); - }); - - it("has exit code 1 and fixes a file if not all rules can be fixed", () => { - const child = runESLint(["--fix", "--no-config-lookup", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]); - const exitCodeAssertion = assertExitCode(child, 1); - const outputFileAssertion = awaitExit(child).then(() => { - assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedText); - }); - - return Promise.all([exitCodeAssertion, outputFileAssertion]); - }); - - afterEach(() => { - fs.unlinkSync(tempFilePath); - }); - }); - - describe("cache files", () => { - const CACHE_PATH = ".temp-eslintcache"; - const SOURCE_PATH = "tests/fixtures/cache/src/test-file.js"; - const ARGS_WITHOUT_CACHE = ["--no-config-lookup", "--no-ignore", SOURCE_PATH, "--cache-location", CACHE_PATH]; - const ARGS_WITH_CACHE = ARGS_WITHOUT_CACHE.concat("--cache"); - - describe("when no cache file exists", () => { - it("creates a cache file when the --cache flag is used", () => { - const child = runESLint(ARGS_WITH_CACHE); - - return assertExitCode(child, 0).then(() => { - assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location"); - - // Cache file should contain valid JSON - JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")); - }); - }); - }); - - describe("when a valid cache file already exists", () => { - beforeEach(() => { - const child = runESLint(ARGS_WITH_CACHE); - - return assertExitCode(child, 0).then(() => { - assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location"); - }); - }); - it("can lint with an existing cache file and the --cache flag", () => { - const child = runESLint(ARGS_WITH_CACHE); - - return assertExitCode(child, 0).then(() => { - - // Note: This doesn't actually verify that the cache file is used for anything. - assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should still exist after linting with --cache"); - }); - }); - it("updates the cache file when the source file is modified", () => { - const initialCacheContent = fs.readFileSync(CACHE_PATH, "utf8"); - - // Update the file to change its mtime - fs.writeFileSync(SOURCE_PATH, fs.readFileSync(SOURCE_PATH, "utf8")); - - const child = runESLint(ARGS_WITH_CACHE); - - return assertExitCode(child, 0).then(() => { - const newCacheContent = fs.readFileSync(CACHE_PATH, "utf8"); - - assert.notStrictEqual(initialCacheContent, newCacheContent, "Cache file should change after source is modified"); - }); - }); - it("deletes the cache file when run without the --cache argument", () => { - const child = runESLint(ARGS_WITHOUT_CACHE); - - return assertExitCode(child, 0).then(() => { - assert.isFalse(fs.existsSync(CACHE_PATH), "Cache file should be deleted after running ESLint without the --cache argument"); - }); - }); - }); - - // https://github.com/eslint/eslint/issues/7748 - describe("when an invalid cache file already exists", () => { - beforeEach(() => { - fs.writeFileSync(CACHE_PATH, "This is not valid JSON."); - - // Sanity check - assert.throws( - () => JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")), - SyntaxError, - /Unexpected token/u, - "Cache file should not contain valid JSON at the start" - ); - }); - - it("overwrites the invalid cache file with a valid one when the --cache argument is used", () => { - const child = runESLint(ARGS_WITH_CACHE); - - return assertExitCode(child, 0).then(() => { - assert.isTrue(fs.existsSync(CACHE_PATH), "Cache file should exist at the given location"); - - // Cache file should contain valid JSON - JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")); - }); - }); - - it("deletes the invalid cache file when the --cache argument is not used", () => { - const child = runESLint(ARGS_WITHOUT_CACHE); - - return assertExitCode(child, 0).then(() => { - assert.isFalse(fs.existsSync(CACHE_PATH), "Cache file should be deleted after running ESLint without the --cache argument"); - }); - }); - }); - - afterEach(() => { - if (fs.existsSync(CACHE_PATH)) { - fs.unlinkSync(CACHE_PATH); - } - }); - }); - - describe("handling crashes", () => { - it("prints the error message to stderr in the event of a crash", () => { - const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "--no-config-lookup", "Makefile.js"]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - const expectedSubstring = "Syntax error in selector"; - - assert.strictEqual(output.stdout, ""); - assert.include(output.stderr, expectedSubstring); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - - it("prints the error message exactly once to stderr in the event of a crash", () => { - const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "--no-config-lookup", "Makefile.js"]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - const expectedSubstring = "Syntax error in selector"; - - assert.strictEqual(output.stdout, ""); - assert.include(output.stderr, expectedSubstring); - - // The message should appear exactly once in stderr - assert.strictEqual(output.stderr.indexOf(expectedSubstring), output.stderr.lastIndexOf(expectedSubstring)); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - - it("does not exit with zero when there is an error in the next tick", () => { - const config = path.join(__dirname, "../fixtures/bin/eslint.config-promise-tick-throws.js"); - const file = path.join(__dirname, "../fixtures/bin/empty.js"); - const child = runESLint(["--config", config, file]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - - // ensure the expected error was printed - assert.include(output.stderr, "test_error_stack"); - - // ensure that linting the file did not cause an error - assert.notInclude(output.stderr, "empty.js"); - assert.notInclude(output.stdout, "empty.js"); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - - // https://github.com/eslint/eslint/issues/17560 - describe("does not print duplicate errors in the event of a crash", () => { - - it("when there is an invalid config read from a config file", () => { - const config = path.join(__dirname, "../fixtures/bin/eslint.config-invalid.js"); - const child = runESLint(["--config", config, "conf", "tools"]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - - // The error text should appear exactly once in stderr - assert.strictEqual(output.stderr.match(/A config object is using the "globals" key/gu).length, 1); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - - it("when there is an error in the next tick", () => { - const config = path.join(__dirname, "../fixtures/bin/eslint.config-tick-throws.js"); - const child = runESLint(["--config", config, "Makefile.js"]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - - // The error text should appear exactly once in stderr - assert.strictEqual(output.stderr.match(/test_error_stack/gu).length, 1); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - }); - }); - - // https://github.com/eslint/eslint/issues/17960 - it("should include key information in the error message when there is an invalid config", () => { - - // The error message should include the key name - const config = path.join(__dirname, "../fixtures/bin/eslint.config-invalid-key.js"); - const child = runESLint(["--config", config, "conf", "tools"]); - const exitCodeAssertion = assertExitCode(child, 2); - const outputAssertion = getOutput(child).then(output => { - assert.include(output.stderr, "Key \"linterOptions\": Key \"reportUnusedDisableDirectives\""); - }); - - return Promise.all([exitCodeAssertion, outputAssertion]); - - }); - - it("prints the error message pointing to line of code", () => { - const invalidConfig = path.join(__dirname, "../fixtures/bin/eslint.config.js"); - const child = runESLint(["--no-ignore", "-c", invalidConfig]); - - return assertExitCode(child, 2); - }); - }); - - afterEach(() => { - - // Clean up all the processes after every test. - forkedProcesses.forEach(child => child.kill()); - forkedProcesses.clear(); - }); + const forkedProcesses = new Set(); + + /** + * Forks the process to run an instance of ESLint. + * @param {string[]} [args] An array of arguments + * @param {Object} [options] An object containing options for the resulting child process + * @returns {ChildProcess} The resulting child process + */ + function runESLint(args, options) { + const newProcess = childProcess.fork( + EXECUTABLE_PATH, + args, + Object.assign({ silent: true }, options), + ); + + forkedProcesses.add(newProcess); + return newProcess; + } + + describe("reading from stdin", () => { + it("has exit code 0 if no linting errors are reported", () => { + const child = runESLint(["--stdin", "--no-config-lookup"]); + + child.stdin.write("var foo = bar;\n"); + child.stdin.end(); + return assertExitCode(child, 0); + }); + + it("has exit code 0 if no linting errors are reported", () => { + const child = runESLint( + [ + "--stdin", + "--no-config-lookup", + "--rule", + "{'no-extra-semi': 2}", + "--fix-dry-run", + "--format", + "json", + ], + { + // Use the tests directory as the CWD to suppress the ESLintIgnoreWarning + cwd: path.resolve(__dirname, "../"), + }, + ); + + const expectedOutput = JSON.stringify([ + { + filePath: "", + messages: [], + suppressedMessages: [], + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var foo = bar;\n", + usedDeprecatedRules: [ + { + ruleId: "no-extra-semi", + replacedBy: ["@stylistic/js/no-extra-semi"], + info: { + 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-js", + url: "https://eslint.style/packages/js", + }, + rule: { + name: "no-extra-semi", + url: "https://eslint.style/rules/js/no-extra-semi", + }, + }, + ], + }, + }, + ], + }, + ]); + + const exitCodePromise = assertExitCode(child, 0); + const stdoutPromise = getOutput(child).then(output => { + assert.strictEqual(output.stdout.trim(), expectedOutput); + assert.strictEqual(output.stderr, ""); + }); + + child.stdin.write("var foo = bar;;\n"); + child.stdin.end(); + + return Promise.all([exitCodePromise, stdoutPromise]); + }); + + it("has exit code 1 if a syntax error is thrown", () => { + const child = runESLint(["--stdin", "--no-config-lookup"]); + + child.stdin.write("This is not valid JS syntax.\n"); + child.stdin.end(); + return assertExitCode(child, 1); + }); + + it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => { + const child = runESLint([ + "--stdin", + "--no-config-lookup", + "--exit-on-fatal-error", + ]); + + child.stdin.write("This is not valid JS syntax.\n"); + child.stdin.end(); + return assertExitCode(child, 2); + }); + + it("has exit code 1 if a linting error occurs", () => { + const child = runESLint([ + "--stdin", + "--no-config-lookup", + "--rule", + "semi:2", + ]); + + child.stdin.write("var foo = bar // <-- no semicolon\n"); + child.stdin.end(); + return assertExitCode(child, 1); + }); + + it("gives a detailed error message if no config file is found in /", () => { + if (fs.readdirSync("/").includes("eslint.config.js")) { + return Promise.resolve(true); + } + const child = runESLint(["--stdin"], { + cwd: "/", + env: { HOME: "/" }, + }); + + const exitCodePromise = assertExitCode(child, 2); + const stderrPromise = getOutput(child).then(output => { + assert.match(output.stderr, /couldn't find an eslint\.config/u); + }); + + child.stdin.write("1 < 3;\n"); + child.stdin.end(); + return Promise.all([exitCodePromise, stderrPromise]); + }); + + it("successfully reads from an asynchronous pipe", () => { + const child = runESLint(["--stdin", "--no-config-lookup"]); + + child.stdin.write("var foo = bar;\n"); + return new Promise(resolve => setTimeout(resolve, 300)).then(() => { + child.stdin.write("var baz = qux;\n"); + child.stdin.end(); + + return assertExitCode(child, 0); + }); + }); + + it("successfully handles more than 4k data via stdin", () => { + const child = runESLint(["--stdin", "--no-config-lookup"]); + const large = fs.createReadStream( + path.join(__dirname, "../bench/large.js"), + "utf8", + ); + + large.pipe(child.stdin); + + return assertExitCode(child, 0); + }); + }); + + describe("running on files", () => { + it("has exit code 0 if no linting errors occur", () => + assertExitCode( + runESLint(["bin/eslint.js", "--no-config-lookup"]), + 0, + )); + it("has exit code 0 if a linting warning is reported", () => + assertExitCode( + runESLint([ + "bin/eslint.js", + "--no-config-lookup", + "--rule", + "semi: [1, never]", + ]), + 0, + )); + it("has exit code 1 if a linting error is reported", () => + assertExitCode( + runESLint([ + "bin/eslint.js", + "--no-config-lookup", + "--rule", + "semi: [2, never]", + ]), + 1, + )); + it("has exit code 1 if a syntax error is thrown", () => + assertExitCode( + runESLint([ + "tests/fixtures/exit-on-fatal-error/fatal-error.js", + "--no-config-lookup", + ]), + 1, + )); + }); + + describe("automatically fixing files", () => { + const fixturesPath = path.join( + __dirname, + "../fixtures/autofix-integration", + ); + const tempFilePath = `${fixturesPath}/temp.js`; + const startingText = fs + .readFileSync(`${fixturesPath}/left-pad.js`) + .toString(); + const expectedFixedText = fs + .readFileSync(`${fixturesPath}/left-pad-expected.js`) + .toString(); + const expectedFixedTextQuiet = fs + .readFileSync(`${fixturesPath}/left-pad-expected-quiet.js`) + .toString(); + + beforeEach(() => { + fs.writeFileSync(tempFilePath, startingText); + }); + + it("has exit code 0 and fixes a file if all rules can be fixed", () => { + const child = runESLint([ + "--fix", + "--no-config-lookup", + "--no-ignore", + tempFilePath, + ]); + const exitCodeAssertion = assertExitCode(child, 0); + const outputFileAssertion = awaitExit(child).then(() => { + assert.strictEqual( + fs.readFileSync(tempFilePath).toString(), + expectedFixedText, + ); + }); + + return Promise.all([exitCodeAssertion, outputFileAssertion]); + }); + + it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => { + const child = runESLint([ + "--fix", + "--quiet", + "--no-config-lookup", + "--no-ignore", + tempFilePath, + ]); + const exitCodeAssertion = assertExitCode(child, 0); + const stdoutAssertion = getOutput(child).then(output => + assert.strictEqual(output.stdout, ""), + ); + const outputFileAssertion = awaitExit(child).then(() => { + assert.strictEqual( + fs.readFileSync(tempFilePath).toString(), + expectedFixedTextQuiet, + ); + }); + + return Promise.all([ + exitCodeAssertion, + stdoutAssertion, + outputFileAssertion, + ]); + }); + + it("has exit code 1 and fixes a file if not all rules can be fixed", () => { + const child = runESLint([ + "--fix", + "--no-config-lookup", + "--no-ignore", + "--rule", + "max-len: [2, 10]", + tempFilePath, + ]); + const exitCodeAssertion = assertExitCode(child, 1); + const outputFileAssertion = awaitExit(child).then(() => { + assert.strictEqual( + fs.readFileSync(tempFilePath).toString(), + expectedFixedText, + ); + }); + + return Promise.all([exitCodeAssertion, outputFileAssertion]); + }); + + afterEach(() => { + fs.unlinkSync(tempFilePath); + }); + }); + + describe("cache files", () => { + const CACHE_PATH = ".temp-eslintcache"; + const SOURCE_PATH = "tests/fixtures/cache/src/test-file.js"; + const ARGS_WITHOUT_CACHE = [ + "--no-config-lookup", + "--no-ignore", + SOURCE_PATH, + "--cache-location", + CACHE_PATH, + ]; + const ARGS_WITH_CACHE = ARGS_WITHOUT_CACHE.concat("--cache"); + + describe("when no cache file exists", () => { + it("creates a cache file when the --cache flag is used", () => { + const child = runESLint(ARGS_WITH_CACHE); + + return assertExitCode(child, 0).then(() => { + assert.isTrue( + fs.existsSync(CACHE_PATH), + "Cache file should exist at the given location", + ); + + // Cache file should contain valid JSON + JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")); + }); + }); + }); + + describe("when a valid cache file already exists", () => { + beforeEach(() => { + const child = runESLint(ARGS_WITH_CACHE); + + return assertExitCode(child, 0).then(() => { + assert.isTrue( + fs.existsSync(CACHE_PATH), + "Cache file should exist at the given location", + ); + }); + }); + it("can lint with an existing cache file and the --cache flag", () => { + const child = runESLint(ARGS_WITH_CACHE); + + return assertExitCode(child, 0).then(() => { + // Note: This doesn't actually verify that the cache file is used for anything. + assert.isTrue( + fs.existsSync(CACHE_PATH), + "Cache file should still exist after linting with --cache", + ); + }); + }); + it("updates the cache file when the source file is modified", () => { + const initialCacheContent = fs.readFileSync(CACHE_PATH, "utf8"); + + // Update the file to change its mtime + fs.writeFileSync( + SOURCE_PATH, + fs.readFileSync(SOURCE_PATH, "utf8"), + ); + + const child = runESLint(ARGS_WITH_CACHE); + + return assertExitCode(child, 0).then(() => { + const newCacheContent = fs.readFileSync(CACHE_PATH, "utf8"); + + assert.notStrictEqual( + initialCacheContent, + newCacheContent, + "Cache file should change after source is modified", + ); + }); + }); + it("deletes the cache file when run without the --cache argument", () => { + const child = runESLint(ARGS_WITHOUT_CACHE); + + return assertExitCode(child, 0).then(() => { + assert.isFalse( + fs.existsSync(CACHE_PATH), + "Cache file should be deleted after running ESLint without the --cache argument", + ); + }); + }); + }); + + // https://github.com/eslint/eslint/issues/7748 + describe("when an invalid cache file already exists", () => { + beforeEach(() => { + fs.writeFileSync(CACHE_PATH, "This is not valid JSON."); + + // Sanity check + assert.throws( + () => JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")), + SyntaxError, + /Unexpected token/u, + "Cache file should not contain valid JSON at the start", + ); + }); + + it("overwrites the invalid cache file with a valid one when the --cache argument is used", () => { + const child = runESLint(ARGS_WITH_CACHE); + + return assertExitCode(child, 0).then(() => { + assert.isTrue( + fs.existsSync(CACHE_PATH), + "Cache file should exist at the given location", + ); + + // Cache file should contain valid JSON + JSON.parse(fs.readFileSync(CACHE_PATH, "utf8")); + }); + }); + + it("deletes the invalid cache file when the --cache argument is not used", () => { + const child = runESLint(ARGS_WITHOUT_CACHE); + + return assertExitCode(child, 0).then(() => { + assert.isFalse( + fs.existsSync(CACHE_PATH), + "Cache file should be deleted after running ESLint without the --cache argument", + ); + }); + }); + }); + + afterEach(() => { + if (fs.existsSync(CACHE_PATH)) { + fs.unlinkSync(CACHE_PATH); + } + }); + }); + + describe("handling crashes", () => { + it("prints the error message to stderr in the event of a crash", () => { + const child = runESLint([ + "--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", + "--no-config-lookup", + "Makefile.js", + ]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + const expectedSubstring = "Syntax error in selector"; + + assert.strictEqual(output.stdout, ""); + assert.include(output.stderr, expectedSubstring); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("prints the error message exactly once to stderr in the event of a crash", () => { + const child = runESLint([ + "--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", + "--no-config-lookup", + "Makefile.js", + ]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + const expectedSubstring = "Syntax error in selector"; + + assert.strictEqual(output.stdout, ""); + assert.include(output.stderr, expectedSubstring); + + // The message should appear exactly once in stderr + assert.strictEqual( + output.stderr.indexOf(expectedSubstring), + output.stderr.lastIndexOf(expectedSubstring), + ); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("does not exit with zero when there is an error in the next tick", () => { + const config = path.join( + __dirname, + "../fixtures/bin/eslint.config-promise-tick-throws.js", + ); + const file = path.join(__dirname, "../fixtures/bin/empty.js"); + const child = runESLint(["--config", config, file]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + // ensure the expected error was printed + assert.include(output.stderr, "test_error_stack"); + + // ensure that linting the file did not cause an error + assert.notInclude(output.stderr, "empty.js"); + assert.notInclude(output.stdout, "empty.js"); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + // https://github.com/eslint/eslint/issues/17560 + describe("does not print duplicate errors in the event of a crash", () => { + it("when there is an invalid config read from a config file", () => { + const config = path.join( + __dirname, + "../fixtures/bin/eslint.config-invalid.js", + ); + const child = runESLint(["--config", config, "conf", "tools"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + // The error text should appear exactly once in stderr + assert.strictEqual( + output.stderr.match( + /A config object is using the "globals" key/gu, + ).length, + 1, + ); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("when there is an error in the next tick", () => { + const config = path.join( + __dirname, + "../fixtures/bin/eslint.config-tick-throws.js", + ); + const child = runESLint(["--config", config, "Makefile.js"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + // The error text should appear exactly once in stderr + assert.strictEqual( + output.stderr.match(/test_error_stack/gu).length, + 1, + ); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + }); + + // https://github.com/eslint/eslint/issues/17960 + it("should include key information in the error message when there is an invalid config", () => { + // The error message should include the key name + const config = path.join( + __dirname, + "../fixtures/bin/eslint.config-invalid-key.js", + ); + const child = runESLint(["--config", config, "conf", "tools"]); + const exitCodeAssertion = assertExitCode(child, 2); + const outputAssertion = getOutput(child).then(output => { + assert.include( + output.stderr, + 'Key "linterOptions": Key "reportUnusedDisableDirectives"', + ); + }); + + return Promise.all([exitCodeAssertion, outputAssertion]); + }); + + it("prints the error message pointing to line of code", () => { + const invalidConfig = path.join( + __dirname, + "../fixtures/bin/eslint.config.js", + ); + const child = runESLint(["--no-ignore", "-c", invalidConfig]); + + return assertExitCode(child, 2); + }); + }); + + afterEach(() => { + // Clean up all the processes after every test. + forkedProcesses.forEach(child => child.kill()); + forkedProcesses.clear(); + }); }); diff --git a/tests/conf/eslint-all.js b/tests/conf/eslint-all.js index 6475965a02ab..af5f5688dd14 100644 --- a/tests/conf/eslint-all.js +++ b/tests/conf/eslint-all.js @@ -18,26 +18,27 @@ const rules = eslintAll.rules; //------------------------------------------------------------------------------ describe("eslint-all", () => { - it("should only include rules", () => { - const ruleNames = Object.keys(rules); + it("should only include rules", () => { + const ruleNames = Object.keys(rules); - assert.notInclude(ruleNames, ".eslintrc.yml"); + assert.notInclude(ruleNames, ".eslintrc.yml"); + }); - }); + it("should return all rules", () => { + const ruleNames = Object.keys(rules); + const count = ruleNames.length; + const someRule = "yoda"; - it("should return all rules", () => { - const ruleNames = Object.keys(rules); - const count = ruleNames.length; - const someRule = "yoda"; + assert.include(ruleNames, someRule); + assert.isBelow(count, 200); + }); - assert.include(ruleNames, someRule); - assert.isBelow(count, 200); - }); + it("should configure all rules as errors", () => { + const ruleNames = Object.keys(rules); + const nonErrorRules = ruleNames.filter( + ruleName => rules[ruleName] !== "error", + ); - it("should configure all rules as errors", () => { - const ruleNames = Object.keys(rules); - const nonErrorRules = ruleNames.filter(ruleName => rules[ruleName] !== "error"); - - assert.strictEqual(nonErrorRules.length, 0); - }); + assert.strictEqual(nonErrorRules.length, 0); + }); }); diff --git a/tests/conf/eslint-recommended.js b/tests/conf/eslint-recommended.js index 59803dce6cf7..8ea2211882fd 100644 --- a/tests/conf/eslint-recommended.js +++ b/tests/conf/eslint-recommended.js @@ -18,11 +18,11 @@ const rules = eslintRecommended.rules; //------------------------------------------------------------------------------ describe("eslint-recommended", () => { - it("should configure recommended rules as error", () => { - assert.strictEqual(rules["no-undef"], "error"); - }); + it("should configure recommended rules as error", () => { + assert.strictEqual(rules["no-undef"], "error"); + }); - it("should not configure non-recommended rules", () => { - assert.notProperty(rules, "camelcase"); - }); + it("should not configure non-recommended rules", () => { + assert.notProperty(rules, "camelcase"); + }); }); diff --git a/tests/fixtures/bad-examples.md b/tests/fixtures/bad-examples.md index 1d0f118966ce..d112aceb5e0b 100644 --- a/tests/fixtures/bad-examples.md +++ b/tests/fixtures/bad-examples.md @@ -17,7 +17,7 @@ export default "foo"; :::correct -````ts +````rs const foo = "bar"; const foo = "baz"; diff --git a/tests/lib/api.js b/tests/lib/api.js index 71a5f42930ad..7906e4c83555 100644 --- a/tests/lib/api.js +++ b/tests/lib/api.js @@ -10,74 +10,77 @@ //----------------------------------------------------------------------------- const assert = require("chai").assert, - api = require("../../lib/api"), - { LegacyESLint } = require("../../lib/eslint/legacy-eslint"); + api = require("../../lib/api"), + { LegacyESLint } = require("../../lib/eslint/legacy-eslint"); //----------------------------------------------------------------------------- // Tests //----------------------------------------------------------------------------- describe("api", () => { - - it("should have ESLint exposed", () => { - assert.isFunction(api.ESLint); - }); - - it("should have RuleTester exposed", () => { - assert.isFunction(api.RuleTester); - }); - - it("should not have CLIEngine exposed", () => { - assert.isUndefined(api.CLIEngine); - }); - - it("should not have linter exposed", () => { - assert.isUndefined(api.linter); - }); - - it("should have Linter exposed", () => { - assert.isFunction(api.Linter); - }); - - it("should have SourceCode exposed", () => { - assert.isFunction(api.SourceCode); - }); - - describe("loadESLint", () => { - - afterEach(() => { - delete process.env.ESLINT_USE_FLAT_CONFIG; - }); - - it("should be a function", () => { - assert.isFunction(api.loadESLint); - }); - - it("should return a Promise", () => { - assert.instanceOf(api.loadESLint(), Promise); - }); - - it("should return ESLint when useFlatConfig is true", async () => { - assert.strictEqual(await api.loadESLint({ useFlatConfig: true }), api.ESLint); - }); - - it("should return LegacyESLint when useFlatConfig is false", async () => { - assert.strictEqual(await api.loadESLint({ useFlatConfig: false }), LegacyESLint); - }); - - it("should return ESLint when useFlatConfig is not provided", async () => { - assert.strictEqual(await api.loadESLint(), api.ESLint); - }); - - it("should return LegacyESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is false", async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "false"; - assert.strictEqual(await api.loadESLint(), LegacyESLint); - }); - - it("should return ESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is true", async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "true"; - assert.strictEqual(await api.loadESLint(), api.ESLint); - }); - }); - + it("should have ESLint exposed", () => { + assert.isFunction(api.ESLint); + }); + + it("should have RuleTester exposed", () => { + assert.isFunction(api.RuleTester); + }); + + it("should not have CLIEngine exposed", () => { + assert.isUndefined(api.CLIEngine); + }); + + it("should not have linter exposed", () => { + assert.isUndefined(api.linter); + }); + + it("should have Linter exposed", () => { + assert.isFunction(api.Linter); + }); + + it("should have SourceCode exposed", () => { + assert.isFunction(api.SourceCode); + }); + + describe("loadESLint", () => { + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + it("should be a function", () => { + assert.isFunction(api.loadESLint); + }); + + it("should return a Promise", () => { + assert.instanceOf(api.loadESLint(), Promise); + }); + + it("should return ESLint when useFlatConfig is true", async () => { + assert.strictEqual( + await api.loadESLint({ useFlatConfig: true }), + api.ESLint, + ); + }); + + it("should return LegacyESLint when useFlatConfig is false", async () => { + assert.strictEqual( + await api.loadESLint({ useFlatConfig: false }), + LegacyESLint, + ); + }); + + it("should return ESLint when useFlatConfig is not provided", async () => { + assert.strictEqual(await api.loadESLint(), api.ESLint); + }); + + it("should return LegacyESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is false", async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + assert.strictEqual(await api.loadESLint(), LegacyESLint); + }); + + it("should return ESLint when useFlatConfig is not provided and ESLINT_USE_FLAT_CONFIG is true", async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; + assert.strictEqual(await api.loadESLint(), api.ESLint); + }); + }); }); diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js index a7907faaea37..c4944e9347fc 100644 --- a/tests/lib/cli-engine/cli-engine.js +++ b/tests/lib/cli-engine/cli-engine.js @@ -10,18 +10,16 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - path = require("node:path"), - sinon = require("sinon"), - shell = require("shelljs"), - fs = require("node:fs"), - os = require("node:os"), - hash = require("../../../lib/cli-engine/hash"), - { - Legacy: { - CascadingConfigArrayFactory - } - } = require("@eslint/eslintrc"), - { unIndent, createCustomTeardown } = require("../../_utils"); + path = require("node:path"), + sinon = require("sinon"), + shell = require("shelljs"), + fs = require("node:fs"), + os = require("node:os"), + hash = require("../../../lib/cli-engine/hash"), + { + Legacy: { CascadingConfigArrayFactory }, + } = require("@eslint/eslintrc"), + { unIndent, createCustomTeardown } = require("../../_utils"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const fCache = require("file-entry-cache"); @@ -31,3334 +29,4136 @@ const fCache = require("file-entry-cache"); //------------------------------------------------------------------------------ describe("CLIEngine", () => { - - 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 - } - ], - 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": ` + 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, + }, + ], + 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 +4169,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 +4221,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 +4271,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" - }, - { - 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": ` + `, + ); + }); + + 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 { @@ -3971,3049 +4791,3576 @@ describe("CLIEngine", () => { }, }; `, - ".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", - 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", + 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); + }); + }); + }); }); diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js index 0cf78aa74254..288526bc3ece 100644 --- a/tests/lib/cli-engine/file-enumerator.js +++ b/tests/lib/cli-engine/file-enumerator.js @@ -15,9 +15,7 @@ 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,613 +25,830 @@ 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", () => { - 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 }]); - }); - }); - }); - - 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 }, + ]); + }); + }); + }); + + 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/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/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/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/lint-result-cache.js b/tests/lib/cli-engine/lint-result-cache.js index d5d23e5e9705..1be8b7eb92cb 100644 --- a/tests/lib/cli-engine/lint-result-cache.js +++ b/tests/lib/cli-engine/lint-result-cache.js @@ -9,378 +9,422 @@ //----------------------------------------------------------------------------- const assert = require("chai").assert, - { CLIEngine } = require("../../../lib/cli-engine"), - fs = require("node:fs"), - path = require("node: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"; - - 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); - }); - }); + 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 84b2cd6709f2..acd944746efb 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -15,14 +15,14 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - 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"); + 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"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); @@ -31,2140 +31,3111 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); //------------------------------------------------------------------------------ describe("cli", () => { - - 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(() => { - sinon.stub(process, "emitWarning").withArgs(sinon.match.any, "ESLintIgnoreWarning").returns(); - process.emitWarning.callThrough(); - }); - - 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 processStub; - - beforeEach(() => { - sinon.restore(); - processStub = sinon.stub(process, "emitWarning"); - process.env = { ...originalEnv }; - }); - - afterEach(() => { - processStub.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.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.strictEqual(processStub.getCall(0).args[1], "ESLintRCWarning"); - } - }); - - 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(); - }); - - 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 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 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 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 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); - }); - - }); - - 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("unstable_config_lookup_from_file", () => { - - const flag = "unstable_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 - ); - - }); - - }); - }); - }); - + 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(() => { + sinon + .stub(process, "emitWarning") + .withArgs(sinon.match.any, "ESLintIgnoreWarning") + .returns(); + process.emitWarning.callThrough(); + }); + + 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 processStub; + + beforeEach(() => { + sinon.restore(); + processStub = sinon.stub(process, "emitWarning"); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + processStub.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.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.strictEqual( + processStub.getCall(0).args[1], + "ESLintRCWarning", + ); + } + }); + + 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(); + }); + + 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 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 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 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 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); + }); + }); + + 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("unstable_config_lookup_from_file", () => { + const flag = "unstable_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 index 6065cffa6726..b46676c7a4dd 100644 --- a/tests/lib/config/config-loader.js +++ b/tests/lib/config/config-loader.js @@ -12,7 +12,10 @@ const assert = require("node:assert"); const path = require("node:path"); const sinon = require("sinon"); -const { ConfigLoader, LegacyConfigLoader } = require("../../../lib/config/config-loader"); +const { + ConfigLoader, + LegacyConfigLoader, +} = require("../../../lib/config/config-loader"); //------------------------------------------------------------------------------ // Helpers @@ -25,194 +28,286 @@ const fixtureDir = path.resolve(__dirname, "../../fixtures"); //------------------------------------------------------------------------------ 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 configLoader = new ConfigLoaderClass({ - cwd, - ignoreEnabled: true, - configFile: "cjs/eslint.config.cjs" - }); - - const emitWarning = sinon.stub(process, "emitWarning"); - const configArray = await configLoader.loadConfigArrayForFile(path.resolve(cwd, "cjs/foo.js")); - - assert(Array.isArray(configArray), "Expected `loadConfigArrayForFile()` to return a config array"); - assert(emitWarning.called, "Expected `process.emitWarning` to be called"); - assert.strictEqual(emitWarning.args[0][1], "ESLintEmptyConfigWarning", "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument"); - }); - - it("should not error when loading an empty ESM config file", async () => { - const cwd = path.resolve(fixtureDir, "empty-config-file"); - - const configLoader = new ConfigLoaderClass({ - cwd, - ignoreEnabled: true, - configFile: "esm/eslint.config.mjs" - }); - - const emitWarning = sinon.stub(process, "emitWarning"); - const configArray = await configLoader.loadConfigArrayForFile(path.resolve(cwd, "esm/foo.js")); - - assert(Array.isArray(configArray), "Expected `loadConfigArrayForFile()` to return a config array"); - assert(emitWarning.called, "Expected `process.emitWarning` to be called"); - assert.strictEqual(emitWarning.args[0][1], "ESLintEmptyConfigWarning", "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument"); - }); - - it("should not error when loading an ESM config file with an empty array", async () => { - const cwd = path.resolve(fixtureDir, "empty-config-file"); - - const configLoader = new ConfigLoaderClass({ - cwd, - ignoreEnabled: true, - configFile: "esm/eslint.config.empty-array.mjs" - }); - - const emitWarning = sinon.stub(process, "emitWarning"); - const configArray = await configLoader.loadConfigArrayForFile(path.resolve(cwd, "mjs/foo.js")); - - assert(Array.isArray(configArray), "Expected `loadConfigArrayForFile()` to return a config array"); - assert(emitWarning.called, "Expected `process.emitWarning` to be called"); - assert.strictEqual(emitWarning.args[0][1], "ESLintEmptyConfigWarning", "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument"); - }); - - 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); - }); - }); - - }); - }); + 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 configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "cjs/eslint.config.cjs", + }); + + const emitWarning = sinon.stub(process, "emitWarning"); + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "cjs/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + emitWarning.called, + "Expected `process.emitWarning` to be called", + ); + assert.strictEqual( + emitWarning.args[0][1], + "ESLintEmptyConfigWarning", + "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument", + ); + }); + + it("should not error when loading an empty ESM config file", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.mjs", + }); + + const emitWarning = sinon.stub(process, "emitWarning"); + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "esm/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + emitWarning.called, + "Expected `process.emitWarning` to be called", + ); + assert.strictEqual( + emitWarning.args[0][1], + "ESLintEmptyConfigWarning", + "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument", + ); + }); + + it("should not error when loading an ESM config file with an empty array", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.empty-array.mjs", + }); + + const emitWarning = sinon.stub(process, "emitWarning"); + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "mjs/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + emitWarning.called, + "Expected `process.emitWarning` to be called", + ); + assert.strictEqual( + emitWarning.args[0][1], + "ESLintEmptyConfigWarning", + "Expected `process.emitWarning` to be called with 'ESLintEmptyConfigWarning' as the second argument", + ); + }); + + 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/flat-config-array.js b/tests/lib/config/flat-config-array.js index e9b1e8667497..e2e01cdd7c62 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -21,139 +21,140 @@ const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); //----------------------------------------------------------------------------- const baseConfig = { - 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: {} - } - } - } + 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: {}, + }, + }, + }, }; /** @@ -162,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], + }); } /** @@ -177,21 +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"); - if (!result.language) { - result.language = jslang; - } + if (!result.language) { + result.language = jslang; + } - if (!result.languageOptions) { - result.languageOptions = jslang.normalizeLanguageOptions(jslang.defaultLanguageOptions); - } + if (!result.languageOptions) { + result.languageOptions = jslang.normalizeLanguageOptions( + jslang.defaultLanguageOptions, + ); + } - assert.deepStrictEqual(config, result); + assert.deepStrictEqual(config, result); } /** @@ -203,13 +206,12 @@ async function assertMergedResult(values, result) { * has an unexpected message. */ async function assertInvalidConfig(values, message) { - const configs = createFlatConfigArray(values); + const configs = createFlatConfigArray(values); - - assert.throws(() => { - configs.normalizeSync(); - configs.getConfig("foo.js"); - }, message); + assert.throws(() => { + configs.normalizeSync(); + configs.getConfig("foo.js"); + }, message); } //----------------------------------------------------------------------------- @@ -217,2680 +219,2915 @@ async function assertInvalidConfig(values, message) { //----------------------------------------------------------------------------- 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"], - 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); - - }); - - }); - + 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 index e7076d045ce0..e0e9aa89862c 100644 --- a/tests/lib/config/flat-config-helpers.js +++ b/tests/lib/config/flat-config-helpers.js @@ -10,9 +10,9 @@ //----------------------------------------------------------------------------- const { - parseRuleId, - getRuleFromConfig, - getRuleOptionsSchema + parseRuleId, + getRuleFromConfig, + getRuleOptionsSchema, } = require("../../../lib/config/flat-config-helpers"); const assert = require("chai").assert; @@ -21,172 +21,167 @@ const assert = require("chai").assert; //----------------------------------------------------------------------------- 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); - - }); - }); - - describe("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 = 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 = 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 = 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 = 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 = 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 = getRuleOptionsSchema(rule); - - assert.deepStrictEqual(result, schema); - }); - - it("should return `null` if `meta.schema` is `false`", () => { - const rule = { meta: { schema: false } }; - const result = 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(() => { - 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 = getRuleOptionsSchema(rule); - - assert.deepStrictEqual(result, noOptionsSchema); - }); - }); - + 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); + }); + }); + + describe("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 = 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 = 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 = 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 = 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 = 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 = getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, schema); + }); + + it("should return `null` if `meta.schema` is `false`", () => { + const rule = { meta: { schema: false } }; + const result = 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(() => { + 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 = getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + }); }); diff --git a/tests/lib/config/flat-config-schema.js b/tests/lib/config/flat-config-schema.js index b1b7303af620..f351667736b1 100644 --- a/tests/lib/config/flat-config-schema.js +++ b/tests/lib/config/flat-config-schema.js @@ -7,7 +7,9 @@ const { flatConfigSchema } = require("../../../lib/config/flat-config-schema"); const { assert } = require("chai"); -const { Legacy: { ConfigArray } } = require("@eslint/eslintrc"); +const { + Legacy: { ConfigArray }, +} = require("@eslint/eslintrc"); /** * This function checks the result of merging two values in eslintrc config. @@ -20,314 +22,324 @@ const { Legacy: { ConfigArray } } = require("@eslint/eslintrc"); * @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); + 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); + }); - 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("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); - 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" }; + assert.deepStrictEqual(result.a, result.d); - expected.first = first; - expected.second = second; - assert.deepStrictEqual(result, expected); - }); + const expected = { + a: { foo: 42, bar: "baz" }, + b: { foo: 42, bar: "something else" }, + c: { foo: "different", bar: "baz" }, + d: { foo: 42, bar: "baz" }, + }; - 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); - }); + assert.deepStrictEqual(result, expected); + }); }); diff --git a/tests/lib/eslint/eslint.config.js b/tests/lib/eslint/eslint.config.js index a9e2b1b1efef..f8bdd00b5f8e 100644 --- a/tests/lib/eslint/eslint.config.js +++ b/tests/lib/eslint/eslint.config.js @@ -1,11 +1,11 @@ /* 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 df606a6331b4..9b65c0b06cb1 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -43,11 +43,11 @@ const espree = require("espree"); * @returns {void} */ function ensureDirectoryExists(dirPath) { - try { - fs.statSync(dirPath); - } catch { - fs.mkdirSync(dirPath); - } + try { + fs.statSync(dirPath); + } catch { + fs.mkdirSync(dirPath); + } } /** @@ -56,7 +56,7 @@ function ensureDirectoryExists(dirPath) { * @returns {Promise} */ async function sleep(time) { - await util.promisify(setTimeout)(time); + await util.promisify(setTimeout)(time); } //------------------------------------------------------------------------------ @@ -64,6174 +64,7608 @@ async function sleep(time) { //------------------------------------------------------------------------------ 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 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")); - sinon.stub(process, "emitWarning").withArgs(sinon.match.any, "ESLintIgnoreWarning").returns(); - process.emitWarning.callThrough(); - }); - - afterEach(() => { - sinon.restore(); - }); - - [ - [], - ["unstable_config_lookup_from_file"] - ].forEach(flags => { - - 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 processStub = sinon.stub(process, "emitWarning"); - - // eslint-disable-next-line no-new -- for testing purpose only - new ESLint({ cwd, flags }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.strictEqual(processStub.getCall(0).args[0], "The \".eslintignore\" file is no longer supported. Switch to using the \"ignores\" property in \"eslint.config.js\": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files"); - assert.strictEqual(processStub.getCall(0).args[1], "ESLintIgnoreWarning"); - - processStub.restore(); - }); - }); - - 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(); - }); - - 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 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 the flag is unknown", () => { - - assert.throws(() => { - eslint = new ESLint({ cwd: getFixturePath(), flags: ["foo_bar"] }); - }, /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/js/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/js/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", () => { - - 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 fail to load a TS config file if jiti is not installed", async () => { - - const { ConfigLoader } = require("../../../lib/config/config-loader"); - - 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 () => { - - const { ConfigLoader } = require("../../../lib/config/config-loader"); - - sinon.stub(ConfigLoader, "loadJiti").resolves({}); - - 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 fail to 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 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); - }); - }); - - 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/js/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/js/semi"], - info: coreRules.get("semi").meta.deprecated - }, - { - ruleId: "quotes", - replacedBy: ["@stylistic/js/quotes"], - info: coreRules.get("quotes").meta.deprecated - }, - { - ruleId: "space-infix-ops", - replacedBy: ["@stylistic/js/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/js/semi"], - info: coreRules.get("semi").meta.deprecated - }, - { - ruleId: "quotes", - replacedBy: ["@stylistic/js/quotes"], - info: coreRules.get("quotes").meta.deprecated - }, - { - ruleId: "space-infix-ops", - replacedBy: ["@stylistic/js/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 - } - ], - 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/js/semi"], - info: coreRules.get("semi").meta.deprecated - }, - { - ruleId: "quotes", - replacedBy: ["@stylistic/js/quotes"], - info: coreRules.get("quotes").meta.deprecated - }, - { - ruleId: "space-infix-ops", - replacedBy: ["@stylistic/js/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/js/semi"], - info: coreRules.get("semi").meta.deprecated - }, - { - ruleId: "quotes", - replacedBy: ["@stylistic/js/quotes"], - info: coreRules.get("quotes").meta.deprecated - }, - { - ruleId: "space-infix-ops", - replacedBy: ["@stylistic/js/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" - } - ];` - } - }); - - 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" - } - ];` - } - }); - - 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") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - } - ];` - } - }); - - 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 = [ - { - 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 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: ["**/*.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 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 = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - 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); - - 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 fail to load a TS config file if jiti is not installed", async () => { - - const { ConfigLoader } = require("../../../lib/config/config-loader"); - - 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 () => { - - const { ConfigLoader } = require("../../../lib/config/config-loader"); - - sinon.stub(ConfigLoader, "loadJiti").resolves({}); - - 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 fail to 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"); + 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")); + sinon + .stub(process, "emitWarning") + .withArgs(sinon.match.any, "ESLintIgnoreWarning") + .returns(); + process.emitWarning.callThrough(); + }); + + afterEach(() => { + sinon.restore(); + }); + + [[], ["unstable_config_lookup_from_file"]].forEach(flags => { + 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 processStub = sinon.stub(process, "emitWarning"); + + // eslint-disable-next-line no-new -- for testing purpose only + new ESLint({ cwd, flags }); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.strictEqual( + processStub.getCall(0).args[0], + 'The ".eslintignore" file is no longer supported. Switch to using the "ignores" property in "eslint.config.js": https://eslint.org/docs/latest/use/configure/migration-guide#ignoring-files', + ); + assert.strictEqual( + processStub.getCall(0).args[1], + "ESLintIgnoreWarning", + ); + + processStub.restore(); + }); + }); + + 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(); + }); + + 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 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 the flag is unknown", () => { + assert.throws(() => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["foo_bar"], + }); + }, /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/js/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/js/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", () => { + 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 fail to load a TS config file if jiti is not installed", async () => { + const { + ConfigLoader, + } = require("../../../lib/config/config-loader"); + + 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 () => { + const { + ConfigLoader, + } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").resolves({}); + + 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 fail to 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 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); + }); + }); + + 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/js/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/js/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/js/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: [ + "@stylistic/js/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/js/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/js/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: [ + "@stylistic/js/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, + }, + ], + 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/js/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/js/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: [ + "@stylistic/js/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/js/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/js/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: [ + "@stylistic/js/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 stop linting files if a rule crashes", async () => { + 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" + } + ];`, + }, + }); + + 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" + } + ];`, + }, + }); + + 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") + } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" + } + ];`, + }, + }); + + 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 = [ + { + 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" + } + } - const cwd = getFixturePath("files"); - let createCallCount = 0; + ];`, + }, + }); + + 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: ["**/*.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" + } + } - eslint = new ESLint({ - flags, - cwd, - plugins: { - boom: { - rules: { - boom: { - create() { - createCallCount++; - throw Error("Boom!"); + ];`, + }, + }); + + 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 = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") } + }, + { + files: ["**/*.md"], + processor: "markdown/unknown" } - } - } - }, - 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": ` + + ];`, + }, + }); + + 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, + ); + + 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 fail to load a TS config file if jiti is not installed", async () => { + const { + ConfigLoader, + } = require("../../../lib/config/config-loader"); + + 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 () => { + const { + ConfigLoader, + } = require("../../../lib/config/config-loader"); + + sinon.stub(ConfigLoader, "loadJiti").resolves({}); + + 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 fail to 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 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({ @@ -6258,1356 +7692,1893 @@ describe("ESLint", () => { }); eslint.lintFiles("passing.js").catch(() => { }); - ` - } - }); - - 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("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 = [ + `, + }, + }); + + 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("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 = [ { rules: { "no-unused-vars": "error", @@ -7620,69 +9591,93 @@ describe("ESLint", () => { }, }, ];`, - "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 = [ + "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 = [ { rules: { "no-undef": "error", @@ -7695,540 +9690,598 @@ describe("ESLint", () => { }, }, ];`, - "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 = { + "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 = { + "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 = { + "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 = { + "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 = { + "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 = [{ + "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 = [{ + "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 = [ + "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/**/*.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({ 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 = [ + "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/**/*"] } ]`, - "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 = [ + "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"], rules: { @@ -8236,91 +10289,100 @@ describe("ESLint", () => { } } ];`, - "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 - } - ], - 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 = [ + "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, + }, + ], + 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: ["**/*"], ignores: ["foo/*.js"], @@ -8329,1966 +10391,2548 @@ describe("ESLint", () => { } } ]`, - "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 - } - ], - 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 = [{ + "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, + }, + ], + 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 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 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 ESLint(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 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("unstable_config_lookup_from_file", () => { - - let eslint; - const flags = ["unstable_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); - }); - - }); - - }); + "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 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 ESLint(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 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("unstable_config_lookup_from_file", () => { + let eslint; + const flags = ["unstable_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); + }); + }); + }); }); diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js index e53873cfc2c3..8ca69be240a3 100644 --- a/tests/lib/eslint/legacy-eslint.js +++ b/tests/lib/eslint/legacy-eslint.js @@ -20,9 +20,7 @@ const sinon = require("sinon"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const shell = require("shelljs"); const { - Legacy: { - CascadingConfigArrayFactory - } + Legacy: { CascadingConfigArrayFactory }, } = require("@eslint/eslintrc"); const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); @@ -34,3973 +32,5033 @@ const childProcess = require("node:child_process"); //------------------------------------------------------------------------------ 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 - } - ], - 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/us); - }); - - 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/us); - }); - - 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/us); - }); - - 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/us); - }); - - 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/us); - }); - }); - - 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": ` + 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, + }, + ], + 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"] } }; @@ -4011,38 +5069,36 @@ describe("LegacyESLint", () => { } }; `, - ".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": ` + ".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" }, @@ -4062,43 +5118,48 @@ describe("LegacyESLint", () => { } }; `, - ".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": ` + ".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 = { @@ -4106,7 +5167,7 @@ describe("LegacyESLint", () => { "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(); - 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` + `, + }; + + 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 */""} + 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` + `, + ); + }); + + 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 */""} + 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` + `, + ); + }); + + 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 */""} + 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": ` + `, + ); + }); + + 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 { @@ -4561,3107 +5674,3736 @@ describe("LegacyESLint", () => { }, }; `, - ".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 - } - ], - 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 - } - ], - 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); - }); - }); - } + ".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, + }, + ], + 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, + }, + ], + 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 index 79019a5afe18..058669ba618c 100644 --- a/tests/lib/languages/js/source-code/source-code.js +++ b/tests/lib/languages/js/source-code/source-code.js @@ -9,31 +9,31 @@ //------------------------------------------------------------------------------ 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"); + 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 + 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}`; + TEST_CODE = "var answer = 6 * 7;", + SHEBANG_TEST_CODE = `#!/usr/bin/env node\n${TEST_CODE}`; const filename = "foo.js"; /** @@ -44,3237 +44,3566 @@ const filename = "foo.js"; * @private */ function getVariable(scope, name) { - return scope.variables.find(v => v.name === name) || null; + 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); - 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(5), { line: 2, column: 1 }); - assert.deepStrictEqual(sourceCode.getLocFromIndex(3), { line: 1, column: 3 }); - assert.deepStrictEqual(sourceCode.getLocFromIndex(4), { line: 2, column: 0 }); - 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({ 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: 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); + const shebangToken = sourceCode.getAllComments()[0]; + const shebangText = sourceCode.getText(shebangToken); - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); + assert.strictEqual(shebangToken.type, "Shebang"); + assert.strictEqual(shebangText, "#!/usr/bin/env node"); + }); + }); - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; - } - }); + beforeEach(() => { + ast = espree.parse(TEST_CODE, DEFAULT_CONFIG); + sourceCode = new SourceCode(TEST_CODE, ast); + }); - 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 }); + it("should retrieve all text when used without parameters", () => { + const text = sourceCode.getText(); - sourceCode.applyInlineConfig(); - sourceCode.finalize(); + assert.strictEqual(text, TEST_CODE); + }); - const globalScope = sourceCode.scopeManager.scopes[0]; - const variable = globalScope.set.get("bar"); + it("should retrieve all text for root node", () => { + const text = sourceCode.getText(ast); - assert.isDefined(variable); - assert.isTrue(variable.writeable); - }); + assert.strictEqual(text, TEST_CODE); + }); - describe("exported variables", () => { + it("should clamp to valid range when retrieving characters before start of source", () => { + const text = sourceCode.getText(ast, 2, 0); - /** - * 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 }); + assert.strictEqual(text, TEST_CODE); + }); - sourceCode.applyInlineConfig(); - sourceCode.finalize(); + it("should retrieve all text for binary expression", () => { + const node = ast.body[0].declarations[0].init; + const text = sourceCode.getText(node); - 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); - }); - }); + 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(5), { + line: 2, + column: 1, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(3), { + line: 1, + column: 3, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(4), { + line: 2, + column: 0, + }); + 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({ + 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: 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); + }); + }); }); diff --git a/tests/lib/languages/js/source-code/token-store.js b/tests/lib/languages/js/source-code/token-store.js index 043938aa2f70..57cfeb22f654 100644 --- a/tests/lib/languages/js/source-code/token-store.js +++ b/tests/lib/languages/js/source-code/token-store.js @@ -10,22 +10,28 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - espree = require("espree"), - TokenStore = require("../../../../../lib/languages/js/source-code/token-store"); + 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; +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 @@ -38,12 +44,12 @@ const SOURCE_CODE = "/*A*/var answer/*B*/=/*C*/a/*D*/* b/*E*///F\n call();\n/ * @returns {void} */ function check(tokens, expected) { - const length = tokens.length; + const length = tokens.length; - assert.strictEqual(length, expected.length); - for (let i = 0; i < length; i++) { - assert.strictEqual(tokens[i].value, expected[i]); - } + assert.strictEqual(length, expected.length); + for (let i = 0; i < length; i++) { + assert.strictEqual(tokens[i].value, expected[i]); + } } //------------------------------------------------------------------------------ @@ -51,1421 +57,1784 @@ function check(tokens, expected) { //------------------------------------------------------------------------------ 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]), - [] - ); - }); - }); + 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 b0ed6a99da51..6aabf84fab48 100644 --- a/tests/lib/linter/apply-disable-directives.js +++ b/tests/lib/linter/apply-disable-directives.js @@ -25,32 +25,32 @@ const jslang = require("../../../lib/languages/js"); * @returns {ParentDirective} Test-ready ParentDirective object. */ function createParentDirective(range, value, ruleIds = []) { - return { - node: { - range, - loc: { - start: { - line: 1, - column: 1 - }, - end: { - line: 1, - column: range[1] + 1 - } - } - }, - 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; - } + getRange(node) { + return node.range; + }, + getLoc(node) { + return node.loc; + }, }; //------------------------------------------------------------------------------ @@ -58,3450 +58,4181 @@ const sourceCode = { //------------------------------------------------------------------------------ describe("apply-disable-directives", () => { - 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 - } - ] - ); - }); - }); + 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 dde4bef4198e..85cad16a9735 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -10,24 +10,27 @@ //------------------------------------------------------------------------------ const assert = require("node:assert"), - fs = require("node:fs"), - path = require("node: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"); + fs = require("node:fs"), + path = require("node: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"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys }; +const STANDARD_ESQUERY_OPTION = { + visitorKeys: vk.KEYS, + fallback: Traverser.getKeys, +}; const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu; const lineEndingPattern = /\r?\n/gu; @@ -40,16 +43,16 @@ const linter = new Linter({ configType: "eslintrc" }); * @returns {string[]} DOT arrows. */ function getExpectedDotArrows(source) { - expectedPattern.lastIndex = 0; + expectedPattern.lastIndex = 0; - const retv = []; - let m; + const retv = []; + let m; - while ((m = expectedPattern.exec(source)) !== null) { - retv.push(m[1].replace(lineEndingPattern, "\n")); - } + while ((m = expectedPattern.exec(source)) !== null) { + retv.push(m[1].replace(lineEndingPattern, "\n")); + } - return retv; + return retv; } //------------------------------------------------------------------------------ @@ -57,674 +60,688 @@ 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); - }); - - }); - - 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]); - } - }); - }); - }); + 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); + }); + }); + + 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]); + } + }); + }); + }); }); diff --git a/tests/lib/linter/code-path-analysis/code-path.js b/tests/lib/linter/code-path-analysis/code-path.js index aefc913aa459..3ddf1da45c24 100644 --- a/tests/lib/linter/code-path-analysis/code-path.js +++ b/tests/lib/linter/code-path-analysis/code-path.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("node:assert"), - { Linter } = require("../../../../lib/linter"); + { Linter } = require("../../../../lib/linter"); const linter = new Linter({ configType: "eslintrc" }); //------------------------------------------------------------------------------ @@ -23,22 +23,22 @@ const linter = new Linter({ configType: "eslintrc" }); * @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.defineRule("test", { + create: () => ({ + onCodePathStart(codePath) { + retv.push(codePath); + }, + }), + }); + + linter.verify(code, { + rules: { test: 2 }, + parserOptions: { ecmaVersion: "latest" }, + }); + + return retv; } /** @@ -51,16 +51,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 +68,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 +134,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 +157,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 +190,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 +210,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 +239,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 +270,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 +294,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 +325,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 +360,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,19 +395,31 @@ 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"]); - - /* + }); + + 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]; @@ -382,20 +435,24 @@ describe("CodePathAnalyzer", () => { 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]; + 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(); - } - }); + const order = getOrderOfTraversing( + codePath, + null, + (segment, controller) => { + if (segment.id === "s1_1") { + controller.skip(); + } + }, + ); - assert.deepStrictEqual(order, ["s1_1"]); + 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]; @@ -407,7 +464,7 @@ describe("CodePathAnalyzer", () => { initial->s1_1->s1_2->s1_3->s1_2->s1_4->final; } */ - }); - /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ - }); + }); + /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ + }); }); diff --git a/tests/lib/linter/interpolate.js b/tests/lib/linter/interpolate.js index 9c96d09117ba..bfaff9c90c23 100644 --- a/tests/lib/linter/interpolate.js +++ b/tests/lib/linter/interpolate.js @@ -5,54 +5,60 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert; -const { getPlaceholderMatcher, interpolate } = require("../../../lib/linter/interpolate"); +const { + getPlaceholderMatcher, + interpolate, +} = require("../../../lib/linter/interpolate"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("getPlaceholderMatcher", () => { - it("returns a global regular expression", () => { - const matcher = getPlaceholderMatcher(); + it("returns a global regular expression", () => { + const matcher = getPlaceholderMatcher(); - assert.strictEqual(matcher.global, true); - }); + assert.strictEqual(matcher.global, true); + }); - it("matches text with placeholders", () => { - const matcher = getPlaceholderMatcher(); + it("matches text with placeholders", () => { + const matcher = getPlaceholderMatcher(); - assert.match("{{ placeholder }}", matcher); - }); + assert.match("{{ placeholder }}", matcher); + }); - it("does not match text without placeholders", () => { - const matcher = getPlaceholderMatcher(); + it("does not match text without placeholders", () => { + const matcher = getPlaceholderMatcher(); - assert.notMatch("no placeholders in sight", matcher); - }); + 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)); + 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 "]]); - }); + 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 82667353a946..b74cb3c33d20 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -10,10 +10,10 @@ //------------------------------------------------------------------------------ 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"); @@ -31,7 +31,7 @@ const jsonPlugin = jsonPluginPackage.default || jsonPluginPackage; //------------------------------------------------------------------------------ const TEST_CODE = "var answer = 6 * 7;", - BROKEN_TEST_CODE = "var;"; + BROKEN_TEST_CODE = "var;"; //------------------------------------------------------------------------------ // Helpers @@ -45,7 +45,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; } /** @@ -61,5115 +61,5369 @@ const ESLINT_ENV = "eslint-env"; //------------------------------------------------------------------------------ describe("Linter", () => { - const filename = "filename.js"; - - /** @type {InstanceType} */ - let linter; - - beforeEach(() => { - linter = new Linter({ configType: "eslintrc" }); - }); - - afterEach(() => { - sinon.verifyAndRestore(); - }); + const filename = "filename.js"; + + /** @type {InstanceType} */ + let linter; + + beforeEach(() => { + linter = new Linter({ configType: "eslintrc" }); + }); + + 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); + }); - 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({}) - }); + it("should be configurable by passing in string value as an array", () => { + const rule = "semi", + config = { rules: {} }; - const config = { rules: { "test-rule": 2 }, parser: alternateParser }; + config.rules[rule] = ["warn"]; - linter.verify("0", config, filename); - }); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - it("should use parseForESLint() in custom parser when custom parser is specified", () => { - const config = { rules: {}, parser: "enhanced-parser" }; + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].ruleId, rule); - linter.defineParser("enhanced-parser", testParsers.enhancedParser); - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not be configurable by setting other value", () => { + const rule = "semi", + config = { rules: {} }; - 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() - }); - } - }) - }); + config.rules[rule] = "1"; - const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; - const messages = linter.verify("0", 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].message, "Hi!"); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(messages.length, 0); + 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() - }); - } - }) - }); + it("should process empty config", () => { + const config = {}; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - 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, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); - 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: {} }; - - 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"; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - 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("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(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` + 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.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 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(); - it("should report a violation when using Promise", () => { - const code = "new Promise();"; + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); - const config = { rules: { "no-undef": 1 } }; + describe("when evaluating code with comments to environment", () => { + it("should not support legacy config", () => { + const code = "/*jshint mocha:true */ describe();"; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + const config = { rules: { "no-undef": 1 } }; - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - describe("when evaluating code with comments to environment", () => { - it("should not support legacy config", () => { - const code = "/*jshint mocha:true */ describe();"; + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-undef"); + assert.strictEqual(messages[0].nodeType, "Identifier"); + assert.strictEqual(messages[0].line, 1); - const config = { rules: { "no-undef": 1 } }; + assert.strictEqual(suppressedMessages.length, 0); + }); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + it("should not report a violation", () => { + const code = "/*eslint-env es6 */ new Promise();"; - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-undef"); - assert.strictEqual(messages[0].nodeType, "Identifier"); - assert.strictEqual(messages[0].line, 1); + const config = { rules: { "no-undef": 1 } }; - assert.strictEqual(suppressedMessages.length, 0); - }); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - it("should not report a violation", () => { - const code = "/*eslint-env es6 */ new Promise();"; + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - 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 } }; - 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); + } + }); - // 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 } }; + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`; - for (const code of codes) { - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + const config = { rules: { "no-undef": 1 } }; - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - } + 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", () => { - const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`; + 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 mocha */ suite();test();"; + 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} amd */ define();require();`; + 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 = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`; + 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 = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`; + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} node */ process.exit();`; - const config = { rules: { "no-undef": 1 } }; + 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_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: {} }; - - 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", () => { - 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(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: ` + 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, @@ -5177,15 +5431,15 @@ var a = "test2"; used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5193,15 +5447,15 @@ var a = "test2"; unused-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5209,15 +5463,15 @@ var a = "test2"; unused-2, */ `, - output: ` + output: ` /* eslint-disable used-1, used-2, */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,unused-1 ,used-1 @@ -5225,15 +5479,15 @@ var a = "test2"; ,used-2 */ `, - output: ` + output: ` /* eslint-disable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,used-1 ,unused-1 @@ -5241,15 +5495,15 @@ var a = "test2"; ,unused-2 */ `, - output: ` + output: ` /* eslint-disable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5259,17 +5513,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, @@ -5278,16 +5532,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, @@ -5296,16 +5550,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 @@ -5315,17 +5569,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 @@ -5335,17 +5589,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 @@ -5355,17 +5609,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 @@ -5375,17 +5629,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 @@ -5395,17 +5649,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 @@ -5417,7 +5671,7 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5426,10252 +5680,12336 @@ 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.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("let x = /(?a)|(?b)/;", { - 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(` + `, + }, + + // 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.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("let x = /(?a)|(?b)/;", { + 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, - 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(` + `, + { 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("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("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); - }); - }); + `, + {}, + ); + 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", () => { + let processStub; + + beforeEach(() => { + // in the browser test, `process.emitWarning` is not defined + if ( + typeof process !== "undefined" && + typeof process.emitWarning !== "undefined" + ) { + processStub = sinon + .stub(process, "emitWarning") + .withArgs( + sinon.match.any, + sinon.match("ESLintCircularFixesWarning"), + ) + .returns(); + } + }); + + afterEach(() => { + sinon.restore(); + }); + + 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 + if (processStub) { + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "Circular fixes detected while fixing test.js. It is likely that you have conflicting rules in your configuration.", + "ESLintCircularFixesWarning", + ]); + } + + 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. - * @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(() => { - 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("hasFlag()", () => { - - let processStub; - - beforeEach(() => { - - // in the browser test, `process.emitWarning` is not defined - if (typeof process !== "undefined" && typeof process.emitWarning !== "undefined") { - processStub = sinon.stub(process, "emitWarning").withArgs(sinon.match.any, sinon.match(/^ESLintInactiveFlag_/u)).returns(); - } - }); - - afterEach(() => { - sinon.restore(); - }); - - 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"] }).hasFlag("test_only"), - true - ); - - if (processStub) { - 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", () => { - assert.strictEqual( - new Linter({ configType: "flat", flags: ["test_only_enabled_by_default"] }).hasFlag("test_only_enabled_by_default"), - false - ); - - if (processStub) { - 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-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("let x = /(?a)|(?b)/;"); // ECMAScript 2025 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( - 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(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 = ` + 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. + * @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(() => { + 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("hasFlag()", () => { + let processStub; + + beforeEach(() => { + // in the browser test, `process.emitWarning` is not defined + if ( + typeof process !== "undefined" && + typeof process.emitWarning !== "undefined" + ) { + processStub = sinon + .stub(process, "emitWarning") + .withArgs( + sinon.match.any, + sinon.match(/^ESLintInactiveFlag_/u), + ) + .returns(); + } + }); + + afterEach(() => { + sinon.restore(); + }); + + 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"], + }).hasFlag("test_only"), + true, + ); + + if (processStub) { + 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", () => { + assert.strictEqual( + new Linter({ + configType: "flat", + flags: ["test_only_enabled_by_default"], + }).hasFlag("test_only_enabled_by_default"), + false, + ); + + if (processStub) { + 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-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( + "let x = /(?a)|(?b)/;", + ); // ECMAScript 2025 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(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` + 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 }, 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 = ` + 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 - }, - 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(` + 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, - 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(` + `, + { + 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" }, 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(` + `, + { + 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" }, 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(` + `, + { + 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" }, 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(` + `, + { + 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" }, 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(` + `, + { + 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" }, 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(` + `, + { + 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(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 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 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 = ` /* 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, 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: ` + 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, @@ -15679,15 +18017,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, @@ -15695,15 +18033,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, @@ -15711,15 +18049,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 @@ -15727,15 +18065,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 @@ -15743,15 +18081,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, @@ -15761,17 +18099,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, @@ -15780,16 +18118,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 @@ -15799,17 +18137,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 @@ -15819,17 +18157,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 @@ -15839,17 +18177,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 @@ -15859,17 +18197,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 @@ -15879,17 +18217,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 @@ -15901,7 +18239,7 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -15910,1878 +18248,2323 @@ 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(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("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; - - // 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 - ); - }); - }); - - }); - - 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 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); - }); - - }); - - }); - + `, + }, + + // 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", () => { + let processStub; + + beforeEach(() => { + // in the browser test, `process.emitWarning` is not defined + if ( + typeof process !== "undefined" && + typeof process.emitWarning !== "undefined" + ) { + processStub = sinon + .stub(process, "emitWarning") + .withArgs( + sinon.match.any, + sinon.match("ESLintCircularFixesWarning"), + ) + .returns(); + } + }); + + afterEach(() => { + sinon.restore(); + }); + + 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 + if (processStub) { + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "Circular fixes detected while fixing test.js. It is likely that you have conflicting rules in your configuration.", + "ESLintCircularFixesWarning", + ]); + } + + 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; + + // 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); + }); + }); + }); + + 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 index d741c137f64a..b1c8355c49aa 100644 --- a/tests/lib/linter/node-event-generator.js +++ b/tests/lib/linter/node-event-generator.js @@ -9,465 +9,532 @@ //------------------------------------------------------------------------------ const assert = require("node: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"); - + 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 + ecmaVersion: 6, + comment: true, + tokens: true, + range: true, + loc: true, }; -const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys }; +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 dummyParent = { type: "Foo" }; - const dummyNode = { type: "Bar" }; - - generator.enterNode(dummyParent); - generator.enterNode(dummyNode); - - assert(emitter.emit.calledThrice); - assert(emitter.emit.calledWith("Foo", dummyParent)); - assert(emitter.emit.calledWith("Foo > Bar", dummyNode)); - assert(emitter.emit.calledWith("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) { - 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) { - 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 - ); - }); - }); + 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 dummyParent = { type: "Foo" }; + const dummyNode = { type: "Bar" }; + + generator.enterNode(dummyParent); + generator.enterNode(dummyNode); + + assert(emitter.emit.calledThrice); + assert(emitter.emit.calledWith("Foo", dummyParent)); + assert(emitter.emit.calledWith("Foo > Bar", dummyNode)); + assert(emitter.emit.calledWith("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) { + 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) { + 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 5ed1dec01b2b..fd075e906626 100644 --- a/tests/lib/linter/report-translator.js +++ b/tests/lib/linter/report-translator.js @@ -19,1259 +19,1384 @@ const jslang = require("../../../lib/languages/js"); //------------------------------------------------------------------------------ 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({ - 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); - }); - }); + /** + * 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 403527811501..d4bedc75772f 100644 --- a/tests/lib/linter/rule-fixer.js +++ b/tests/lib/linter/rule-fixer.js @@ -9,192 +9,149 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - { RuleFixer } = require("../../../lib/linter/rule-fixer"); + { RuleFixer } = require("../../../lib/linter/rule-fixer"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("RuleFixer", () => { - - 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" - }); - - }); - - }); - + 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 0c4b5a26d4ee..b617974ff1c9 100644 --- a/tests/lib/linter/rules.js +++ b/tests/lib/linter/rules.js @@ -10,65 +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-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")); - }); - }); + 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 index 0808b4c84e82..310d1860a93a 100644 --- a/tests/lib/linter/safe-emitter.js +++ b/tests/lib/linter/safe-emitter.js @@ -17,35 +17,35 @@ const assert = require("chai").assert; //------------------------------------------------------------------------------ describe("safe-emitter", () => { - describe("emit() and on()", () => { - it("allows listeners to be registered calls them when emitted", () => { - const emitter = createEmitter(); - const colors = []; + 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.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.emit("foo"); + assert.deepStrictEqual(colors, ["red", "blue"]); - emitter.on("bar", color => colors.push(color)); - emitter.emit("bar", "yellow"); + emitter.on("bar", color => colors.push(color)); + emitter.emit("bar", "yellow"); - assert.deepStrictEqual(colors, ["red", "blue", "green", "yellow"]); - }); + assert.deepStrictEqual(colors, ["red", "blue", "green", "yellow"]); + }); - it("calls listeners with no `this` value", () => { - const emitter = createEmitter(); - let called = false; + 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.on("foo", function () { + assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this -- Checking `this` value + called = true; + }); - emitter.emit("foo"); - assert(called); - }); - }); + emitter.emit("foo"); + assert(called); + }); + }); }); diff --git a/tests/lib/linter/source-code-fixer.js b/tests/lib/linter/source-code-fixer.js index 8154b69e12ee..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,653 +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: " " - } - }, - UNDEFINED_FIX = { - message: "undefined", - fix: void 0 - }, - NULL_FIX = { - message: "null", - fix: null - }; + 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); - }); - - }); - - 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); - }); - - }); - - }); - + 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/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 index 10f13b4815db..55ea3ed3c17a 100644 --- a/tests/lib/linter/vfile.js +++ b/tests/lib/linter/vfile.js @@ -17,63 +17,61 @@ const assert = require("chai").assert; //------------------------------------------------------------------------------ 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); - }); - - }); - + 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 f137e79807ce..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,442 +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 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"]); - }); - }); - + 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/no-test-runners.js b/tests/lib/rule-tester/no-test-runners.js index 4c59dd0ee154..6260ea331439 100644 --- a/tests/lib/rule-tester/no-test-runners.js +++ b/tests/lib/rule-tester/no-test-runners.js @@ -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 1d59b83351d8..52dafeb155b9 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -8,10 +8,10 @@ // Requirements //------------------------------------------------------------------------------ const sinon = require("sinon"), - EventEmitter = require("node:events"), - { RuleTester } = require("../../../lib/rule-tester"), - assert = require("chai").assert, - nodeAssert = require("node:assert"); + EventEmitter = require("node:events"), + { RuleTester } = require("../../../lib/rule-tester"), + assert = require("chai").assert, + nodeAssert = require("node:assert"); const jsonPlugin = require("@eslint/json").default; @@ -20,12 +20,12 @@ const jsonPlugin = require("@eslint/json").default; //----------------------------------------------------------------------------- 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"); })(); /** @@ -36,13 +36,13 @@ const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => { * for the actual and expected input. */ function assertErrorMatches(actual, expected) { - const err = new nodeAssert.AssertionError({ - actual, - expected, - operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR - }); + const err = new nodeAssert.AssertionError({ + actual, + expected, + operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR, + }); - return err.message; + return err.message; } /** @@ -50,8 +50,7 @@ function assertErrorMatches(actual, expected) { * @returns {void} */ function noop() { - - // do nothing. + // do nothing. } //------------------------------------------------------------------------------ @@ -79,3689 +78,5093 @@ const ruleTesterTestEmitter = new EventEmitter(); //------------------------------------------------------------------------------ describe("RuleTester", () => { - - 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 }] } - ] - }); - }, /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 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/us); - }); - - 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/us); - }); - - 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/us); - }); - - 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/us); - }); - - 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" }] }] - }); - }, /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'.\""); - }); - - 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); - }); - - // https://github.com/eslint/eslint/issues/17962 - it("should not throw an error in case of absolute paths", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { - code: "eval(foo)", - filename: "/an-absolute-path/foo.js", - errors: [{ message: "eval sucks.", type: "CallExpression" }] - }, - { - code: "eval(bar)", - filename: "C:\\an-absolute-path\\foo.js", - errors: [{ message: "eval sucks.", type: "CallExpression" }] - } - ] - }); - }); - - 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 = ` + 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, + }, + ], + }, + ], + }, + ); + }, /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 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("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 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 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"); - }); - }); + 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 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 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 91a902d87f62..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/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 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 - }] - }, + { + 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 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 - }] - } - ] + // 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 9b6a9e1ce9ff..29735810f7b0 100644 --- a/tests/lib/rules/array-bracket-newline.js +++ b/tests/lib/rules/array-bracket-newline.js @@ -12,7 +12,6 @@ const rule = require("../../../lib/rules/array-bracket-newline"); const RuleTester = require("../../../lib/rule-tester/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -20,55 +19,53 @@ const RuleTester = require("../../../lib/rule-tester/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,1915 +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 = [ /* 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 003e7a6f2ddf..8e086391cdbf 100644 --- a/tests/lib/rules/array-bracket-spacing.js +++ b/tests/lib/rules/array-bracket-spacing.js @@ -9,8 +9,8 @@ //------------------------------------------------------------------------------ const path = require("node:path"), - rule = require("../../../lib/rules/array-bracket-spacing"), - RuleTester = require("../../../lib/rule-tester/rule-tester"); + rule = require("../../../lib/rules/array-bracket-spacing"), + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -22,7 +22,12 @@ const path = require("node: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 9bb9334391e9..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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -27,663 +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" }, 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" } }] }, + { + 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.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" } }] }, + // 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, - 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 - }] - }, - - // 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 17fad6ab55b5..f807e17ab039 100644 --- a/tests/lib/rules/array-element-newline.js +++ b/tests/lib/rules/array-element-newline.js @@ -12,7 +12,6 @@ const rule = require("../../../lib/rules/array-element-newline"); const RuleTester = require("../../../lib/rule-tester/rule-tester"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ @@ -20,954 +19,1045 @@ const RuleTester = require("../../../lib/rule-tester/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 43c3ea4afae2..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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,729 +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 = () => { 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: ` + 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, @@ -748,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, @@ -808,7 +851,7 @@ ruleTester.run("arrow-body-style", rule, { } )); `, - output: ` + output: ` parsedYears = _map(years, (year) => { return { index : year, @@ -816,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 8427cd8e6e98..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/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 14b5664fc59a..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/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 03095faebcef..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/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 5454e8692f26..db87cbc9c78f 100644 --- a/tests/lib/rules/block-spacing.js +++ b/tests/lib/rules/block-spacing.js @@ -19,1147 +19,1384 @@ const RuleTester = require("../../../lib/rule-tester/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 bf6f31d89c10..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/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 2abf8b3b287b..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/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 df50278a94cd..ba0edbceb66b 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -10,417 +10,470 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/camelcase"), - RuleTester = require("../../../lib/rule-tester/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: "__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 } - }, + { + 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 }; @@ -433,1121 +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 } - }, + options: [{ properties: "never", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + }, - // 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: { 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" - } - ] - }, + // 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", + }, + ], + }, - // 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" } }] - }, + // 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" } }, + ], + }, - // 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" } }] - }, + // 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: ` + // 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 }; @@ -1560,60 +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" - } - ] - } - ] + // 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 09fa95c66788..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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,930 +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 - }] - }, - { - code: "// ęŽŗęŽƒę­š", - output: "// áŖęŽƒę­š", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* đŗĄđŗĄđŗĄ */", // right-to-left-text - output: "/* đ˛ĄđŗĄđŗĄ */", - 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 - }] - }, - { - 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 - }] - }, + // 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 fb423d9ca260..6f50bf584f2a 100644 --- a/tests/lib/rules/class-methods-use-this.js +++ b/tests/lib/rules/class-methods-use-this.js @@ -19,198 +19,685 @@ const RuleTester = require("../../../lib/rule-tester/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 { foo = () => {super.toString} }", + "class A { static foo = function() {} }", + "class A { static foo = () => {} }", + { + code: "class A { #bar() {} }", + options: [{ exceptMethods: ["#bar"] }], + }, + { + code: "class A { foo = function () {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class A { foo = () => {} }", + options: [{ enforceForClassFields: false }], + }, + "class A { foo() { return class { [this.foo] = 1 }; } }", + "class A { static {} }", + ], + 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 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", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index 9f0140f669c0..b2282f040ff3 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -10,9 +10,9 @@ //------------------------------------------------------------------------------ const path = require("node:path"), - { unIndent } = require("../../_utils"), - rule = require("../../../lib/rules/comma-dangle"), - RuleTester = require("../../../lib/rule-tester/rule-tester"); + { unIndent } = require("../../_utils"), + rule = require("../../../lib/rules/comma-dangle"), + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -24,10 +24,12 @@ const path = require("node: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 7bf5abc20302..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/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 907dc71b169a..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/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 412bfeb05a58..28a3de8daa7b 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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -23,15 +23,15 @@ const rule = require("../../../lib/rules/complexity"), * @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,570 +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 }, + }, - // 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" }] }, + // 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 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 } }, + // 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 }, + }, - // 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 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 }, + }, - // object property options - { code: "function b(x) {}", options: [{ max: 1 }] }, + // object property options + { code: "function b(x) {}", options: [{ max: 1 }] }, - // optional chaining - { - code: "function a(b) { b?.c; }", options: [{ max: 2 }] - }, + // optional chaining + { + code: "function a(b) { b?.c; }", + options: [{ max: 2 }], + }, - // default function parameter values - { - code: "function a(b = '') {}", options: [{ max: 2 }] - }, + // 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)] - }, + // 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)] }, + // 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 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 - } - ] - }, + // 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)] }, + // 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)] - }, + // 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 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)] - } - ] + // 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 1b67f39d8276..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/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 39d31659ebd7..bf82bce373f1 100644 --- a/tests/lib/rules/consistent-return.js +++ b/tests/lib/rules/consistent-return.js @@ -9,356 +9,392 @@ // Requirements //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/consistent-return"), - RuleTester = require("../../../lib/rule-tester/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(); }", - "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 } } } }, + // 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 589a9fa97644..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/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,41 +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" }] }, - { code: "var self; (function() { self = this; }())", options: ["self"], languageOptions: { ecmaVersion: 6, sourceType: "module" }, 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 ed61640e81f3..3527979537ca 100644 --- a/tests/lib/rules/constructor-super.js +++ b/tests/lib/rules/constructor-super.js @@ -19,112 +19,111 @@ const RuleTester = require("../../../lib/rule-tester/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(); } } }", - "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 - ` + 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); @@ -139,10 +138,10 @@ ruleTester.run("constructor-super", rule, { } `, - // Optional chaining - "class A extends obj?.prop { constructor() { super(); } }", + // Optional chaining + "class A extends obj?.prop { constructor() { super(); } }", - ` + ` class A extends Base { constructor(list) { for (const a of list) { @@ -154,179 +153,209 @@ ruleTester.run("constructor-super", rule, { 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 { + `, + ], + 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 { + { + code: `class C extends D { constructor() { do { @@ -335,10 +364,10 @@ ruleTester.run("constructor-super", rule, { } }`, - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: `class C extends D { + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: `class C extends D { constructor() { for (let i = 1;;i++) { @@ -349,10 +378,10 @@ ruleTester.run("constructor-super", rule, { } }`, - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: `class C extends D { + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: `class C extends D { constructor() { do { @@ -361,10 +390,10 @@ ruleTester.run("constructor-super", rule, { } }`, - errors: [{ messageId: "duplicate", type: "CallExpression" }] - }, - { - code: `class C extends D { + errors: [{ messageId: "duplicate", type: "CallExpression" }], + }, + { + code: `class C extends D { constructor() { while (foo) { @@ -376,7 +405,7 @@ ruleTester.run("constructor-super", rule, { } }`, - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - } - ] + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + ], }); diff --git a/tests/lib/rules/curly.js b/tests/lib/rules/curly.js index 9f2d2682ceed..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/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 7dd6529d30fc..d7828c52de19 100644 --- a/tests/lib/rules/default-case-last.js +++ b/tests/lib/rules/default-case-last.js @@ -22,16 +22,16 @@ const RuleTester = require("../../../lib/rule-tester/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 b46d9f2d0e71..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/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 856eba217a8b..374d70901ed3 100644 --- a/tests/lib/rules/default-param-last.js +++ b/tests/lib/rules/default-param-last.js @@ -18,97 +18,636 @@ const RuleTester = require("../../../lib/rule-tester/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 5e53607c0a41..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/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 7d94b106e883..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/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 a5248ada3001..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/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 2464ad1e0f8e..43fee3fb11f8 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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -24,152 +24,548 @@ 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", + }, + ], + }, + { + 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, + }, + ], + }, - // 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, + }, + ], + }, + { + 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, + }, + ], + }, - // 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 f5df5bdfbeb7..2f7d6a33a8b1 100644 --- a/tests/lib/rules/for-direction.js +++ b/tests/lib/rules/for-direction.js @@ -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 889f082e3620..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/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 2f2c2114812e..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/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 ad4c322b2ef6..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/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 63fcda87766e..26cd955679bd 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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,336 +19,399 @@ 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: "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" } }] - } - ], + // 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" }, + }, + ], + }, + ], - 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" - } - ] - } - ] + 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", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/function-call-argument-newline.js b/tests/lib/rules/function-call-argument-newline.js index f29fb4f78e14..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/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 d5a28d0bf119..0865ab6cf074 100644 --- a/tests/lib/rules/function-paren-newline.js +++ b/tests/lib/rules/function-paren-newline.js @@ -14,584 +14,590 @@ 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 05953d7f59cf..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/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 5c622043f8e9..1e624ecafc88 100644 --- a/tests/lib/rules/getter-return.js +++ b/tests/lib/rules/getter-return.js @@ -18,317 +18,413 @@ const RuleTester = require("../../../lib/rule-tester/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 fa6b3a380f78..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/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 482b56a8b270..7d2bc849b71b 100644 --- a/tests/lib/rules/grouped-accessor-pairs.js +++ b/tests/lib/rules/grouped-accessor-pairs.js @@ -19,457 +19,1018 @@ const RuleTester = require("../../../lib/rule-tester/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 a4da53be6016..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/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 a42981158221..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/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 7d5b4d3f7f31..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/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 834652196a34..e9fc9dc48831 100644 --- a/tests/lib/rules/id-denylist.js +++ b/tests/lib/rules/id-denylist.js @@ -10,1483 +10,1459 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/id-denylist"), - RuleTester = require("../../../lib/rule-tester/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 } - }, + // 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 }, + }, - // 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 - }, + // 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, + }, - // 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", + }, + ], + }, - // 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" - } - ] + // 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", + }, + ], + }, - }, - { - 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" - } - ] - } - ] + // 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 91bfd0882662..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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,894 +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: "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]"] }] }, + 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 - } - }, + // 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, + }, + }, - // 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 - ] - }, + // 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], + }, - // 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 - ] - }, + // 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 - ] - } - ] + // 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 f0517f20430a..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/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,749 +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: ["^[^_]+$"], - 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 } - }, + // 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 }, + }, - // 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 } - } + // 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", + }, + ], + }, - ], - 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 { } @@ -774,226 +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'." - } - ] - } - ] + // 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 31b2ec7f2e37..a4aaf293cec8 100644 --- a/tests/lib/rules/implicit-arrow-linebreak.js +++ b/tests/lib/rules/implicit-arrow-linebreak.js @@ -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 6d3ec2eea8b0..bc4f17a035e1 100644 --- a/tests/lib/rules/indent-legacy.js +++ b/tests/lib/rules/indent-legacy.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/indent-legacy"), - RuleTester = require("../../../lib/rule-tester/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); const fs = require("node:fs"); const path = require("node:path"); @@ -18,8 +18,20 @@ 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 8afbcc0ec9fd..ca83c8a1fae2 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/indent"), - RuleTester = require("../../../lib/rule-tester/rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); const fs = require("node:fs"); const path = require("node:path"); @@ -18,12 +18,23 @@ 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,59 +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] - }, - 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` @@ -4687,10 +4755,10 @@ ruleTester.run("indent", rule, { /> `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` @@ -4701,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 @@ -4768,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 ? (
@@ -4805,7 +4873,7 @@ ruleTester.run("indent", rule, {
) `, - unIndent` + unIndent`
{ /* foo */ @@ -4813,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 @@ -4943,7 +5011,7 @@ ruleTester.run("indent", rule, { `, - unIndent` + unIndent` < /* * Comment @@ -4952,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`
{ ( @@ -5027,7 +5095,7 @@ ruleTester.run("indent", rule, { }
`, - unIndent` + unIndent` function A() { return (
@@ -5041,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 }, @@ -5121,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(); @@ -5139,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, @@ -5189,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 \${ @@ -5209,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(); @@ -5256,10 +5349,10 @@ ruleTester.run("indent", rule, { doSomethingElse(); } `, - options: [4, { ignoreComments: true }] - }, - { - code: unIndent` + options: [4, { ignoreComments: true }], + }, + { + code: unIndent` if (foo) { doSomething(); @@ -5267,9 +5360,9 @@ ruleTester.run("indent", rule, { doSomethingElse(); } `, - options: [4, { ignoreComments: true }] - }, - unIndent` + options: [4, { ignoreComments: true }], + }, + unIndent` const obj = { foo () { return condition ? // comment @@ -5279,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 }, @@ -5317,7 +5410,7 @@ ruleTester.run("indent", rule, { bar }]; `, - unIndent` + unIndent` [{ foo }, @@ -5328,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 @@ -5405,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 @@ -5433,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 @@ -5461,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 @@ -5513,7 +5606,7 @@ ruleTester.run("indent", rule, { ;[1, 2, 3].forEach(() => {}) } `, - unIndent` + unIndent` { const foo = 1 const bar = foo @@ -5521,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 @@ -5560,10 +5653,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` { tag\` multiline @@ -5575,10 +5668,10 @@ ruleTester.run("indent", rule, { }); } `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo(() => { tagOne\` multiline @@ -5598,10 +5691,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` { tagOne\` \${a} \${b} @@ -5621,10 +5714,10 @@ ruleTester.run("indent", rule, { }); }; `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` tagOne\`multiline \${a} \${b} template @@ -5641,10 +5734,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` tagOne\`multiline template literal @@ -5658,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 @@ -5695,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\` @@ -5727,10 +5820,10 @@ ruleTester.run("indent", rule, { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .bar\` \${a} \${b} @@ -5738,10 +5831,10 @@ ruleTester.run("indent", rule, { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .test\` \${a} \${b} @@ -5749,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} @@ -5761,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) => @@ -5800,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(); @@ -5872,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(); @@ -5884,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(); @@ -5896,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(); @@ -5908,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(); @@ -5920,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(); @@ -5932,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 { @@ -5945,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, @@ -5957,11 +6098,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { @@ -5970,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) { @@ -5983,11 +6124,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { { @@ -5996,11 +6137,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} @@ -6012,11 +6153,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { @@ -6029,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; @@ -6046,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) { @@ -6067,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 { @@ -6081,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; @@ -6095,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() @@ -6202,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(); @@ -6373,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(); @@ -6383,7 +6527,7 @@ ruleTester.run("indent", rule, { if (bar) doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6392,7 +6536,7 @@ ruleTester.run("indent", rule, { doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6401,7 +6545,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6411,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(); @@ -6426,7 +6570,7 @@ ruleTester.run("indent", rule, { doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6434,7 +6578,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6443,7 +6587,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6456,7 +6600,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6468,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(); @@ -6483,7 +6627,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6492,7 +6636,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6501,7 +6645,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6510,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; })) { @@ -6556,7 +6704,7 @@ ruleTester.run("indent", rule, { c++; // <- } `, - output: unIndent` + output: unIndent` if (array.some(function(){ return true; })) { @@ -6565,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(); @@ -6732,7 +6896,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6745,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(); @@ -6779,7 +6949,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6791,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": @@ -6815,7 +6985,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": case "2": @@ -6835,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(); @@ -6850,7 +7024,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6861,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(); @@ -6897,7 +7071,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch (a) { case '1': b(); @@ -6907,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) { @@ -6995,7 +7163,7 @@ ruleTester.run("indent", rule, { }); }; `, - output: unIndent` + output: unIndent` TestClass.prototype.method = function () { return Promise.resolve(3) .then(function (x) { @@ -7003,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 }, @@ -7032,7 +7198,7 @@ ruleTester.run("indent", rule, { x: 2 }]; `, - output: unIndent` + output: unIndent` lmn = [{ a: 1 }, @@ -7043,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++; @@ -7184,7 +7348,7 @@ ruleTester.run("indent", rule, { }, b; `, - output: unIndent` + output: unIndent` var a = function() { a++; b++; @@ -7192,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( @@ -7401,7 +7569,7 @@ ruleTester.run("indent", rule, { } ) `, - output: unIndent` + output: unIndent` define([ 'foo' ], function( @@ -7411,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') @@ -7439,7 +7610,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function salutation () { switch (1) { case 0: return console.log('hi') @@ -7447,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(); @@ -7477,7 +7644,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch (a) { case '1': b(); @@ -7487,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 }), @@ -7691,7 +7846,7 @@ ruleTester.run("indent", rule, { }), d = 4; `, - output: unIndent` + output: unIndent` var a = new Test({ a: 1 }), @@ -7701,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 = @@ -7718,7 +7873,7 @@ ruleTester.run("indent", rule, { b: 2 }; `, - output: unIndent` + output: unIndent` var abc = 5, c = 2, xyz = @@ -7727,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: { @@ -7758,7 +7913,7 @@ ruleTester.run("indent", rule, { }, bar = 1; `, - output: unIndent` + output: unIndent` var foo = { bar: 1, baz: { @@ -7767,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(){} @@ -7846,7 +8003,7 @@ ruleTester.run("indent", rule, { get b(){} }; `, - output: unIndent` + output: unIndent` var a = 1, B = class { constructor(){} @@ -7854,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(); @@ -7868,7 +8025,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` { if(a){ foo(); @@ -7878,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(); @@ -7892,7 +8049,7 @@ ruleTester.run("indent", rule, { } `, - output: unIndent` + output: unIndent` { if(a){ foo(); @@ -7902,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(); @@ -7914,7 +8071,7 @@ ruleTester.run("indent", rule, { bar(); } `, - output: unIndent` + output: unIndent` { if(a) foo(); @@ -7922,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"]) - }, - { + errors: expectedErrors([3, 8, 4, "Identifier"]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 - // 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(); @@ -8285,7 +8467,7 @@ ruleTester.run("indent", rule, { qux(); } `, - output: unIndent` + output: unIndent` if (foo) bar(); else if (baz) { foobar(); @@ -8293,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, @@ -8407,7 +8618,7 @@ ruleTester.run("indent", rule, { bar(); } `, - output: unIndent` + output: unIndent` var foo = function(aaa, bbb, ccc, @@ -8415,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) { @@ -8543,7 +8779,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function foo() { function bar(baz, qux) { @@ -8551,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) { @@ -8563,7 +8799,7 @@ ruleTester.run("indent", rule, { }; } `, - output: unIndent` + output: unIndent` function foo() { var bar = function(baz, qux) { @@ -8571,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 { } @@ -8603,7 +8845,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` { try { } @@ -8613,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){ @@ -8681,7 +8923,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function test(){ switch(length){ case 1: return function(a){ @@ -8690,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 : @@ -8908,7 +9173,7 @@ ruleTester.run("indent", rule, { qux ] `, - output: unIndent` + output: unIndent` [ foo ? bar : @@ -8916,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 @@ -8932,7 +9197,7 @@ ruleTester.run("indent", rule, { return false } `, - output: unIndent` + output: unIndent` condition ? () => { return true @@ -8945,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 @@ -8973,7 +9238,7 @@ ruleTester.run("indent", rule, { return false } `, - output: unIndent` + output: unIndent` condition ? () => { return true @@ -8986,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 @@ -9014,7 +9278,7 @@ ruleTester.run("indent", rule, { bar(); // trailing comment `, - output: unIndent` + output: unIndent` foo(); // comment /* multiline @@ -9022,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, @@ -9079,7 +9346,7 @@ ruleTester.run("indent", rule, { foobar: baz = foobar } = qux; `, - output: unIndent` + output: unIndent` var { foo, bar, @@ -9087,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 }, @@ -9209,7 +9497,7 @@ ruleTester.run("indent", rule, { qux: 4 } ] `, - output: unIndent` + output: unIndent` var foo = [ { bar: 1, baz: 2 }, @@ -9217,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, @@ -9275,7 +9573,7 @@ ruleTester.run("indent", rule, { foobar = baz ] = qux; `, - output: unIndent` + output: unIndent` var [ foo, bar, @@ -9283,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, @@ -9347,7 +9653,7 @@ ruleTester.run("indent", rule, { baz }; `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9355,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, @@ -9367,7 +9676,7 @@ ruleTester.run("indent", rule, { baz } from 'qux'; `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9375,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 \${ @@ -9595,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 \${ @@ -9603,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 \${ @@ -9614,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 \${ @@ -9622,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( @@ -9684,7 +10024,7 @@ ruleTester.run("indent", rule, { ); } `, - output: unIndent` + output: unIndent` function f() { return asyncCall() .then( @@ -9697,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, { @@ -9780,7 +10136,7 @@ ruleTester.run("indent", rule, { } ) `, - output: unIndent` + output: unIndent` foo( bar, { @@ -9788,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, @@ -9805,7 +10161,7 @@ ruleTester.run("indent", rule, { baz } `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9813,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 @@ -9998,7 +10358,7 @@ ruleTester.run("indent", rule, { ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz @@ -10007,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 ? @@ -10025,7 +10385,7 @@ ruleTester.run("indent", rule, { boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? @@ -10034,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( @@ -10324,7 +10720,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName = (baz( @@ -10332,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( @@ -10343,7 +10743,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName = (baz( @@ -10351,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( @@ -10362,7 +10766,7 @@ ruleTester.run("indent", rule, { 'bar' ); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName =baz( @@ -10370,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; @@ -10405,7 +10809,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` namespace Foo { const bar = 3, baz = 2; @@ -10415,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, @@ -10433,7 +10842,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` abstract class Foo { public bar() { let aaa = 4, @@ -10447,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 { @@ -10464,7 +10879,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function foo() { function bar() { abstract class X { @@ -10477,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() { @@ -10502,7 +10923,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` namespace Unknown { function foo() { function bar() { @@ -10517,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 ( @@ -10617,7 +11044,7 @@ ruleTester.run("indent", rule, { ); } `, - output: unIndent` + output: unIndent` function App() { return ( @@ -10626,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 (

`; } //----------------------------------------------------------------------------- @@ -117,28 +115,28 @@ const HTML_TEMPLATE = stripIndents` `; (async () => { - const [allSponsors, team] = await Promise.all([ - fetchSponsorsMarkdown(), - fetchTeamData() - ]); - - // replace all of the section - let newReadme = readme.replace( - /[\w\W]*?/u, - ejs.render(HTML_TEMPLATE, { - team, - formatTeamMembers - }) - ); - - newReadme = newReadme.replace( - /[\w\W]*?/u, - `\n${allSponsors}\n` - ); - - // replace multiple consecutive blank lines with just one blank line - newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); - - // output to the file - fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); + const [allSponsors, team] = await Promise.all([ + fetchSponsorsMarkdown(), + fetchTeamData(), + ]); + + // replace all of the section + let newReadme = readme.replace( + /[\w\W]*?/u, + ejs.render(HTML_TEMPLATE, { + team, + formatTeamMembers, + }), + ); + + newReadme = newReadme.replace( + /[\w\W]*?/u, + `\n\n${allSponsors}\n\n`, + ); + + // replace multiple consecutive blank lines with just one blank line + newReadme = newReadme.replace(/(?<=^|\n)\n{2,}/gu, "\n"); + + // output to the file + fs.writeFileSync(README_FILE_PATH, newReadme, "utf8"); })(); diff --git a/tools/update-rule-type-headers.js b/tools/update-rule-type-headers.js index f5c05d8a5b6a..9328a9092727 100644 --- a/tools/update-rule-type-headers.js +++ b/tools/update-rule-type-headers.js @@ -38,18 +38,17 @@ const ts = require("typescript"); * @returns {string} The escaped text. */ function escapeForMultilineComment(line) { - return line.replaceAll( - /(`*)([^`]+)\1/gu, - (substring, backticks, capture) => { - - // In a TSDoc comment, the sequence `*/` ("*" + "/") must be escaped as `*\/`. - // But escaping inside a markdown code block is not possible, so discard the backticks. - if (capture.includes("*/")) { - return capture.replaceAll("/", "\\/"); - } - return substring; - } - ); + return line.replaceAll( + /(`*)([^`]+)\1/gu, + (substring, backticks, capture) => { + // In a TSDoc comment, the sequence `*/` ("*" + "/") must be escaped as `*\/`. + // But escaping inside a markdown code block is not possible, so discard the backticks. + if (capture.includes("*/")) { + return capture.replaceAll("/", "\\/"); + } + return substring; + }, + ); } /** @@ -58,26 +57,26 @@ function escapeForMultilineComment(line) { * @returns {boolean} A boolean value indicating whether the specified interface extends `Linter.RulesRecord`. */ function extendsRulesRecord(node) { - const { heritageClauses } = node; - - if (!heritageClauses) { - return false; - } - for (const heritageClause of heritageClauses) { - for (const { expression } of heritageClause.types) { - if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) { - if ( - expression.expression.kind === ts.SyntaxKind.Identifier && - expression.expression.text === "Linter" && - expression.name.kind === ts.SyntaxKind.Identifier && - expression.name.text === "RulesRecord" - ) { - return true; - } - } - } - } - return false; + const { heritageClauses } = node; + + if (!heritageClauses) { + return false; + } + for (const heritageClause of heritageClauses) { + for (const { expression } of heritageClause.types) { + if (expression.kind === ts.SyntaxKind.PropertyAccessExpression) { + if ( + expression.expression.kind === ts.SyntaxKind.Identifier && + expression.expression.text === "Linter" && + expression.name.kind === ts.SyntaxKind.Identifier && + expression.name.text === "RulesRecord" + ) { + return true; + } + } + } + } + return false; } /** @@ -86,7 +85,7 @@ function extendsRulesRecord(node) { * @returns {string} The markdown link. */ function formatNameAndURL({ name, url }) { - return `[\`${name}\`](${url})`; + return `[\`${name}\`](${url})`; } /** @@ -95,13 +94,13 @@ function formatNameAndURL({ name, url }) { * @returns {string} The formatted text of the TSDoc comment. */ function formatTSDoc(lines) { - const formattedLines = ["/**"]; + const formattedLines = ["/**"]; - for (const line of lines) { - formattedLines.push(` *${line ? ` ${line}` : ""}`); - } - formattedLines.push(" */"); - return formattedLines.join("\n"); + for (const line of lines) { + formattedLines.push(`\t *${line ? ` ${line}` : ""}`); + } + formattedLines.push("\t */"); + return formattedLines.join("\n"); } /** @@ -111,18 +110,22 @@ function formatTSDoc(lines) { * @returns {Set} The names of the rules to be considered for the current run. */ function getConsideredRuleIds(args) { - let ruleIds; - - if (args.length) { - const ruleDir = join(__dirname, "../lib/rules"); - - ruleIds = args - .filter(arg => relative(arg, ruleDir) === ".." && basename(arg) !== "index.js") - .map(ruleFile => basename(ruleFile, ".js")); - } else { - ruleIds = rules.keys(); - } - return new Set(ruleIds); + let ruleIds; + + if (args.length) { + const ruleDir = join(__dirname, "../lib/rules"); + + ruleIds = args + .filter( + arg => + relative(arg, ruleDir) === ".." && + basename(arg) !== "index.js", + ) + .map(ruleFile => basename(ruleFile, ".js")); + } else { + ruleIds = rules.keys(); + } + return new Set(ruleIds); } /** @@ -134,41 +137,50 @@ function getConsideredRuleIds(args) { * The text positions indicate the locations of the TSDoc header comments and the end of the type definition in the source. */ function getTextPositionsMap(sourceText, consideredRuleIds) { - const textPositionsMap = new Map(); - const ast = ts.createSourceFile("", sourceText); - - for (const statement of ast.statements) { - if (statement.kind === ts.SyntaxKind.InterfaceDeclaration && extendsRulesRecord(statement)) { - const { members } = statement; - - for (const member of members) { - if (member.kind === ts.SyntaxKind.PropertySignature) { - const ruleId = member.name.text; - - if (consideredRuleIds.has(ruleId)) { - let textPositions; - - // Only the last TSDoc comment is regarded. - const tsDoc = member.jsDoc?.at(-1); - - if (tsDoc) { - textPositions = { tsDocStart: tsDoc.pos, tsDocEnd: tsDoc.end }; - } else { - const regExp = /\S/gu; - - regExp.lastIndex = member.pos; - const { index } = regExp.exec(sourceText); - - textPositions = { tsDocStart: index, tsDocEnd: index }; - } - textPositions.typeEnd = member.end; - textPositionsMap.set(ruleId, textPositions); - } - } - } - } - } - return textPositionsMap; + const textPositionsMap = new Map(); + const ast = ts.createSourceFile("", sourceText); + + for (const statement of ast.statements) { + if ( + statement.kind === ts.SyntaxKind.InterfaceDeclaration && + extendsRulesRecord(statement) + ) { + const { members } = statement; + + for (const member of members) { + if (member.kind === ts.SyntaxKind.PropertySignature) { + const ruleId = member.name.text; + + if (consideredRuleIds.has(ruleId)) { + let textPositions; + + // Only the last TSDoc comment is regarded. + const tsDoc = member.jsDoc?.at(-1); + + if (tsDoc) { + textPositions = { + tsDocStart: tsDoc.pos, + tsDocEnd: tsDoc.end, + }; + } else { + const regExp = /\S/gu; + + regExp.lastIndex = member.pos; + const { index } = regExp.exec(sourceText); + + textPositions = { + tsDocStart: index, + tsDocEnd: index, + }; + } + textPositions.typeEnd = member.end; + textPositionsMap.set(ruleId, textPositions); + } + } + } + } + } + return textPositionsMap; } /** @@ -178,18 +190,18 @@ function getTextPositionsMap(sourceText, consideredRuleIds) { * @returns {string} The reworded rule description. */ function paraphraseDescription(description) { - let newDescription; - const match = /^(Disallow|Enforce|Require) /u.exec(description); - - if (match) { - newDescription = `Rule to ${description[0].toLowerCase()}${description.slice(1)}`; - } else { - newDescription = description; - } - if (!newDescription.endsWith(".")) { - newDescription += "."; - } - return newDescription; + let newDescription; + const match = /^(Disallow|Enforce|Require) /u.exec(description); + + if (match) { + newDescription = `Rule to ${description[0].toLowerCase()}${description.slice(1)}`; + } else { + newDescription = description; + } + if (!newDescription.endsWith(".")) { + newDescription += "."; + } + return newDescription; } /** @@ -198,22 +210,26 @@ function paraphraseDescription(description) { * @returns {string[]} The deprecation notice for the specified rule. */ function createDeprecationNotice({ deprecated }) { - const deprecationNotice = [`@deprecated since ${deprecated.deprecatedSince}.`]; - - deprecationNotice.push(escapeForMultilineComment(deprecated.message)); - if (deprecated.replacedBy?.length) { - const replacements = deprecated.replacedBy.map(replacedBy => { - let replacement = formatNameAndURL(replacedBy.rule); - - if (replacedBy.plugin) { - replacement += ` in ${formatNameAndURL(replacedBy.plugin)}`; - } - return replacement; - }).join(" or "); - - deprecationNotice.push(`Please, use ${replacements}.`); - } - return deprecationNotice; + const deprecationNotice = [ + `@deprecated since ${deprecated.deprecatedSince}.`, + ]; + + deprecationNotice.push(escapeForMultilineComment(deprecated.message)); + if (deprecated.replacedBy?.length) { + const replacements = deprecated.replacedBy + .map(replacedBy => { + let replacement = formatNameAndURL(replacedBy.rule); + + if (replacedBy.plugin) { + replacement += ` in ${formatNameAndURL(replacedBy.plugin)}`; + } + return replacement; + }) + .join(" or "); + + deprecationNotice.push(`Please, use ${replacements}.`); + } + return deprecationNotice; } /** @@ -222,30 +238,33 @@ function createDeprecationNotice({ deprecated }) { * @returns {string} The TSDoc comment. */ function createTSDoc(ruleId) { - const ruleMeta = rules.get(ruleId).meta; - const ruleDocs = ruleMeta.docs; - const since = added[ruleId]; - const lines = [escapeForMultilineComment(paraphraseDescription(ruleDocs.description)), ""]; - - if (ruleDocs.recommended) { - lines.push( - "@remarks", - "Recommended by ESLint, the rule was enabled in `eslint:recommended`.", - "" - ); - } - if (since) { - lines.push(`@since ${since}`); - } - if (ruleMeta.deprecated) { - const deprecationNotice = createDeprecationNotice(ruleMeta); - - lines.push(...deprecationNotice); - } - lines.push(`@see ${ruleDocs.url}`); - const tsDoc = formatTSDoc(lines); - - return tsDoc; + const ruleMeta = rules.get(ruleId).meta; + const ruleDocs = ruleMeta.docs; + const since = added[ruleId]; + const lines = [ + escapeForMultilineComment(paraphraseDescription(ruleDocs.description)), + "", + ]; + + if (ruleDocs.recommended) { + lines.push( + "@remarks", + "Recommended by ESLint, the rule was enabled in `eslint:recommended`.", + "", + ); + } + if (since) { + lines.push(`@since ${since}`); + } + if (ruleMeta.deprecated) { + const deprecationNotice = createDeprecationNotice(ruleMeta); + + lines.push(...deprecationNotice); + } + lines.push(`@see ${ruleDocs.url}`); + const tsDoc = formatTSDoc(lines); + + return tsDoc; } /** @@ -256,36 +275,41 @@ function createTSDoc(ruleId) { * @returns {Promise>} The names of the rules found in the `.d.ts` file. */ async function updateTypeDeclaration(ruleTypeFile, consideredRuleIds, check) { - const sourceText = await readFile(ruleTypeFile, "utf-8"); - const textPositionsMap = getTextPositionsMap(sourceText, consideredRuleIds); - const sortedRuleIds = [...textPositionsMap.keys()].sort(); - const chunks = []; - let lastPos = 0; - - for (const [, { tsDocStart: insertStart, typeEnd: insertEnd }] of textPositionsMap) { - const textBeforeTSDoc = sourceText.slice(lastPos, insertStart); - const ruleId = sortedRuleIds.shift(); - const { tsDocEnd, typeEnd } = textPositionsMap.get(ruleId); - const tsDoc = createTSDoc(ruleId); - const ruleText = sourceText.slice(tsDocEnd, typeEnd); - - chunks.push(textBeforeTSDoc, tsDoc); - if (sourceText[tsDocEnd] !== "\n") { - chunks.push("\n "); - } - chunks.push(ruleText); - lastPos = insertEnd; - } - chunks.push(sourceText.slice(Math.max(0, lastPos))); - const newSourceText = chunks.join(""); - - if (newSourceText !== sourceText) { - if (check) { - throw new Error("The rule types are not up-to-date. Please, run `node tools/update-rule-type-headers.js` to fix."); - } - await writeFile(ruleTypeFile, newSourceText); - } - return textPositionsMap.keys(); + const sourceText = await readFile(ruleTypeFile, "utf-8"); + const textPositionsMap = getTextPositionsMap(sourceText, consideredRuleIds); + const sortedRuleIds = [...textPositionsMap.keys()].sort(); + const chunks = []; + let lastPos = 0; + + for (const [ + , + { tsDocStart: insertStart, typeEnd: insertEnd }, + ] of textPositionsMap) { + const textBeforeTSDoc = sourceText.slice(lastPos, insertStart); + const ruleId = sortedRuleIds.shift(); + const { tsDocEnd, typeEnd } = textPositionsMap.get(ruleId); + const tsDoc = createTSDoc(ruleId); + const ruleText = sourceText.slice(tsDocEnd, typeEnd); + + chunks.push(textBeforeTSDoc, tsDoc); + if (sourceText[tsDocEnd] !== "\n") { + chunks.push("\n "); + } + chunks.push(ruleText); + lastPos = insertEnd; + } + chunks.push(sourceText.slice(Math.max(0, lastPos))); + const newSourceText = chunks.join(""); + + if (newSourceText !== sourceText) { + if (check) { + throw new Error( + "The rule types are not up-to-date. Please, run `node tools/update-rule-type-headers.js` to fix.", + ); + } + await writeFile(ruleTypeFile, newSourceText); + } + return textPositionsMap.keys(); } //----------------------------------------------------------------------------- @@ -293,32 +317,36 @@ async function updateTypeDeclaration(ruleTypeFile, consideredRuleIds, check) { //----------------------------------------------------------------------------- (async () => { - let check = false; - const args = process.argv.slice(2).filter(arg => { - if (arg === "--check") { - check = true; - return false; - } - return true; - }); - const consideredRuleIds = getConsideredRuleIds(args); - const ruleTypeFile = join(__dirname, "../lib/types/rules.d.ts"); - const untypedRuleIds = []; - - console.log(`Considering ${consideredRuleIds.size} rule(s).`); - const ruleIds = await updateTypeDeclaration(ruleTypeFile, consideredRuleIds, check); - const typedRuleIds = new Set(ruleIds); - - for (const ruleId of consideredRuleIds) { - if (!typedRuleIds.has(ruleId)) { - untypedRuleIds.push(ruleId); - } - } - if (untypedRuleIds.length) { - console.warn( - "The following rules have no type definition:%s", - untypedRuleIds.map(ruleId => `\n* ${ruleId}`).join("") - ); - process.exitCode = 1; - } + let check = false; + const args = process.argv.slice(2).filter(arg => { + if (arg === "--check") { + check = true; + return false; + } + return true; + }); + const consideredRuleIds = getConsideredRuleIds(args); + const ruleTypeFile = join(__dirname, "../lib/types/rules.d.ts"); + const untypedRuleIds = []; + + console.log(`Considering ${consideredRuleIds.size} rule(s).`); + const ruleIds = await updateTypeDeclaration( + ruleTypeFile, + consideredRuleIds, + check, + ); + const typedRuleIds = new Set(ruleIds); + + for (const ruleId of consideredRuleIds) { + if (!typedRuleIds.has(ruleId)) { + untypedRuleIds.push(ruleId); + } + } + if (untypedRuleIds.length) { + console.warn( + "The following rules have no type definition:%s", + untypedRuleIds.map(ruleId => `\n* ${ruleId}`).join(""), + ); + process.exitCode = 1; + } })(); diff --git a/wdio.conf.js b/wdio.conf.js deleted file mode 100644 index cca5f31acea9..000000000000 --- a/wdio.conf.js +++ /dev/null @@ -1,388 +0,0 @@ -"use strict"; - -const path = require("node:path"); -const commonjs = require("vite-plugin-commonjs").default; - -exports.config = { - - /* - * - * ==================== - * Runner Configuration - * ==================== - * WebdriverIO supports running e2e tests as well as unit and component tests. - */ - runner: ["browser", { - viteConfig: { - resolve: { - alias: { - util: "rollup-plugin-node-polyfills/polyfills/util", - path: "rollup-plugin-node-polyfills/polyfills/path", - "node:path": "rollup-plugin-node-polyfills/polyfills/path", - assert: "rollup-plugin-node-polyfills/polyfills/assert" - } - }, - plugins: [ - commonjs(), - { - name: "wdio:import-fix", - enforce: "pre", - transform(source, id) { - if (!id.endsWith("/tests/lib/linter/linter.js")) { - return source; - } - - return source.replace( - 'const { Linter } = require("../../../lib/linter");', - 'const { Linter } = require("../../../build/eslint");\n' + - 'process.cwd = () => "/";' - ); - } - } - ] - } - }], - - /* - * - * ================== - * Specify Test Files - * ================== - * Define which test specs should run. The pattern is relative to the directory - * of the configuration file being run. - * - * The specs are defined as an array of spec files (optionally using wildcards - * that will be expanded). The test for each spec file will be run in a separate - * worker process. In order to have a group of spec files run in the same worker - * process simply enclose them in an array within the specs array. - * - * If you are calling `wdio` from an NPM script (see https://docs.npmjs.com/cli/run-script), - * then the current working directory is where your `package.json` resides, so `wdio` - * will be called from there. - * - */ - specs: [ - path.join(__dirname, "tests", "lib", "linter", "linter.js") - ], - - // Patterns to exclude. - exclude: [], - - /* - * - * ============ - * Capabilities - * ============ - * Define your capabilities here. WebdriverIO can run multiple capabilities at the same - * time. Depending on the number of capabilities, WebdriverIO launches several test - * sessions. Within your capabilities you can overwrite the spec and exclude options in - * order to group specific specs to a specific capability. - * - * First, you can define how many instances should be started at the same time. Let"s - * say you have 3 different capabilities (Chrome, Firefox, and Safari) and you have - * set maxInstances to 1; wdio will spawn 3 processes. Therefore, if you have 10 spec - * files and you set maxInstances to 10, all spec files will get tested at the same time - * and 30 processes will get spawned. The property handles how many capabilities - * from the same test should run tests. - * - */ - maxInstances: 10, - - /* - * - * If you have trouble getting all important capabilities together, check out the - * Sauce Labs platform configurator - a great tool to configure your capabilities: - * https://saucelabs.com/platform/platform-configurator - * - */ - capabilities: [{ - browserName: "chrome", - "goog:chromeOptions": { - args: process.env.CI ? ["headless", "disable-gpu"] : [] - } - }], - - /* - * - * =================== - * Test Configurations - * =================== - * Define all options that are relevant for the WebdriverIO instance here - * - * Level of logging verbosity: trace | debug | info | warn | error | silent - */ - logLevel: "trace", - outputDir: "./wdio-logs", - - /* - * - * Set specific log levels per logger - * loggers: - * - webdriver, webdriverio - * - @wdio/browserstack-service, @wdio/devtools-service, @wdio/sauce-service - * - @wdio/mocha-framework, @wdio/jasmine-framework - * - @wdio/local-runner - * - @wdio/sumologic-reporter - * - @wdio/cli, @wdio/config, @wdio/utils - * Level of logging verbosity: trace | debug | info | warn | error | silent - * logLevels: { - * webdriver: 'info', - * '@wdio/appium-service': 'info' - * }, - * - * If you only want to run your tests until a specific amount of tests have failed use - * bail (default is 0 - don't bail, run all tests). - */ - bail: 0, - - /* - * - * Set a base URL in order to shorten url command calls. If your `url` parameter starts - * with `/`, the base url gets prepended, not including the path portion of your baseUrl. - * If your `url` parameter starts without a scheme or `/` (like `some/path`), the base url - * gets prepended directly. - */ - baseUrl: "", - - /* - * - * Default timeout for all waitFor* commands. - */ - waitforTimeout: 10000, - - /* - * - * Default timeout in milliseconds for request - * if browser driver or grid doesn't send response - */ - connectionRetryTimeout: 120000, - - /* - * - * Default request retries count - */ - connectionRetryCount: 3, - - /* - * Framework you want to run your specs with. - * The following are supported: Mocha, Jasmine, and Cucumber - * see also: https://webdriver.io/docs/frameworks - * - * Make sure you have the wdio adapter package for the specific framework installed - * before running any tests. - */ - framework: "mocha", - - /* - * - * The number of times to retry the entire specfile when it fails as a whole - * specFileRetries: 1, - * - * Delay in seconds between the spec file retry attempts - * specFileRetriesDelay: 0, - * - * Whether or not retried specfiles should be retried immediately or deferred to the end of the queue - * specFileRetriesDeferred: false, - * - * Test reporter for stdout. - * The only one supported by default is 'dot' - * see also: https://webdriver.io/docs/dot-reporter - */ - reporters: ["concise"], - - /* - * - * Options to be passed to Mocha. - * See the full list at http://mochajs.org/ - */ - mochaOpts: { - ui: "bdd", - timeout: 5 * 60 * 1000, // 5min - grep: "@skipWeb", - invert: true - } - - /* - * - * ===== - * Hooks - * ===== - * WebdriverIO provides several hooks you can use to interfere with the test process in order to enhance - * it and to build services around it. You can either apply a single function or an array of - * methods to it. If one of them returns with a promise, WebdriverIO will wait until that promise got - * resolved to continue. - */ - /** - * Gets executed once before all workers get launched. - * @param {Object} config wdio configuration object - * @param {Array} capabilities list of capabilities details - */ - /* - * onPrepare: function (config, capabilities) { - * }, - */ - /** - * Gets executed before a worker process is spawned and can be used to initialise specific service - * for that worker as well as modify runtime environments in an async fashion. - * @param {string} cid capability id (e.g 0-0) - * @param {Object} caps object containing capabilities for session that will be spawn in the worker - * @param {Object} specs specs to be run in the worker process - * @param {Object} args object that will be merged with the main configuration once worker is initialized - * @param {Object} execArgv list of string arguments passed to the worker process - */ - /* - * onWorkerStart: function (cid, caps, specs, args, execArgv) { - * }, - */ - /** - * Gets executed just after a worker process has exited. - * @param {string} cid capability id (e.g 0-0) - * @param {number} exitCode 0 - success, 1 - fail - * @param {Object} specs specs to be run in the worker process - * @param {number} retries number of retries used - */ - /* - * onWorkerEnd: function (cid, exitCode, specs, retries) { - * }, - */ - /** - * Gets executed just before initialising the webdriver session and test framework. It allows you - * to manipulate configurations depending on the capability or spec. - * @param {Object} config wdio configuration object - * @param {Array} capabilities list of capabilities details - * @param {Array} specs List of spec file paths that are to be run - * @param {string} cid worker id (e.g. 0-0) - */ - /* - * beforeSession: function (config, capabilities, specs, cid) { - * }, - */ - /** - * Gets executed before test execution begins. At this point you can access to all global - * variables like `browser`. It is the perfect place to define custom commands. - * @param {Array} capabilities list of capabilities details - * @param {Array} specs List of spec file paths that are to be run - * @param {Object} browser instance of created browser/device session - */ - /* - * before: function (capabilities, specs) { - * }, - */ - /** - * Runs before a WebdriverIO command gets executed. - * @param {string} commandName hook command name - * @param {Array} args arguments that command would receive - */ - /* - * beforeCommand: function (commandName, args) { - * }, - */ - /** - * Hook that gets executed before the suite starts - * @param {Object} suite suite details - */ - /* - * beforeSuite: function (suite) { - * }, - */ - /** - * Function to be executed before a test (in Mocha/Jasmine) starts. - */ - /* - * beforeTest: function (test, context) { - * }, - */ - /** - * Hook that gets executed _before_ a hook within the suite starts (e.g. runs before calling - * beforeEach in Mocha) - */ - /* - * beforeHook: function (test, context) { - * }, - */ - /** - * Hook that gets executed _after_ a hook within the suite starts (e.g. runs after calling - * afterEach in Mocha) - */ - /* - * afterHook: function (test, context, { error, result, duration, passed, retries }) { - * }, - */ - /** - * Function to be executed after a test (in Mocha/Jasmine only) - * @param {Object} test test object - * @param {Object} context scope object the test was executed with - * @param {Error} result.error error object in case the test fails, otherwise `undefined` - * @param {any} result.result return object of test function - * @param {number} result.duration duration of test - * @param {boolean} result.passed true if test has passed, otherwise false - * @param {Object} result.retries informations to spec related retries, e.g. `{ attempts: 0, limit: 0 }` - */ - /* - * afterTest: function(test, context, { error, result, duration, passed, retries }) { - * }, - */ - - - /** - * Hook that gets executed after the suite has ended - * @param {Object} suite suite details - */ - /* - * afterSuite: function (suite) { - * }, - */ - /** - * Runs after a WebdriverIO command gets executed - * @param {string} commandName hook command name - * @param {Array} args arguments that command would receive - * @param {number} result 0 - command success, 1 - command error - * @param {Object} error error object if any - */ - /* - * afterCommand: function (commandName, args, result, error) { - * }, - */ - /** - * Gets executed after all tests are done. You still have access to all global variables from - * the test. - * @param {number} result 0 - test pass, 1 - test fail - * @param {Array} capabilities list of capabilities details - * @param {Array} specs List of spec file paths that ran - */ - /* - * after: function (result, capabilities, specs) { - * }, - */ - /** - * Gets executed right after terminating the webdriver session. - * @param {Object} config wdio configuration object - * @param {Array} capabilities list of capabilities details - * @param {Array} specs List of spec file paths that ran - */ - /* - * afterSession: function (config, capabilities, specs) { - * }, - */ - /** - * Gets executed after all workers got shut down and the process is about to exit. An error - * thrown in the onComplete hook will result in the test run failing. - * @param {Object} exitCode 0 - success, 1 - fail - * @param {Object} config wdio configuration object - * @param {Array} capabilities list of capabilities details - * @param {Object} results object containing test results - */ - /* - * onComplete: function(exitCode, config, capabilities, results) { - * }, - */ - /** - * Gets executed when a refresh happens. - * @param {string} oldSessionId session ID of the old session - * @param {string} newSessionId session ID of the new session - */ - /* - * onReload: function(oldSessionId, newSessionId) { - * } - */ -}; diff --git a/webpack.config.js b/webpack.config.js index fb426465ef1e..d1c9ade5cddf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,57 +5,62 @@ const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); /** @type {import("webpack").Configuration} */ module.exports = { - mode: "none", - entry: { - eslint: ["core-js/stable", "regenerator-runtime/runtime", "./lib/linter/linter.js"] - }, - output: { - filename: "[name].js", - library: "[name]", - libraryTarget: "umd", - globalObject: "this" - }, - module: { - rules: [ - { - test: /\.m?js$/u, - loader: "babel-loader", - options: { - presets: [ - ["@babel/preset-env", { - debug: true, // ← to print actual browser versions + mode: "none", + entry: { + eslint: [ + "core-js/stable", + "regenerator-runtime/runtime", + "./lib/linter/linter.js", + ], + }, + output: { + filename: "[name].js", + library: "[name]", + libraryTarget: "umd", + globalObject: "this", + }, + module: { + rules: [ + { + test: /\.m?js$/u, + loader: "babel-loader", + options: { + presets: [ + [ + "@babel/preset-env", + { + debug: true, // ← to print actual browser versions - /* - * We want to remove `transform-unicode-regex` convert because of https://github.com/eslint/eslint/pull/12662. - * - * With `>0.5%`, `@babel/preset-env@7.7.6` prints below: - * - * transform-unicode-regex { "chrome":"49", "ie":"11", "safari":"5.1" } - * - * So this excludes those versions: - * - * - IE 11 - * - Chrome 49 (2016; the last version on Windows XP) - * - Safari 5.1 (2011-2013; the last version on Windows) - */ - targets: ">0.5%, not chrome 49, not ie 11, not safari 5.1" - }] - ] - } - } - ] - }, - plugins: [ - new webpack.NormalModuleReplacementPlugin( - /^node:/u, - resource => { - resource.request = resource.request.replace(/^node:/u, ""); - } - ), - new NodePolyfillPlugin() - ], - resolve: { - mainFields: ["browser", "main", "module"] - }, - stats: "errors-only" + /* + * We want to remove `transform-unicode-regex` convert because of https://github.com/eslint/eslint/pull/12662. + * + * With `>0.5%`, `@babel/preset-env@7.7.6` prints below: + * + * transform-unicode-regex { "chrome":"49", "ie":"11", "safari":"5.1" } + * + * So this excludes those versions: + * + * - IE 11 + * - Chrome 49 (2016; the last version on Windows XP) + * - Safari 5.1 (2011-2013; the last version on Windows) + */ + targets: + ">0.5%, not chrome 49, not ie 11, not safari 5.1", + }, + ], + ], + }, + }, + ], + }, + plugins: [ + new webpack.NormalModuleReplacementPlugin(/^node:/u, resource => { + resource.request = resource.request.replace(/^node:/u, ""); + }), + new NodePolyfillPlugin(), + ], + resolve: { + mainFields: ["browser", "main", "module"], + }, + stats: "errors-only", };